UNPKG

11.5 kBPlain TextView Raw
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
4import { HttpOperationResponse } from "../httpOperationResponse";
5import { OperationResponse } from "../operationResponse";
6import { OperationSpec, isStreamOperation } from "../operationSpec";
7import { RestError } from "../restError";
8import { Mapper, MapperType } from "../serializer";
9import * as utils from "../util/utils";
10import { parseXML } from "../util/xml";
11import { WebResourceLike } from "../webResource";
12import {
13 BaseRequestPolicy,
14 RequestPolicy,
15 RequestPolicyFactory,
16 RequestPolicyOptionsLike,
17} from "./requestPolicy";
18
19/**
20 * The content-types that will indicate that an operation response should be deserialized in a
21 * particular way.
22 */
23export interface DeserializationContentTypes {
24 /**
25 * The content-types that indicate that an operation response should be deserialized as JSON.
26 * Defaults to [ "application/json", "text/json" ].
27 */
28 json?: string[];
29
30 /**
31 * The content-types that indicate that an operation response should be deserialized as XML.
32 * Defaults to [ "application/xml", "application/atom+xml" ].
33 */
34 xml?: string[];
35}
36
37/**
38 * Create a new serialization RequestPolicyCreator that will serialized HTTP request bodies as they
39 * pass through the HTTP pipeline.
40 */
41export function deserializationPolicy(
42 deserializationContentTypes?: DeserializationContentTypes
43): RequestPolicyFactory {
44 return {
45 create: (nextPolicy: RequestPolicy, options: RequestPolicyOptionsLike) => {
46 return new DeserializationPolicy(nextPolicy, deserializationContentTypes, options);
47 },
48 };
49}
50
51export const defaultJsonContentTypes = ["application/json", "text/json"];
52export const defaultXmlContentTypes = ["application/xml", "application/atom+xml"];
53
54/**
55 * A RequestPolicy that will deserialize HTTP response bodies and headers as they pass through the
56 * HTTP pipeline.
57 */
58export class DeserializationPolicy extends BaseRequestPolicy {
59 public readonly jsonContentTypes: string[];
60 public readonly xmlContentTypes: string[];
61
62 constructor(
63 nextPolicy: RequestPolicy,
64 deserializationContentTypes: DeserializationContentTypes | undefined,
65 options: RequestPolicyOptionsLike
66 ) {
67 super(nextPolicy, options);
68
69 this.jsonContentTypes =
70 (deserializationContentTypes && deserializationContentTypes.json) || defaultJsonContentTypes;
71 this.xmlContentTypes =
72 (deserializationContentTypes && deserializationContentTypes.xml) || defaultXmlContentTypes;
73 }
74
75 public async sendRequest(request: WebResourceLike): Promise<HttpOperationResponse> {
76 return this._nextPolicy
77 .sendRequest(request)
78 .then((response: HttpOperationResponse) =>
79 deserializeResponseBody(this.jsonContentTypes, this.xmlContentTypes, response)
80 );
81 }
82}
83
84function getOperationResponse(
85 parsedResponse: HttpOperationResponse
86): undefined | OperationResponse {
87 let result: OperationResponse | undefined;
88 const request: WebResourceLike = parsedResponse.request;
89 const operationSpec: OperationSpec | undefined = request.operationSpec;
90 if (operationSpec) {
91 const operationResponseGetter:
92 | undefined
93 | ((
94 operationSpec: OperationSpec,
95 response: HttpOperationResponse
96 ) => undefined | OperationResponse) = request.operationResponseGetter;
97 if (!operationResponseGetter) {
98 result = operationSpec.responses[parsedResponse.status];
99 } else {
100 result = operationResponseGetter(operationSpec, parsedResponse);
101 }
102 }
103 return result;
104}
105
106function shouldDeserializeResponse(parsedResponse: HttpOperationResponse): boolean {
107 const shouldDeserialize: undefined | boolean | ((response: HttpOperationResponse) => boolean) =
108 parsedResponse.request.shouldDeserialize;
109 let result: boolean;
110 if (shouldDeserialize === undefined) {
111 result = true;
112 } else if (typeof shouldDeserialize === "boolean") {
113 result = shouldDeserialize;
114 } else {
115 result = shouldDeserialize(parsedResponse);
116 }
117 return result;
118}
119
120export function deserializeResponseBody(
121 jsonContentTypes: string[],
122 xmlContentTypes: string[],
123 response: HttpOperationResponse
124): Promise<HttpOperationResponse> {
125 return parse(jsonContentTypes, xmlContentTypes, response).then((parsedResponse) => {
126 const shouldDeserialize: boolean = shouldDeserializeResponse(parsedResponse);
127 if (shouldDeserialize) {
128 const operationSpec: OperationSpec | undefined = parsedResponse.request.operationSpec;
129 if (operationSpec && operationSpec.responses) {
130 const statusCode: number = parsedResponse.status;
131
132 const expectedStatusCodes: string[] = Object.keys(operationSpec.responses);
133
134 const hasNoExpectedStatusCodes: boolean =
135 expectedStatusCodes.length === 0 ||
136 (expectedStatusCodes.length === 1 && expectedStatusCodes[0] === "default");
137
138 const responseSpec: OperationResponse | undefined = getOperationResponse(parsedResponse);
139
140 const isExpectedStatusCode: boolean = hasNoExpectedStatusCodes
141 ? 200 <= statusCode && statusCode < 300
142 : !!responseSpec;
143 if (!isExpectedStatusCode) {
144 const defaultResponseSpec: OperationResponse = operationSpec.responses.default;
145 if (defaultResponseSpec) {
146 const initialErrorMessage: string = isStreamOperation(operationSpec)
147 ? `Unexpected status code: ${statusCode}`
148 : (parsedResponse.bodyAsText as string);
149
150 const error = new RestError(initialErrorMessage);
151 error.statusCode = statusCode;
152 error.request = utils.stripRequest(parsedResponse.request);
153 error.response = utils.stripResponse(parsedResponse);
154
155 let parsedErrorResponse: { [key: string]: any } = parsedResponse.parsedBody;
156 try {
157 if (parsedErrorResponse) {
158 const defaultResponseBodyMapper: Mapper | undefined =
159 defaultResponseSpec.bodyMapper;
160 if (
161 defaultResponseBodyMapper &&
162 defaultResponseBodyMapper.serializedName === "CloudError"
163 ) {
164 if (parsedErrorResponse.error) {
165 parsedErrorResponse = parsedErrorResponse.error;
166 }
167 if (parsedErrorResponse.code) {
168 error.code = parsedErrorResponse.code;
169 }
170 if (parsedErrorResponse.message) {
171 error.message = parsedErrorResponse.message;
172 }
173 } else {
174 let internalError: any = parsedErrorResponse;
175 if (parsedErrorResponse.error) {
176 internalError = parsedErrorResponse.error;
177 }
178
179 error.code = internalError.code;
180 if (internalError.message) {
181 error.message = internalError.message;
182 }
183 }
184
185 if (defaultResponseBodyMapper) {
186 let valueToDeserialize: any = parsedErrorResponse;
187 if (
188 operationSpec.isXML &&
189 defaultResponseBodyMapper.type.name === MapperType.Sequence
190 ) {
191 valueToDeserialize =
192 typeof parsedErrorResponse === "object"
193 ? parsedErrorResponse[defaultResponseBodyMapper.xmlElementName!]
194 : [];
195 }
196 error.body = operationSpec.serializer.deserialize(
197 defaultResponseBodyMapper,
198 valueToDeserialize,
199 "error.body"
200 );
201 }
202 }
203 } catch (defaultError) {
204 error.message = `Error \"${defaultError.message}\" occurred in deserializing the responseBody - \"${parsedResponse.bodyAsText}\" for the default response.`;
205 }
206 return Promise.reject(error);
207 }
208 } else if (responseSpec) {
209 if (responseSpec.bodyMapper) {
210 let valueToDeserialize: any = parsedResponse.parsedBody;
211 if (operationSpec.isXML && responseSpec.bodyMapper.type.name === MapperType.Sequence) {
212 valueToDeserialize =
213 typeof valueToDeserialize === "object"
214 ? valueToDeserialize[responseSpec.bodyMapper.xmlElementName!]
215 : [];
216 }
217 try {
218 parsedResponse.parsedBody = operationSpec.serializer.deserialize(
219 responseSpec.bodyMapper,
220 valueToDeserialize,
221 "operationRes.parsedBody"
222 );
223 } catch (error) {
224 const restError = new RestError(
225 `Error ${error} occurred in deserializing the responseBody - ${parsedResponse.bodyAsText}`
226 );
227 restError.request = utils.stripRequest(parsedResponse.request);
228 restError.response = utils.stripResponse(parsedResponse);
229 return Promise.reject(restError);
230 }
231 } else if (operationSpec.httpMethod === "HEAD") {
232 // head methods never have a body, but we return a boolean to indicate presence/absence of the resource
233 parsedResponse.parsedBody = response.status >= 200 && response.status < 300;
234 }
235
236 if (responseSpec.headersMapper) {
237 parsedResponse.parsedHeaders = operationSpec.serializer.deserialize(
238 responseSpec.headersMapper,
239 parsedResponse.headers.rawHeaders(),
240 "operationRes.parsedHeaders"
241 );
242 }
243 }
244 }
245 }
246 return Promise.resolve(parsedResponse);
247 });
248}
249
250function parse(
251 jsonContentTypes: string[],
252 xmlContentTypes: string[],
253 operationResponse: HttpOperationResponse
254): Promise<HttpOperationResponse> {
255 const errorHandler = (err: Error & { code: string }) => {
256 const msg = `Error "${err}" occurred while parsing the response body - ${operationResponse.bodyAsText}.`;
257 const errCode = err.code || RestError.PARSE_ERROR;
258 const e = new RestError(
259 msg,
260 errCode,
261 operationResponse.status,
262 operationResponse.request,
263 operationResponse,
264 operationResponse.bodyAsText
265 );
266 return Promise.reject(e);
267 };
268
269 if (!operationResponse.request.streamResponseBody && operationResponse.bodyAsText) {
270 const text = operationResponse.bodyAsText;
271 const contentType: string = operationResponse.headers.get("Content-Type") || "";
272 const contentComponents: string[] = !contentType
273 ? []
274 : contentType.split(";").map((component) => component.toLowerCase());
275 if (
276 contentComponents.length === 0 ||
277 contentComponents.some((component) => jsonContentTypes.indexOf(component) !== -1)
278 ) {
279 return new Promise<HttpOperationResponse>((resolve) => {
280 operationResponse.parsedBody = JSON.parse(text);
281 resolve(operationResponse);
282 }).catch(errorHandler);
283 } else if (contentComponents.some((component) => xmlContentTypes.indexOf(component) !== -1)) {
284 return parseXML(text)
285 .then((body) => {
286 operationResponse.parsedBody = body;
287 return operationResponse;
288 })
289 .catch(errorHandler);
290 }
291 }
292
293 return Promise.resolve(operationResponse);
294}
295
\No newline at end of file