From 547521027dea4c45c2d9ade5c01508d75a098849 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Fri, 22 Jun 2018 13:47:33 -0700 Subject: [PATCH] Move response body parsing to serializationPolicy --- lib/axiosHttpClient.ts | 49 +------- lib/policies/serializationPolicy.ts | 113 ++++++++++++------ test/shared/axiosHttpClientTests.ts | 28 ----- .../policies/serializationPolicyTests.ts | 67 ++++++++++- 4 files changed, 143 insertions(+), 114 deletions(-) diff --git a/lib/axiosHttpClient.ts b/lib/axiosHttpClient.ts index b8034c0..4dbeef5 100644 --- a/lib/axiosHttpClient.ts +++ b/lib/axiosHttpClient.ts @@ -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 -}; \ No newline at end of file diff --git a/lib/policies/serializationPolicy.ts b/lib/policies/serializationPolicy.ts index 357fb3d..bf1592f 100644 --- a/lib/policies/serializationPolicy.ts +++ b/lib/policies/serializationPolicy.ts @@ -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 { - let result: Promise; + 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 { +function deserializeResponseBody(response: HttpOperationResponse): Promise { + 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; -} \ No newline at end of file +} + +function parse(operationResponse: HttpOperationResponse): Promise { + 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(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(resolve => { + operationResponse.parsedBody = JSON.parse(text); + resolve(operationResponse); + }).catch(errorHandler); + } + } + + return Promise.resolve(operationResponse); +} diff --git a/test/shared/axiosHttpClientTests.ts b/test/shared/axiosHttpClientTests.ts index dc5466e..eda46d7 100644 --- a/test/shared/axiosHttpClientTests.ts +++ b/test/shared/axiosHttpClientTests.ts @@ -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]); - }); }); diff --git a/test/shared/policies/serializationPolicyTests.ts b/test/shared/policies/serializationPolicyTests.ts index 8c48049..5d5d3e3 100644 --- a/test/shared/policies/serializationPolicyTests.ts +++ b/test/shared/policies/serializationPolicyTests.ts @@ -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]); + }); }); \ No newline at end of file