diff --git a/packages/abstractions/src/apiClientProxifier.ts b/packages/abstractions/src/apiClientProxifier.ts index c8b2d22e3..62df794da 100644 --- a/packages/abstractions/src/apiClientProxifier.ts +++ b/packages/abstractions/src/apiClientProxifier.ts @@ -150,6 +150,24 @@ function send( metadata.responseBodyFactory as ParsableFactory, metadata.errorMappings, ); + case "sendEnum": + if (!metadata.enumObject) { + throw new Error("couldn't find response body factory"); + } + return requestAdapter.sendEnum( + requestInfo, + metadata.enumObject, + metadata.errorMappings, + ); + case "sendCollectionOfEnum": + if (!metadata.enumObject) { + throw new Error("couldn't find response body factory"); + } + return requestAdapter.sendCollectionOfEnum( + requestInfo, + metadata.enumObject, + metadata.errorMappings, + ); case "sendCollectionOfPrimitive": if (!metadata.responseBodyFactory) { throw new Error("couldn't find response body factory"); @@ -416,6 +434,7 @@ export interface RequestMetadata { requestInformationContentSetMethod?: keyof RequestInformationSetContent; queryParametersMapper?: Record; uriTemplate: string; + enumObject?: EnumObject; } export interface RequestsMetadata { delete?: RequestMetadata; @@ -435,6 +454,8 @@ export interface NavigationMetadata { pathParametersMappings?: string[]; } +type EnumObject = Record> = T; + export type KeysToExcludeForNavigationMetadata = | KeysOfRequestsMetadata | "toDeleteRequestInformation" diff --git a/packages/abstractions/src/requestAdapter.ts b/packages/abstractions/src/requestAdapter.ts index 7ce564703..27a07f7ce 100644 --- a/packages/abstractions/src/requestAdapter.ts +++ b/packages/abstractions/src/requestAdapter.ts @@ -84,6 +84,34 @@ export interface RequestAdapter { requestInfo: RequestInformation, errorMappings: ErrorMappings | undefined, ): Promise; + /** + * Executes the HTTP request specified by the given RequestInformation and returns the deserialized enum response model. + * + * @template EnumObject - The type of the enum object. Must extend Record. + * @param {RequestInformation} requestInfo - The request info to execute. + * @param {EnumObject} enumObject - The Enum object expected in the response. + * @param {ErrorMappings | undefined} errorMappings - the error factories mapping to use in case of a failed request. + * @returns {Promise} - A promise that resolves to the response of the request, or undefined if an error occurred. + */ + sendEnum>( + requestInfo: RequestInformation, + enumObject: EnumObject, + errorMappings: ErrorMappings | undefined, + ): Promise; + /** + * Executes the HTTP request specified by the given RequestInformation and returns the deserialized response model collection. + * + * @template EnumObject - The type of the enum objects. Must extend Record. + * @param {RequestInformation} requestInfo - The request info to execute. + * @param {EnumObject} enumObject - The Enum object expected in the response. + * @param {ErrorMappings | undefined} errorMappings - the error factories mapping to use in case of a failed request. + * @returns {Promise} - with the deserialized response model collection. + */ + sendCollectionOfEnum>( + requestInfo: RequestInformation, + enumObject: EnumObject, + errorMappings: ErrorMappings | undefined, + ): Promise; /** * Enables the backing store proxies for the SerializationWriters and ParseNodes in use. * @param backingStoreFactory the backing store factory to use. diff --git a/packages/http/fetch/src/fetchRequestAdapter.ts b/packages/http/fetch/src/fetchRequestAdapter.ts index 5b7549972..99b4ab856 100644 --- a/packages/http/fetch/src/fetchRequestAdapter.ts +++ b/packages/http/fetch/src/fetchRequestAdapter.ts @@ -281,6 +281,74 @@ export class FetchRequestAdapter implements RequestAdapter { } }); }; + public sendEnum = >(requestInfo: RequestInformation, enumObject: EnumObject, errorMappings: ErrorMappings | undefined): Promise => { + if (!requestInfo) { + throw new Error("requestInfo cannot be null"); + } + return this.startTracingSpan(requestInfo, "sendEnum", async (span) => { + try { + const response = await this.getHttpResponseMessage(requestInfo, span); + const responseHandler = this.getResponseHandler(requestInfo); + if (responseHandler) { + span.addEvent(FetchRequestAdapter.eventResponseHandlerInvokedKey); + return await responseHandler.handleResponse(response, errorMappings); + } else { + try { + await this.throwIfFailedResponse(response, errorMappings, span); + if (this.shouldReturnUndefined(response)) return undefined; + const rootNode = await this.getRootParseNode(response); + return trace.getTracer(this.observabilityOptions.getTracerInstrumentationName()).startActiveSpan("getEnumValue", (deserializeSpan) => { + try { + span.setAttribute(FetchRequestAdapter.responseTypeAttributeKey, "enum"); + const result = rootNode.getEnumValue(enumObject); + return result as unknown as EnumObject[keyof EnumObject]; + } finally { + deserializeSpan.end(); + } + }); + } finally { + await this.purgeResponseBody(response); + } + } + } finally { + span.end(); + } + }) as Promise; + } + public sendCollectionOfEnum = >(requestInfo: RequestInformation, enumObject: EnumObject, errorMappings: ErrorMappings | undefined): Promise => { + if (!requestInfo) { + throw new Error("requestInfo cannot be null"); + } + return this.startTracingSpan(requestInfo, "sendCollectionOfEnum", async (span) => { + try { + const response = await this.getHttpResponseMessage(requestInfo, span); + const responseHandler = this.getResponseHandler(requestInfo); + if (responseHandler) { + span.addEvent(FetchRequestAdapter.eventResponseHandlerInvokedKey); + return await responseHandler.handleResponse(response, errorMappings); + } else { + try { + await this.throwIfFailedResponse(response, errorMappings, span); + if (this.shouldReturnUndefined(response)) return undefined; + const rootNode = await this.getRootParseNode(response); + return trace.getTracer(this.observabilityOptions.getTracerInstrumentationName()).startActiveSpan("getCollectionOfEnumValues", (deserializeSpan) => { + try { + const result = rootNode.getCollectionOfEnumValues(enumObject); + span.setAttribute(FetchRequestAdapter.responseTypeAttributeKey, "enum[]"); + return result as unknown as EnumObject[keyof EnumObject][]; + } finally { + deserializeSpan.end(); + } + }); + } finally { + await this.purgeResponseBody(response); + } + } + } finally { + span.end(); + } + }); + } public enableBackingStore = (backingStoreFactory?: BackingStoreFactory | undefined): void => { this.parseNodeFactory = enableBackingStoreForParseNodeFactory(this.parseNodeFactory); this.serializationWriterFactory = enableBackingStoreForSerializationWriterFactory(this.serializationWriterFactory); diff --git a/packages/http/fetch/test/common/fetchRequestAdapter.ts b/packages/http/fetch/test/common/fetchRequestAdapter.ts index 3d354a9d4..63dd436a3 100644 --- a/packages/http/fetch/test/common/fetchRequestAdapter.ts +++ b/packages/http/fetch/test/common/fetchRequestAdapter.ts @@ -13,6 +13,9 @@ import { HttpClient } from "../../src/httpClient"; import { getResponse } from "../testUtils"; import { createMockEntityFromDiscriminatorValue } from "./mockEntity"; import { MockParseNode, MockParseNodeFactory } from "./mockParseNodeFactory"; +import { JsonParseNode, JsonParseNodeFactory } from "@microsoft/kiota-serialization-json"; +import { TextParseNodeFactory } from "@microsoft/kiota-serialization-text"; +import { FormParseNodeFactory } from "@microsoft/kiota-serialization-form"; // eslint-disable-next-line no-var var Response = Response; @@ -20,6 +23,14 @@ if (typeof Response !== "object") { Response = getResponse(); } +const TestEnumObject = { + A: "a", + B: "b", + C: "c" +} as const; + +type TestEnum = (typeof TestEnumObject)[keyof typeof TestEnumObject]; + describe("FetchRequestAdapter.ts", () => { describe("getClaimsFromResponse", () => { it("should get claims from response header", async () => { @@ -124,6 +135,55 @@ describe("FetchRequestAdapter.ts", () => { }); } }); + describe("send enum", () => { + for (const statusCode of [200, 201, 202, 203]) { + const enumResponse = "a"; + it(`should return object for status code ${statusCode}`, async () => { + const mockHttpClient = new HttpClient(); + mockHttpClient.executeFetch = async (url: string, requestInit: RequestInit, requestOptions?: Record) => { + const response = new Response(enumResponse, { + status: statusCode, + } as ResponseInit); + response.headers.set("Content-Type", "text/plain"); + return Promise.resolve(response); + }; + const mockFactory = new TextParseNodeFactory(); + const requestAdapter = new FetchRequestAdapter(new AnonymousAuthenticationProvider(), mockFactory, undefined, mockHttpClient); + const requestInformation = new RequestInformation(); + requestInformation.URL = "https://www.example.com"; + requestInformation.httpMethod = HttpMethod.GET; + const result: TestEnum | undefined = await requestAdapter.sendEnum(requestInformation, TestEnumObject, undefined); + assert.isDefined(result); + assert.equal(result, enumResponse); + }); + } + }); + describe("send and deserialize collection of enum", () => { + for (const statusCode of [200, 201, 202, 203]) { + const enumResponse = `["a","b","c","e","f"]`; + it(`should return object for status code ${statusCode}`, async () => { + const mockHttpClient = new HttpClient(); + mockHttpClient.executeFetch = async (url: string, requestInit: RequestInit, requestOptions?: Record) => { + const response = new Response(enumResponse, { + status: statusCode, + } as ResponseInit); + response.headers.set("Content-Type", "application/json"); + return Promise.resolve(response); + }; + const mockFactory = new JsonParseNodeFactory(); + const requestAdapter = new FetchRequestAdapter(new AnonymousAuthenticationProvider(), mockFactory, undefined, mockHttpClient); + const requestInformation = new RequestInformation(); + requestInformation.URL = "https://www.example.com"; + requestInformation.httpMethod = HttpMethod.GET; + const result: TestEnum[] | undefined = await requestAdapter.sendCollectionOfEnum(requestInformation, TestEnumObject, undefined); + assert.isDefined(result); + assert.equal(result?.length, 3); + assert.equal(result![0], "a"); + assert.equal(result![1], "b"); + assert.equal(result![2], "c"); + }); + } + }); describe("Throws API error", () => { it("should throw API error", async () => { const mockHttpClient = new HttpClient();