From be134f51c8c5ee8e82cfcf84e5d1e2c8ab2a913b Mon Sep 17 00:00:00 2001 From: Jeff Fisher Date: Tue, 12 Jan 2021 15:26:49 -0800 Subject: [PATCH] [core-client] Switch _response property for callback. (#13132) * Switch _response property for callback. --- .../core-client/review/core-client.api.md | 13 ++---- sdk/core/core-client/src/index.ts | 4 +- sdk/core/core-client/src/interfaces.ts | 24 ++++++---- sdk/core/core-client/src/serviceClient.ts | 46 +++++++++---------- .../core-client/test/serviceClient.spec.ts | 32 +++++++++---- 5 files changed, 66 insertions(+), 53 deletions(-) diff --git a/sdk/core/core-client/review/core-client.api.md b/sdk/core/core-client/review/core-client.api.md index 0d1e4ea21cb2..c9d42042999c 100644 --- a/sdk/core/core-client/review/core-client.api.md +++ b/sdk/core/core-client/review/core-client.api.md @@ -202,6 +202,7 @@ export interface OperationArguments { // @public export interface OperationOptions { abortSignal?: AbortSignalLike; + onResponse?: RawResponseCallback; requestOptions?: OperationRequestOptions; serializerOptions?: SerializerOptions; tracingOptions?: OperationTracingOptions; @@ -241,13 +242,6 @@ export interface OperationRequestOptions { timeout?: number; } -// @public -export interface OperationResponse { - // (undocumented) - [key: string]: any; - _response: FullOperationResponse; -} - // @public export interface OperationResponseMap { bodyMapper?: Mapper; @@ -297,6 +291,9 @@ export interface PolymorphicDiscriminator { // @public export type QueryCollectionFormat = "CSV" | "SSV" | "TSV" | "Pipes" | "Multi"; +// @public +export type RawResponseCallback = (rawResponse: FullOperationResponse, flatResponse: unknown) => void; + // @public (undocumented) export interface SequenceMapper extends BaseMapper { // (undocumented) @@ -347,7 +344,7 @@ export interface SerializerOptions { // @public export class ServiceClient { constructor(options?: ServiceClientOptions); - sendOperationRequest(operationArguments: OperationArguments, operationSpec: OperationSpec): Promise; + sendOperationRequest(operationArguments: OperationArguments, operationSpec: OperationSpec): Promise; sendRequest(request: PipelineRequest): Promise; } diff --git a/sdk/core/core-client/src/index.ts b/sdk/core/core-client/src/index.ts index 9c76b295bd2b..c7f7b9d808c9 100644 --- a/sdk/core/core-client/src/index.ts +++ b/sdk/core/core-client/src/index.ts @@ -36,14 +36,14 @@ export { OperationRequestInfo, QueryCollectionFormat, ParameterPath, - OperationResponse, FullOperationResponse, PolymorphicDiscriminator, SpanConfig, XML_ATTRKEY, XML_CHARKEY, XmlOptions, - SerializerOptions + SerializerOptions, + RawResponseCallback } from "./interfaces"; export { deserializationPolicy, diff --git a/sdk/core/core-client/src/interfaces.ts b/sdk/core/core-client/src/interfaces.ts index 135ae8b5d33f..1a3dd02c8ad6 100644 --- a/sdk/core/core-client/src/interfaces.ts +++ b/sdk/core/core-client/src/interfaces.ts @@ -105,6 +105,13 @@ export interface OperationOptions { * Options to override serialization/de-serialization behavior. */ serializerOptions?: SerializerOptions; + + /** + * A function to be called each time a response is received from the server + * while performing the requested operation. + * May be called multiple times. + */ + onResponse?: RawResponseCallback; } /** @@ -333,17 +340,14 @@ export interface FullOperationResponse extends PipelineResponse { } /** - * The processed and flattened response to an operation call. - * Contains merged properties of the parsed body and headers. + * A function to be called each time a response is received from the server + * while performing the requested operation. + * May be called multiple times. */ -export interface OperationResponse { - /** - * The underlying HTTP response containing both raw and deserialized response data. - */ - _response: FullOperationResponse; - - [key: string]: any; -} +export type RawResponseCallback = ( + rawResponse: FullOperationResponse, + flatResponse: unknown +) => void; /** * Used to map raw response objects to final shapes. diff --git a/sdk/core/core-client/src/serviceClient.ts b/sdk/core/core-client/src/serviceClient.ts index 8b228e133276..8ba9a45fa87f 100644 --- a/sdk/core/core-client/src/serviceClient.ts +++ b/sdk/core/core-client/src/serviceClient.ts @@ -13,7 +13,6 @@ import { InternalPipelineOptions } from "@azure/core-https"; import { - OperationResponse, OperationArguments, OperationSpec, OperationRequest, @@ -125,13 +124,14 @@ export class ServiceClient { /** * Send an HTTP request that is populated using the provided OperationSpec. + * @typeParam T The typed result of the request, based on the OperationSpec. * @param {OperationArguments} operationArguments The arguments that the HTTP request's templated values will be populated from. * @param {OperationSpec} operationSpec The OperationSpec to use to populate the httpRequest. */ - async sendOperationRequest( + async sendOperationRequest( operationArguments: OperationArguments, operationSpec: OperationSpec - ): Promise { + ): Promise { const baseUri: string | undefined = operationSpec.baseUrl || this._baseUri; if (!baseUri) { throw new Error( @@ -200,7 +200,14 @@ export class ServiceClient { try { const rawResponse = await this.sendRequest(request); - return flattenResponse(rawResponse, operationSpec.responses[rawResponse.status]); + const flatResponse = flattenResponse( + rawResponse, + operationSpec.responses[rawResponse.status] + ) as T; + if (options?.onResponse) { + options.onResponse(rawResponse, flatResponse); + } + return flatResponse; } catch (error) { if (error.response) { error.details = flattenResponse( @@ -285,29 +292,18 @@ export function createClientPipeline(options: ClientPipelineOptions = {}): Pipel function flattenResponse( fullResponse: FullOperationResponse, responseSpec: OperationResponseMap | undefined -): OperationResponse { +): unknown { const parsedHeaders = fullResponse.parsedHeaders; const bodyMapper = responseSpec && responseSpec.bodyMapper; - function addResponse( - obj: T - ): T & { readonly _response: FullOperationResponse } { - return Object.defineProperty(obj, "_response", { - configurable: false, - enumerable: false, - writable: false, - value: fullResponse - }); - } - if (bodyMapper) { const typeName = bodyMapper.type.name; if (typeName === "Stream") { - return addResponse({ + return { ...parsedHeaders, blobBody: fullResponse.blobBody, readableStreamBody: fullResponse.readableStreamBody - }); + }; } const modelProperties = @@ -330,14 +326,14 @@ function flattenResponse( arrayResponse[key] = parsedHeaders[key]; } } - return addResponse(arrayResponse); + return arrayResponse; } if (typeName === "Composite" || typeName === "Dictionary") { - return addResponse({ + return { ...parsedHeaders, ...fullResponse.parsedBody - }); + }; } } @@ -346,16 +342,16 @@ function flattenResponse( fullResponse.request.method === "HEAD" || isPrimitiveType(fullResponse.parsedBody) ) { - return addResponse({ + return { ...parsedHeaders, body: fullResponse.parsedBody - }); + }; } - return addResponse({ + return { ...parsedHeaders, ...fullResponse.parsedBody - }); + }; } function getCredentialScopes(options: ServiceClientOptions): string | string[] | undefined { diff --git a/sdk/core/core-client/test/serviceClient.spec.ts b/sdk/core/core-client/test/serviceClient.spec.ts index 3fe1fd6f87a9..9f1e3c9f146c 100644 --- a/sdk/core/core-client/test/serviceClient.spec.ts +++ b/sdk/core/core-client/test/serviceClient.spec.ts @@ -14,7 +14,8 @@ import { Mapper, CompositeMapper, OperationSpec, - serializationPolicy + serializationPolicy, + FullOperationResponse } from "../src"; import { createHttpHeaders, @@ -249,20 +250,26 @@ describe("ServiceClient", function() { assert.deepEqual(request!.headers.toJSON(), expected); }); - it("responses should not show the _response property when serializing", async function() { + it("should call rawResponseCallback with the full response", async function() { let request: OperationRequest; const client = new ServiceClient({ httpsClient: { sendRequest: (req) => { request = req; - return Promise.resolve({ request, status: 200, headers: createHttpHeaders() }); + return Promise.resolve({ + request, + status: 200, + headers: createHttpHeaders({ "X-Extra-Info": "foo" }) + }); } }, pipeline: createEmptyPipeline() }); + let rawResponse: FullOperationResponse | undefined; + const response = await client.sendOperationRequest( - {}, + { options: { onResponse: (response) => (rawResponse = response) } }, { httpMethod: "GET", baseUrl: "https://example.com", @@ -275,8 +282,10 @@ describe("ServiceClient", function() { ); assert(request!); - // _response should be not enumerable assert.strictEqual(JSON.stringify(response), "{}"); + assert.strictEqual(rawResponse?.status, 200); + assert.strictEqual(rawResponse?.request, request!); + assert.strictEqual(rawResponse?.headers.get("X-Extra-Info"), "foo"); }); it("should serialize collection:csv query parameters", async function() { @@ -336,8 +345,15 @@ describe("ServiceClient", function() { pipeline }); - const res = await client1.sendOperationRequest( - {}, + let rawResponse: FullOperationResponse | undefined; + const res = await client1.sendOperationRequest>( + { + options: { + onResponse: (response) => { + rawResponse = response; + } + } + }, { serializer: createSerializer(), httpMethod: "GET", @@ -359,7 +375,7 @@ describe("ServiceClient", function() { } ); - assert.strictEqual(res._response.status, 200); + assert.strictEqual(rawResponse?.status, 200); assert.deepStrictEqual(res.slice(), [1, 2, 3]); });