Move response body parsing to serializationPolicy

This commit is contained in:
Rikki Gibson 2018-06-22 13:47:33 -07:00
Родитель f98bfdc3b3
Коммит 547521027d
4 изменённых файлов: 143 добавлений и 114 удалений

Просмотреть файл

@ -4,7 +4,6 @@
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
import * as FormData from "form-data";
import * as tough from "isomorphic-tough-cookie";
import * as xml2js from "isomorphic-xml2js";
import { HttpClient } from "./httpClient";
import { HttpHeaders } from "./httpHeaders";
import { HttpOperationResponse } from "./httpOperationResponse";
@ -149,8 +148,10 @@ export class AxiosHttpClient implements HttpClient {
request: httpRequest,
status: res.status,
headers,
readableStreamBody: httpRequest.rawResponse && isNode ? res.data as any : undefined,
blobBody: !httpRequest.rawResponse || isNode ? undefined : () => res.data
blobBody: !httpRequest.rawResponse || isNode ? undefined : () => res.data,
bodyAsText: httpRequest.rawResponse ? undefined : res.data
};
if (this.cookieJar) {
@ -168,50 +169,6 @@ export class AxiosHttpClient implements HttpClient {
}
}
if (!httpRequest.rawResponse) {
try {
operationResponse.bodyAsText = res.data;
} catch (err) {
const msg = `Error "${err}" occured while converting the raw response body into string.`;
const errCode = err.code || "RAWTEXT_CONVERSION_ERROR";
const e = new RestError(msg, errCode, res.status, httpRequest, operationResponse, res.data);
return Promise.reject(e);
}
try {
if (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 xmlParser = new xml2js.Parser(XML2JS_PARSER_OPTS);
const parseString = new Promise(function (resolve: (result: any) => void, reject: (err: any) => void) {
xmlParser.parseString(operationResponse.bodyAsText!, function (err: any, result: any) {
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
operationResponse.parsedBody = await parseString;
} else if (contentComponents.some(component => component === "application/json" || component === "text/json") || !contentType) {
operationResponse.parsedBody = JSON.parse(operationResponse.bodyAsText);
}
}
} catch (err) {
const msg = `Error "${err}" occured while executing JSON.parse on the response body - ${operationResponse.bodyAsText}.`;
const errCode = err.code || "JSON_PARSE_ERROR";
const e = new RestError(msg, errCode, res.status, httpRequest, operationResponse, operationResponse.bodyAsText);
return Promise.reject(e);
}
}
return Promise.resolve(operationResponse);
}
}
const XML2JS_PARSER_OPTS: xml2js.OptionsV2 = {
explicitArray: false,
explicitCharkey: false,
explicitRoot: false
};

Просмотреть файл

@ -10,6 +10,7 @@ import { Mapper, MapperType } from "../serializer";
import * as utils from "../util/utils";
import { WebResource } from "../webResource";
import { BaseRequestPolicy, RequestPolicy, RequestPolicyCreator, RequestPolicyOptions } from "./requestPolicy";
import * as xml2js from "isomorphic-xml2js";
/**
* Create a new serialization RequestPolicyCreator that will serialized HTTP request bodies as they
@ -30,50 +31,45 @@ export class SerializationPolicy extends BaseRequestPolicy {
}
public async sendRequest(request: WebResource): Promise<HttpOperationResponse> {
let result: Promise<HttpOperationResponse>;
serializeRequestBody(request);
return this._nextPolicy.sendRequest(request).then(operationResponse => deserializeResponseBody(operationResponse));
}
}
/**
* Serialize the provided HTTP request's body based on the requestBodyMapper assigned to the HTTP
* request.
* @param {WebResource} request - The HTTP request that will have its body serialized.
*/
function serializeRequestBody(request: WebResource): void {
const operationSpec: OperationSpec | undefined = request.operationSpec;
if (operationSpec && operationSpec.requestBody && operationSpec.requestBody.mapper) {
const bodyMapper = operationSpec.requestBody.mapper;
const { required, xmlName, xmlElementName, serializedName } = bodyMapper;
const typeName = bodyMapper.type.name;
try {
this.serializeRequestBody(request);
const operationResponse: HttpOperationResponse = await this._nextPolicy.sendRequest(request);
result = this.deserializeResponseBody(operationResponse);
} catch (error) {
result = Promise.reject(error);
}
return result;
}
/**
* Serialize the provided HTTP request's body based on the requestBodyMapper assigned to the HTTP
* request.
* @param {WebResource} request - The HTTP request that will have its body serialized.
*/
public serializeRequestBody(request: WebResource): void {
const operationSpec: OperationSpec | undefined = request.operationSpec;
if (operationSpec && operationSpec.requestBody && operationSpec.requestBody.mapper) {
const bodyMapper = operationSpec.requestBody.mapper;
const { required, xmlName, xmlElementName, serializedName } = bodyMapper;
const typeName = bodyMapper.type.name;
try {
if (request.body != undefined || required) {
const requestBodyParameterPathString: string = getPathStringFromParameter(operationSpec.requestBody);
request.body = operationSpec.serializer.serialize(bodyMapper, request.body, requestBodyParameterPathString);
if (operationSpec.isXML) {
if (typeName === MapperType.Sequence) {
request.body = utils.stringifyXML(utils.prepareXMLRootList(request.body, xmlElementName || xmlName || serializedName), { rootName: xmlName || serializedName });
}
else {
request.body = utils.stringifyXML(request.body, { rootName: xmlName || serializedName });
}
} else if (typeName !== MapperType.Stream) {
request.body = JSON.stringify(request.body);
if (request.body != undefined || required) {
const requestBodyParameterPathString: string = getPathStringFromParameter(operationSpec.requestBody);
request.body = operationSpec.serializer.serialize(bodyMapper, request.body, requestBodyParameterPathString);
if (operationSpec.isXML) {
if (typeName === MapperType.Sequence) {
request.body = utils.stringifyXML(utils.prepareXMLRootList(request.body, xmlElementName || xmlName || serializedName), { rootName: xmlName || serializedName });
}
else {
request.body = utils.stringifyXML(request.body, { rootName: xmlName || serializedName });
}
} else if (typeName !== MapperType.Stream) {
request.body = JSON.stringify(request.body);
}
} catch (error) {
throw new Error(`Error "${error.message}" occurred in serializing the payload - ${JSON.stringify(serializedName, undefined, " ")}.`);
}
} catch (error) {
throw new Error(`Error "${error.message}" occurred in serializing the payload - ${JSON.stringify(serializedName, undefined, " ")}.`);
}
}
}
public deserializeResponseBody(response: HttpOperationResponse): Promise<HttpOperationResponse> {
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;
@ -143,7 +139,7 @@ export class SerializationPolicy extends BaseRequestPolicy {
}
}
return Promise.resolve(response);
}
});
}
function isStreamOperation(responseSpecs: { [statusCode: string]: OperationResponse }): boolean {
@ -156,4 +152,43 @@ function isStreamOperation(responseSpecs: { [statusCode: string]: OperationRespo
}
}
return result;
}
}
function parse(operationResponse: HttpOperationResponse): Promise<HttpOperationResponse> {
const errorHandler = (err: any) => {
const msg = `Error "${err}" occurred while parsing the response body - ${operationResponse.bodyAsText}.`;
const errCode = err.code || "PARSE_ERROR";
const e = new RestError(msg, errCode, operationResponse.status, operationResponse.request, operationResponse, operationResponse.bodyAsText);
return Promise.reject(e);
};
if (!operationResponse.request.rawResponse && 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 xmlParser = new xml2js.Parser({
explicitArray: false,
explicitCharkey: false,
explicitRoot: false
});
return new Promise<HttpOperationResponse>(function (resolve, reject) {
xmlParser.parseString(text, function (err: any, result: any) {
if (err) {
reject(err);
} else {
operationResponse.parsedBody = result;
resolve(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);
}
}
return Promise.resolve(operationResponse);
}

Просмотреть файл

@ -177,32 +177,4 @@ describe("axiosHttpClient", () => {
assert(uploadNotified);
assert(downloadNotified);
});
it("should parse a JSON response body", async function() {
const request = new WebResource(`${baseURL}/json`);
const client = new AxiosHttpClient();
const response = await client.sendRequest(request);
assert.deepStrictEqual(response.parsedBody, [123,456,789]);
});
it("should parse a JSON response body with a charset specified in Content-Type", async function() {
const request = new WebResource(`${baseURL}/json-charset`);
const client = new AxiosHttpClient();
const response = await client.sendRequest(request);
assert.deepStrictEqual(response.parsedBody, [123,456,789]);
});
it("should parse a JSON response body with an uppercase Content-Type", async function() {
const request = new WebResource(`${baseURL}/json-uppercase-content-type`);
const client = new AxiosHttpClient();
const response = await client.sendRequest(request);
assert.deepStrictEqual(response.parsedBody, [123,456,789]);
});
it("should parse a JSON response body with a missing Content-Type", async function() {
const request = new WebResource(`${baseURL}/json-no-content-type`);
const client = new AxiosHttpClient();
const response = await client.sendRequest(request);
assert.deepStrictEqual(response.parsedBody, [123,456,789]);
});
});

Просмотреть файл

@ -5,8 +5,9 @@ import * as assert from "assert";
import { HttpHeaders } from "../../../lib/httpHeaders";
import { HttpOperationResponse } from "../../../lib/httpOperationResponse";
import { RequestPolicy, RequestPolicyOptions } from "../../../lib/policies/requestPolicy";
import { SerializationPolicy } from "../../../lib/policies/serializationPolicy";
import { SerializationPolicy, serializationPolicy } from "../../../lib/policies/serializationPolicy";
import { WebResource } from "../../../lib/webResource";
import { HttpClient } from '../../../lib/msRest';
describe("serializationPolicy", () => {
const mockPolicy: RequestPolicy = {
@ -28,4 +29,68 @@ describe("serializationPolicy", () => {
await serializationPolicy.sendRequest(request);
assert.strictEqual(request.body, "hello there!");
});
it("should parse a JSON response body", async function() {
const request = new WebResource();
const mockClient: HttpClient = {
sendRequest: req => Promise.resolve({
request: req,
status: 200,
headers: new HttpHeaders({ "Content-Type": "application/json" }),
bodyAsText: "[123, 456, 789]"
})
};
const policy = serializationPolicy()(mockClient, new RequestPolicyOptions());
const response = await policy.sendRequest(request);
assert.deepStrictEqual(response.parsedBody, [123,456,789]);
});
it("should parse a JSON response body with a charset specified in Content-Type", async function() {
const request = new WebResource();
const mockClient: HttpClient = {
sendRequest: req => Promise.resolve({
request: req,
status: 200,
headers: new HttpHeaders({ "Content-Type": "application/json;charset=UTF-8" }),
bodyAsText: "[123, 456, 789]"
})
};
const policy = serializationPolicy()(mockClient, new RequestPolicyOptions());
const response = await policy.sendRequest(request);
assert.deepStrictEqual(response.parsedBody, [123,456,789]);
});
it("should parse a JSON response body with an uppercase Content-Type", async function() {
const request = new WebResource();
const mockClient: HttpClient = {
sendRequest: req => Promise.resolve({
request: req,
status: 200,
headers: new HttpHeaders({ "Content-Type": "APPLICATION/JSON" }),
bodyAsText: "[123, 456, 789]"
})
};
const policy = serializationPolicy()(mockClient, new RequestPolicyOptions());
const response = await policy.sendRequest(request);
assert.deepStrictEqual(response.parsedBody, [123,456,789]);
});
it("should parse a JSON response body with a missing Content-Type", async function() {
const request = new WebResource();
const mockClient: HttpClient = {
sendRequest: req => Promise.resolve({
request: req,
status: 200,
headers: new HttpHeaders(),
bodyAsText: "[123, 456, 789]"
})
};
const policy = serializationPolicy()(mockClient, new RequestPolicyOptions());
const response = await policy.sendRequest(request);
assert.deepStrictEqual(response.parsedBody, [123,456,789]);
});
});