Skip to content

Commit

Permalink
Merge pull request #201 from Azure/daschult/skipdeserialization
Browse files Browse the repository at this point in the history
Changes needed for LRO support
  • Loading branch information
Dan Schulte authored Aug 24, 2018
2 parents 533ab2c + fb739be commit 9220249
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 124 deletions.
6 changes: 3 additions & 3 deletions lib/axiosHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export class AxiosHttpClient implements HttpClient {
validateStatus: () => true,
// Workaround for https://github.com/axios/axios/issues/1362
maxContentLength: 1024 * 1024 * 1024 * 10,
responseType: httpRequest.rawResponse ? "stream" : "text",
responseType: httpRequest.streamResponseBody ? "stream" : "text",
cancelToken,
timeout: httpRequest.timeout
};
Expand Down Expand Up @@ -174,8 +174,8 @@ export class AxiosHttpClient implements HttpClient {
request: httpRequest,
status: res.status,
headers,
readableStreamBody: httpRequest.rawResponse ? responseBody as Readable : undefined,
bodyAsText: httpRequest.rawResponse ? undefined : responseBody as string
readableStreamBody: httpRequest.streamResponseBody ? responseBody as Readable : undefined,
bodyAsText: httpRequest.streamResponseBody ? undefined : responseBody as string
};

if (this.cookieJar) {
Expand Down
5 changes: 3 additions & 2 deletions lib/msRest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ export { HttpOperationResponse, HttpResponse } from "./httpOperationResponse";
export { HttpPipelineLogger } from "./httpPipelineLogger";
export { HttpPipelineLogLevel } from "./httpPipelineLogLevel";
export { RestError } from "./restError";
export { OperationSpec } from "./operationSpec";
export { OperationArguments } from "./operationArguments";
export { OperationParameter, OperationQueryParameter, OperationURLParameter } from "./operationParameter";
export { OperationResponse } from "./operationResponse";
export { OperationSpec } from "./operationSpec";
export { ServiceClient, ServiceClientOptions } from "./serviceClient";
export { QueryCollectionFormat } from "./queryCollectionFormat";
export { Constants } from "./util/constants";
Expand All @@ -22,7 +23,7 @@ export { systemErrorRetryPolicy } from "./policies/systemErrorRetryPolicy";
export { redirectPolicy } from "./policies/redirectPolicy";
export { signingPolicy } from "./policies/signingPolicy";
export { msRestUserAgentPolicy } from "./policies/msRestUserAgentPolicy";
export { deserializationPolicy } from "./policies/deserializationPolicy";
export { deserializationPolicy, deserializeResponseBody } from "./policies/deserializationPolicy";
export {
MapperType, SimpleMapperType, CompositeMapperType, DictionaryMapperType, SequenceMapperType, EnumMapperType,
Mapper, BaseMapper, CompositeMapper, SequenceMapper, DictionaryMapper, EnumMapper,
Expand Down
14 changes: 13 additions & 1 deletion lib/operationSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { OperationParameter, OperationQueryParameter, OperationURLParameter } from "./operationParameter";
import { OperationResponse } from "./operationResponse";
import { Serializer } from "./serializer";
import { MapperType, Serializer } from "./serializer";
import { HttpMethods } from "./webResource";

/**
Expand Down Expand Up @@ -76,4 +76,16 @@ export interface OperationSpec {
* returned.
*/
readonly responses: { [responseCode: string]: OperationResponse };
}

export function isStreamOperation(operationSpec: OperationSpec): boolean {
let result = false;
for (const statusCode in operationSpec.responses) {
const operationResponse: OperationResponse = operationSpec.responses[statusCode];
if (operationResponse.bodyMapper && operationResponse.bodyMapper.type.name === MapperType.Stream) {
result = true;
break;
}
}
return result;
}
215 changes: 117 additions & 98 deletions lib/policies/deserializationPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { HttpOperationResponse } from "../httpOperationResponse";
import { OperationResponse } from "../operationResponse";
import { OperationSpec } from "../operationSpec";
import { OperationSpec, isStreamOperation } from "../operationSpec";
import { RestError } from "../restError";
import { Mapper, MapperType } from "../serializer";
import * as utils from "../util/utils";
Expand Down Expand Up @@ -37,109 +37,128 @@ export class DeserializationPolicy extends BaseRequestPolicy {
}
}

function deserializeResponseBody(response: HttpOperationResponse): Promise<HttpOperationResponse> {
return parse(response).then(response => {
const operationSpec: OperationSpec | undefined = response.request.operationSpec;
if (operationSpec && operationSpec.responses) {
const statusCode: number = response.status;

const expectedStatusCodes: string[] = Object.keys(operationSpec.responses);

const hasNoExpectedStatusCodes: boolean = (expectedStatusCodes.length === 0 || (expectedStatusCodes.length === 1 && expectedStatusCodes[0] === "default"));

const responseSpec: OperationResponse = operationSpec.responses[statusCode];

const isExpectedStatusCode: boolean = hasNoExpectedStatusCodes ? (200 <= statusCode && statusCode < 300) : !!responseSpec;
if (!isExpectedStatusCode) {
const defaultResponseSpec: OperationResponse = operationSpec.responses.default;
if (defaultResponseSpec) {
const initialErrorMessage: string = isStreamOperation(operationSpec.responses)
? `Unexpected status code: ${statusCode}`
: response.bodyAsText as string;

const error = new RestError(initialErrorMessage);
error.statusCode = statusCode;
error.request = utils.stripRequest(response.request);
error.response = utils.stripResponse(response);

let parsedErrorResponse: { [key: string]: any } = response.parsedBody;
try {
if (parsedErrorResponse) {
const defaultResponseBodyMapper: Mapper | undefined = defaultResponseSpec.bodyMapper;
if (defaultResponseBodyMapper && defaultResponseBodyMapper.serializedName === "CloudError") {
if (parsedErrorResponse.error) {
parsedErrorResponse = parsedErrorResponse.error;
}
if (parsedErrorResponse.code) {
error.code = parsedErrorResponse.code;
}
if (parsedErrorResponse.message) {
error.message = parsedErrorResponse.message;
}
} else {
let internalError: any = parsedErrorResponse;
if (parsedErrorResponse.error) {
internalError = parsedErrorResponse.error;
}
function getOperationResponse(parsedResponse: HttpOperationResponse): undefined | OperationResponse {
let result: OperationResponse | undefined;
const request: WebResource = parsedResponse.request;
const operationSpec: OperationSpec | undefined = request.operationSpec;
if (operationSpec) {
const operationResponseGetter: undefined | ((operationSpec: OperationSpec, response: HttpOperationResponse) => (undefined | OperationResponse)) = request.operationResponseGetter;
if (!operationResponseGetter) {
result = operationSpec.responses[parsedResponse.status];
} else {
result = operationResponseGetter(operationSpec, parsedResponse);
}
}
return result;
}

error.code = internalError.code;
if (internalError.message) {
error.message = internalError.message;
function shouldDeserializeResponse(parsedResponse: HttpOperationResponse): boolean {
const shouldDeserialize: undefined | boolean | ((response: HttpOperationResponse) => boolean) = parsedResponse.request.shouldDeserialize;
let result: boolean;
if (shouldDeserialize === undefined) {
result = true;
} else if (typeof shouldDeserialize === "boolean") {
result = shouldDeserialize;
} else {
result = shouldDeserialize(parsedResponse);
}
return result;
}

export function deserializeResponseBody(response: HttpOperationResponse): Promise<HttpOperationResponse> {
return parse(response).then(parsedResponse => {
const shouldDeserialize: boolean = shouldDeserializeResponse(parsedResponse);
if (shouldDeserialize) {
const operationSpec: OperationSpec | undefined = parsedResponse.request.operationSpec;
if (operationSpec && operationSpec.responses) {
const statusCode: number = parsedResponse.status;

const expectedStatusCodes: string[] = Object.keys(operationSpec.responses);

const hasNoExpectedStatusCodes: boolean = (expectedStatusCodes.length === 0 || (expectedStatusCodes.length === 1 && expectedStatusCodes[0] === "default"));

const responseSpec: OperationResponse | undefined = getOperationResponse(parsedResponse);

const isExpectedStatusCode: boolean = hasNoExpectedStatusCodes ? (200 <= statusCode && statusCode < 300) : !!responseSpec;
if (!isExpectedStatusCode) {
const defaultResponseSpec: OperationResponse = operationSpec.responses.default;
if (defaultResponseSpec) {
const initialErrorMessage: string = isStreamOperation(operationSpec)
? `Unexpected status code: ${statusCode}`
: parsedResponse.bodyAsText as string;

const error = new RestError(initialErrorMessage);
error.statusCode = statusCode;
error.request = utils.stripRequest(parsedResponse.request);
error.response = utils.stripResponse(parsedResponse);

let parsedErrorResponse: { [key: string]: any } = parsedResponse.parsedBody;
try {
if (parsedErrorResponse) {
const defaultResponseBodyMapper: Mapper | undefined = defaultResponseSpec.bodyMapper;
if (defaultResponseBodyMapper && defaultResponseBodyMapper.serializedName === "CloudError") {
if (parsedErrorResponse.error) {
parsedErrorResponse = parsedErrorResponse.error;
}
if (parsedErrorResponse.code) {
error.code = parsedErrorResponse.code;
}
if (parsedErrorResponse.message) {
error.message = parsedErrorResponse.message;
}
} else {
let internalError: any = parsedErrorResponse;
if (parsedErrorResponse.error) {
internalError = parsedErrorResponse.error;
}

error.code = internalError.code;
if (internalError.message) {
error.message = internalError.message;
}
}
}

if (defaultResponseBodyMapper) {
let valueToDeserialize: any = parsedErrorResponse;
if (operationSpec.isXML && defaultResponseBodyMapper.type.name === MapperType.Sequence) {
valueToDeserialize = typeof parsedErrorResponse === "object"
? parsedErrorResponse[defaultResponseBodyMapper.xmlElementName!]
: [];
if (defaultResponseBodyMapper) {
let valueToDeserialize: any = parsedErrorResponse;
if (operationSpec.isXML && defaultResponseBodyMapper.type.name === MapperType.Sequence) {
valueToDeserialize = typeof parsedErrorResponse === "object"
? parsedErrorResponse[defaultResponseBodyMapper.xmlElementName!]
: [];
}
error.body = operationSpec.serializer.deserialize(defaultResponseBodyMapper, valueToDeserialize, "error.body");
}
error.body = operationSpec.serializer.deserialize(defaultResponseBodyMapper, valueToDeserialize, "error.body");
}
} catch (defaultError) {
error.message = `Error \"${defaultError.message}\" occurred in deserializing the responseBody - \"${parsedResponse.bodyAsText}\" for the default response.`;
}
} catch (defaultError) {
error.message = `Error \"${defaultError.message}\" occurred in deserializing the responseBody - \"${response.bodyAsText}\" for the default response.`;
}
return Promise.reject(error);
}
} else if (responseSpec) {
if (responseSpec.bodyMapper) {
let valueToDeserialize: any = response.parsedBody;
if (operationSpec.isXML && responseSpec.bodyMapper.type.name === MapperType.Sequence) {
valueToDeserialize = typeof valueToDeserialize === "object" ? valueToDeserialize[responseSpec.bodyMapper.xmlElementName!] : [];
return Promise.reject(error);
}
try {
response.parsedBody = operationSpec.serializer.deserialize(responseSpec.bodyMapper, valueToDeserialize, "operationRes.parsedBody");
} catch (error) {
const restError = new RestError(`Error ${error} occurred in deserializing the responseBody - ${response.bodyAsText}`);
restError.request = utils.stripRequest(response.request);
restError.response = utils.stripResponse(response);
return Promise.reject(restError);
} else if (responseSpec) {
if (responseSpec.bodyMapper) {
let valueToDeserialize: any = parsedResponse.parsedBody;
if (operationSpec.isXML && responseSpec.bodyMapper.type.name === MapperType.Sequence) {
valueToDeserialize = typeof valueToDeserialize === "object" ? valueToDeserialize[responseSpec.bodyMapper.xmlElementName!] : [];
}
try {
parsedResponse.parsedBody = operationSpec.serializer.deserialize(responseSpec.bodyMapper, valueToDeserialize, "operationRes.parsedBody");
} catch (error) {
const restError = new RestError(`Error ${error} occurred in deserializing the responseBody - ${parsedResponse.bodyAsText}`);
restError.request = utils.stripRequest(parsedResponse.request);
restError.response = utils.stripResponse(parsedResponse);
return Promise.reject(restError);
}
}
}

if (responseSpec.headersMapper) {
response.parsedHeaders = operationSpec.serializer.deserialize(responseSpec.headersMapper, response.headers.rawHeaders(), "operationRes.parsedHeaders");
if (responseSpec.headersMapper) {
parsedResponse.parsedHeaders = operationSpec.serializer.deserialize(responseSpec.headersMapper, parsedResponse.headers.rawHeaders(), "operationRes.parsedHeaders");
}
}
}
}
return Promise.resolve(response);
return Promise.resolve(parsedResponse);
});
}

function isStreamOperation(responseSpecs: { [statusCode: string]: OperationResponse }): boolean {
let result = false;
for (const statusCode in responseSpecs) {
const operationResponse: OperationResponse = responseSpecs[statusCode];
if (operationResponse.bodyMapper && operationResponse.bodyMapper.type.name === MapperType.Stream) {
result = true;
break;
}
}
return result;
}

function parse(operationResponse: HttpOperationResponse): Promise<HttpOperationResponse> {
const errorHandler = (err: any) => {
const msg = `Error "${err}" occurred while parsing the response body - ${operationResponse.bodyAsText}.`;
Expand All @@ -148,22 +167,22 @@ function parse(operationResponse: HttpOperationResponse): Promise<HttpOperationR
return Promise.reject(e);
};

if (!operationResponse.request.rawResponse && operationResponse.bodyAsText) {
if (!operationResponse.request.streamResponseBody && operationResponse.bodyAsText) {
const text = operationResponse.bodyAsText;
const contentType = operationResponse.headers.get("Content-Type") || "";
const contentComponents = contentType.split(";").map(component => component.toLowerCase());
if (contentComponents.some(component => component === "application/xml" || component === "text/xml")) {
const contentType: string = operationResponse.headers.get("Content-Type") || "";
const contentComponents: string[] = !contentType ? [] : contentType.split(";").map(component => component.toLowerCase());
if (contentComponents.length === 0 || contentComponents.some(component => component === "application/json" || component === "text/json")) {
return new Promise<HttpOperationResponse>(resolve => {
operationResponse.parsedBody = JSON.parse(text);
resolve(operationResponse);
}).catch(errorHandler);
} else if (contentComponents.some(component => component === "application/xml" || component === "text/xml")) {
return parseXML(text)
.then(body => {
operationResponse.parsedBody = body;
return operationResponse;
})
.catch(errorHandler);
} else if (contentComponents.some(component => component === "application/json" || component === "text/json") || !contentType) {
return new Promise<HttpOperationResponse>(resolve => {
operationResponse.parsedBody = JSON.parse(text);
resolve(operationResponse);
}).catch(errorHandler);
}
}

Expand Down
17 changes: 4 additions & 13 deletions lib/serviceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { HttpOperationResponse } from "./httpOperationResponse";
import { HttpPipelineLogger } from "./httpPipelineLogger";
import { OperationArguments } from "./operationArguments";
import { getPathStringFromParameter, getPathStringFromParameterPath, OperationParameter, ParameterPath } from "./operationParameter";
import { OperationResponse } from "./operationResponse";
import { OperationSpec } from "./operationSpec";
import { isStreamOperation, OperationSpec } from "./operationSpec";
import { deserializationPolicy } from "./policies/deserializationPolicy";
import { exponentialRetryPolicy } from "./policies/exponentialRetryPolicy";
import { generateClientRequestIdPolicy } from "./policies/generateClientRequestIdPolicy";
Expand All @@ -25,7 +24,7 @@ import { URLBuilder } from "./url";
import { Constants } from "./util/constants";
import * as utils from "./util/utils";
import { stringifyXML } from "./util/xml";
import { RequestPrepareOptions, WebResource, RequestOptionsBase } from "./webResource";
import { RequestOptionsBase, RequestPrepareOptions, WebResource } from "./webResource";

/**
* Options to be provided while creating the client.
Expand Down Expand Up @@ -291,16 +290,8 @@ export class ServiceClient {

serializeRequestBody(this, httpRequest, operationArguments, operationSpec);

if (operationSpec.responses) {
let rawResponse = false;
for (const responseStatusCode in operationSpec.responses) {
const responseSpec: OperationResponse = operationSpec.responses[responseStatusCode];
if (responseSpec.bodyMapper && responseSpec.bodyMapper.type.name === MapperType.Stream) {
rawResponse = true;
break;
}
}
httpRequest.rawResponse = rawResponse;
if (httpRequest.streamResponseBody == undefined) {
httpRequest.streamResponseBody = isStreamOperation(operationSpec);
}

result = this.sendRequest(httpRequest);
Expand Down
Loading

0 comments on commit 9220249

Please sign in to comment.