1 |
|
2 |
|
3 |
|
4 | import { HttpOperationResponse } from "../httpOperationResponse";
|
5 | import { OperationResponse } from "../operationResponse";
|
6 | import { OperationSpec, isStreamOperation } from "../operationSpec";
|
7 | import { RestError } from "../restError";
|
8 | import { Mapper, MapperType } from "../serializer";
|
9 | import * as utils from "../util/utils";
|
10 | import { parseXML } from "../util/xml";
|
11 | import { WebResourceLike } from "../webResource";
|
12 | import {
|
13 | BaseRequestPolicy,
|
14 | RequestPolicy,
|
15 | RequestPolicyFactory,
|
16 | RequestPolicyOptionsLike,
|
17 | } from "./requestPolicy";
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 | export interface DeserializationContentTypes {
|
24 | |
25 |
|
26 |
|
27 |
|
28 | json?: string[];
|
29 |
|
30 | |
31 |
|
32 |
|
33 |
|
34 | xml?: string[];
|
35 | }
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 | export 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 |
|
51 | export const defaultJsonContentTypes = ["application/json", "text/json"];
|
52 | export const defaultXmlContentTypes = ["application/xml", "application/atom+xml"];
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | export 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 |
|
84 | function 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 |
|
106 | function 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 |
|
120 | export 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 |
|
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 |
|
250 | function 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 |