Skip to content

Commit

Permalink
[core-client] Switch _response property for callback. (#13132)
Browse files Browse the repository at this point in the history
* Switch _response property for callback.
  • Loading branch information
xirzec authored Jan 12, 2021
1 parent 1defe37 commit be134f5
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 53 deletions.
13 changes: 5 additions & 8 deletions sdk/core/core-client/review/core-client.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export interface OperationArguments {
// @public
export interface OperationOptions {
abortSignal?: AbortSignalLike;
onResponse?: RawResponseCallback;
requestOptions?: OperationRequestOptions;
serializerOptions?: SerializerOptions;
tracingOptions?: OperationTracingOptions;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -347,7 +344,7 @@ export interface SerializerOptions {
// @public
export class ServiceClient {
constructor(options?: ServiceClientOptions);
sendOperationRequest(operationArguments: OperationArguments, operationSpec: OperationSpec): Promise<OperationResponse>;
sendOperationRequest<T>(operationArguments: OperationArguments, operationSpec: OperationSpec): Promise<T>;
sendRequest(request: PipelineRequest): Promise<PipelineResponse>;
}

Expand Down
4 changes: 2 additions & 2 deletions sdk/core/core-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ export {
OperationRequestInfo,
QueryCollectionFormat,
ParameterPath,
OperationResponse,
FullOperationResponse,
PolymorphicDiscriminator,
SpanConfig,
XML_ATTRKEY,
XML_CHARKEY,
XmlOptions,
SerializerOptions
SerializerOptions,
RawResponseCallback
} from "./interfaces";
export {
deserializationPolicy,
Expand Down
24 changes: 14 additions & 10 deletions sdk/core/core-client/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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.
Expand Down
46 changes: 21 additions & 25 deletions sdk/core/core-client/src/serviceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
InternalPipelineOptions
} from "@azure/core-https";
import {
OperationResponse,
OperationArguments,
OperationSpec,
OperationRequest,
Expand Down Expand Up @@ -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<T>(
operationArguments: OperationArguments,
operationSpec: OperationSpec
): Promise<OperationResponse> {
): Promise<T> {
const baseUri: string | undefined = operationSpec.baseUrl || this._baseUri;
if (!baseUri) {
throw new Error(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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<T extends object>(
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 =
Expand All @@ -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
});
};
}
}

Expand All @@ -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 {
Expand Down
32 changes: 24 additions & 8 deletions sdk/core/core-client/test/serviceClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
Mapper,
CompositeMapper,
OperationSpec,
serializationPolicy
serializationPolicy,
FullOperationResponse
} from "../src";
import {
createHttpHeaders,
Expand Down Expand Up @@ -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",
Expand All @@ -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() {
Expand Down Expand Up @@ -336,8 +345,15 @@ describe("ServiceClient", function() {
pipeline
});

const res = await client1.sendOperationRequest(
{},
let rawResponse: FullOperationResponse | undefined;
const res = await client1.sendOperationRequest<Array<number>>(
{
options: {
onResponse: (response) => {
rawResponse = response;
}
}
},
{
serializer: createSerializer(),
httpMethod: "GET",
Expand All @@ -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]);
});

Expand Down

0 comments on commit be134f5

Please sign in to comment.