deserializationPolicy.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License. See License.txt in the project root for license information.
  3. import { HttpOperationResponse } from "../httpOperationResponse";
  4. import { OperationResponse } from "../operationResponse";
  5. import { OperationSpec, isStreamOperation } from "../operationSpec";
  6. import { RestError } from "../restError";
  7. import { Mapper, MapperType } from "../serializer";
  8. import * as utils from "../util/utils";
  9. import { parseXML } from "../util/xml";
  10. import { WebResource } from "../webResource";
  11. import { BaseRequestPolicy, RequestPolicy, RequestPolicyFactory, RequestPolicyOptions } from "./requestPolicy";
  12. /**
  13. * The content-types that will indicate that an operation response should be deserialized in a
  14. * particular way.
  15. */
  16. export interface DeserializationContentTypes {
  17. /**
  18. * The content-types that indicate that an operation response should be deserialized as JSON.
  19. * Defaults to [ "application/json", "text/json" ].
  20. */
  21. json?: string[];
  22. /**
  23. * The content-types that indicate that an operation response should be deserialized as XML.
  24. * Defaults to [ "application/xml", "application/atom+xml" ].
  25. */
  26. xml?: string[];
  27. }
  28. /**
  29. * Create a new serialization RequestPolicyCreator that will serialized HTTP request bodies as they
  30. * pass through the HTTP pipeline.
  31. */
  32. export function deserializationPolicy(deserializationContentTypes?: DeserializationContentTypes): RequestPolicyFactory {
  33. return {
  34. create: (nextPolicy: RequestPolicy, options: RequestPolicyOptions) => {
  35. return new DeserializationPolicy(nextPolicy, deserializationContentTypes, options);
  36. }
  37. };
  38. }
  39. export const defaultJsonContentTypes = ["application/json", "text/json"];
  40. export const defaultXmlContentTypes = ["application/xml", "application/atom+xml"];
  41. /**
  42. * A RequestPolicy that will deserialize HTTP response bodies and headers as they pass through the
  43. * HTTP pipeline.
  44. */
  45. export class DeserializationPolicy extends BaseRequestPolicy {
  46. public readonly jsonContentTypes: string[];
  47. public readonly xmlContentTypes: string[];
  48. constructor(nextPolicy: RequestPolicy, deserializationContentTypes: DeserializationContentTypes | undefined, options: RequestPolicyOptions) {
  49. super(nextPolicy, options);
  50. this.jsonContentTypes = deserializationContentTypes && deserializationContentTypes.json || defaultJsonContentTypes;
  51. this.xmlContentTypes = deserializationContentTypes && deserializationContentTypes.xml || defaultXmlContentTypes;
  52. }
  53. public async sendRequest(request: WebResource): Promise<HttpOperationResponse> {
  54. return this._nextPolicy.sendRequest(request).then((response: HttpOperationResponse) => deserializeResponseBody(this.jsonContentTypes, this.xmlContentTypes, response));
  55. }
  56. }
  57. function getOperationResponse(parsedResponse: HttpOperationResponse): undefined | OperationResponse {
  58. let result: OperationResponse | undefined;
  59. const request: WebResource = parsedResponse.request;
  60. const operationSpec: OperationSpec | undefined = request.operationSpec;
  61. if (operationSpec) {
  62. const operationResponseGetter: undefined | ((operationSpec: OperationSpec, response: HttpOperationResponse) => (undefined | OperationResponse)) = request.operationResponseGetter;
  63. if (!operationResponseGetter) {
  64. result = operationSpec.responses[parsedResponse.status];
  65. } else {
  66. result = operationResponseGetter(operationSpec, parsedResponse);
  67. }
  68. }
  69. return result;
  70. }
  71. function shouldDeserializeResponse(parsedResponse: HttpOperationResponse): boolean {
  72. const shouldDeserialize: undefined | boolean | ((response: HttpOperationResponse) => boolean) = parsedResponse.request.shouldDeserialize;
  73. let result: boolean;
  74. if (shouldDeserialize === undefined) {
  75. result = true;
  76. } else if (typeof shouldDeserialize === "boolean") {
  77. result = shouldDeserialize;
  78. } else {
  79. result = shouldDeserialize(parsedResponse);
  80. }
  81. return result;
  82. }
  83. export function deserializeResponseBody(jsonContentTypes: string[], xmlContentTypes: string[], response: HttpOperationResponse): Promise<HttpOperationResponse> {
  84. return parse(jsonContentTypes, xmlContentTypes, response).then(parsedResponse => {
  85. const shouldDeserialize: boolean = shouldDeserializeResponse(parsedResponse);
  86. if (shouldDeserialize) {
  87. const operationSpec: OperationSpec | undefined = parsedResponse.request.operationSpec;
  88. if (operationSpec && operationSpec.responses) {
  89. const statusCode: number = parsedResponse.status;
  90. const expectedStatusCodes: string[] = Object.keys(operationSpec.responses);
  91. const hasNoExpectedStatusCodes: boolean = (expectedStatusCodes.length === 0 || (expectedStatusCodes.length === 1 && expectedStatusCodes[0] === "default"));
  92. const responseSpec: OperationResponse | undefined = getOperationResponse(parsedResponse);
  93. const isExpectedStatusCode: boolean = hasNoExpectedStatusCodes ? (200 <= statusCode && statusCode < 300) : !!responseSpec;
  94. if (!isExpectedStatusCode) {
  95. const defaultResponseSpec: OperationResponse = operationSpec.responses.default;
  96. if (defaultResponseSpec) {
  97. const initialErrorMessage: string = isStreamOperation(operationSpec)
  98. ? `Unexpected status code: ${statusCode}`
  99. : parsedResponse.bodyAsText as string;
  100. const error = new RestError(initialErrorMessage);
  101. error.statusCode = statusCode;
  102. error.request = utils.stripRequest(parsedResponse.request);
  103. error.response = utils.stripResponse(parsedResponse);
  104. let parsedErrorResponse: { [key: string]: any } = parsedResponse.parsedBody;
  105. try {
  106. if (parsedErrorResponse) {
  107. const defaultResponseBodyMapper: Mapper | undefined = defaultResponseSpec.bodyMapper;
  108. if (defaultResponseBodyMapper && defaultResponseBodyMapper.serializedName === "CloudError") {
  109. if (parsedErrorResponse.error) {
  110. parsedErrorResponse = parsedErrorResponse.error;
  111. }
  112. if (parsedErrorResponse.code) {
  113. error.code = parsedErrorResponse.code;
  114. }
  115. if (parsedErrorResponse.message) {
  116. error.message = parsedErrorResponse.message;
  117. }
  118. } else {
  119. let internalError: any = parsedErrorResponse;
  120. if (parsedErrorResponse.error) {
  121. internalError = parsedErrorResponse.error;
  122. }
  123. error.code = internalError.code;
  124. if (internalError.message) {
  125. error.message = internalError.message;
  126. }
  127. }
  128. if (defaultResponseBodyMapper) {
  129. let valueToDeserialize: any = parsedErrorResponse;
  130. if (operationSpec.isXML && defaultResponseBodyMapper.type.name === MapperType.Sequence) {
  131. valueToDeserialize = typeof parsedErrorResponse === "object"
  132. ? parsedErrorResponse[defaultResponseBodyMapper.xmlElementName!]
  133. : [];
  134. }
  135. error.body = operationSpec.serializer.deserialize(defaultResponseBodyMapper, valueToDeserialize, "error.body");
  136. }
  137. }
  138. } catch (defaultError) {
  139. error.message = `Error \"${defaultError.message}\" occurred in deserializing the responseBody - \"${parsedResponse.bodyAsText}\" for the default response.`;
  140. }
  141. return Promise.reject(error);
  142. }
  143. } else if (responseSpec) {
  144. if (responseSpec.bodyMapper) {
  145. let valueToDeserialize: any = parsedResponse.parsedBody;
  146. if (operationSpec.isXML && responseSpec.bodyMapper.type.name === MapperType.Sequence) {
  147. valueToDeserialize = typeof valueToDeserialize === "object" ? valueToDeserialize[responseSpec.bodyMapper.xmlElementName!] : [];
  148. }
  149. try {
  150. parsedResponse.parsedBody = operationSpec.serializer.deserialize(responseSpec.bodyMapper, valueToDeserialize, "operationRes.parsedBody");
  151. } catch (error) {
  152. const restError = new RestError(`Error ${error} occurred in deserializing the responseBody - ${parsedResponse.bodyAsText}`);
  153. restError.request = utils.stripRequest(parsedResponse.request);
  154. restError.response = utils.stripResponse(parsedResponse);
  155. return Promise.reject(restError);
  156. }
  157. } else if (operationSpec.httpMethod === "HEAD") {
  158. // head methods never have a body, but we return a boolean to indicate presence/absence of the resource
  159. parsedResponse.parsedBody = response.status >= 200 && response.status < 300;
  160. }
  161. if (responseSpec.headersMapper) {
  162. parsedResponse.parsedHeaders = operationSpec.serializer.deserialize(responseSpec.headersMapper, parsedResponse.headers.rawHeaders(), "operationRes.parsedHeaders");
  163. }
  164. }
  165. }
  166. }
  167. return Promise.resolve(parsedResponse);
  168. });
  169. }
  170. function parse(jsonContentTypes: string[], xmlContentTypes: string[], operationResponse: HttpOperationResponse): Promise<HttpOperationResponse> {
  171. const errorHandler = (err: Error & { code: string }) => {
  172. const msg = `Error "${err}" occurred while parsing the response body - ${operationResponse.bodyAsText}.`;
  173. const errCode = err.code || RestError.PARSE_ERROR;
  174. const e = new RestError(msg, errCode, operationResponse.status, operationResponse.request, operationResponse, operationResponse.bodyAsText);
  175. return Promise.reject(e);
  176. };
  177. if (!operationResponse.request.streamResponseBody && operationResponse.bodyAsText) {
  178. const text = operationResponse.bodyAsText;
  179. const contentType: string = operationResponse.headers.get("Content-Type") || "";
  180. const contentComponents: string[] = !contentType ? [] : contentType.split(";").map(component => component.toLowerCase());
  181. if (contentComponents.length === 0 || contentComponents.some(component => jsonContentTypes.indexOf(component) !== -1)) {
  182. return new Promise<HttpOperationResponse>(resolve => {
  183. operationResponse.parsedBody = JSON.parse(text);
  184. resolve(operationResponse);
  185. }).catch(errorHandler);
  186. } else if (contentComponents.some(component => xmlContentTypes.indexOf(component) !== -1)) {
  187. return parseXML(text)
  188. .then(body => {
  189. operationResponse.parsedBody = body;
  190. return operationResponse;
  191. })
  192. .catch(errorHandler);
  193. }
  194. }
  195. return Promise.resolve(operationResponse);
  196. }