UNPKG

29.9 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 { TokenCredential, isTokenCredential } from "@azure/core-auth";
5import { ServiceClientCredentials } from "./credentials/serviceClientCredentials";
6import { DefaultHttpClient } from "./defaultHttpClient";
7import { HttpClient } from "./httpClient";
8import { HttpOperationResponse, RestResponse } from "./httpOperationResponse";
9import { HttpPipelineLogger } from "./httpPipelineLogger";
10import { OperationArguments } from "./operationArguments";
11import {
12 getPathStringFromParameter,
13 getPathStringFromParameterPath,
14 OperationParameter,
15 ParameterPath,
16} from "./operationParameter";
17import { isStreamOperation, OperationSpec } from "./operationSpec";
18import {
19 deserializationPolicy,
20 DeserializationContentTypes,
21} from "./policies/deserializationPolicy";
22import { exponentialRetryPolicy } from "./policies/exponentialRetryPolicy";
23import { generateClientRequestIdPolicy } from "./policies/generateClientRequestIdPolicy";
24import {
25 userAgentPolicy,
26 getDefaultUserAgentHeaderName,
27 getDefaultUserAgentValue,
28} from "./policies/userAgentPolicy";
29import { DefaultRedirectOptions, RedirectOptions, redirectPolicy } from "./policies/redirectPolicy";
30import {
31 RequestPolicy,
32 RequestPolicyFactory,
33 RequestPolicyOptions,
34 RequestPolicyOptionsLike,
35} from "./policies/requestPolicy";
36import { rpRegistrationPolicy } from "./policies/rpRegistrationPolicy";
37import { signingPolicy } from "./policies/signingPolicy";
38import { systemErrorRetryPolicy } from "./policies/systemErrorRetryPolicy";
39import { QueryCollectionFormat } from "./queryCollectionFormat";
40import { CompositeMapper, DictionaryMapper, Mapper, MapperType, Serializer } from "./serializer";
41import { URLBuilder } from "./url";
42import * as utils from "./util/utils";
43import { stringifyXML } from "./util/xml";
44import {
45 RequestOptionsBase,
46 RequestPrepareOptions,
47 WebResourceLike,
48 isWebResourceLike,
49 WebResource,
50} from "./webResource";
51import { OperationResponse } from "./operationResponse";
52import { ServiceCallback } from "./util/utils";
53import { agentPolicy } from "./policies/agentPolicy";
54import { proxyPolicy, getDefaultProxySettings } from "./policies/proxyPolicy";
55import { throttlingRetryPolicy } from "./policies/throttlingRetryPolicy";
56import { Agent } from "http";
57import {
58 AzureIdentityCredentialAdapter,
59 azureResourceManagerEndpoints,
60} from "./credentials/azureIdentityTokenCredentialAdapter";
61
62/**
63 * HTTP proxy settings (Node.js only)
64 */
65export interface ProxySettings {
66 host: string;
67 port: number;
68 username?: string;
69 password?: string;
70}
71
72/**
73 * HTTP and HTTPS agents (Node.js only)
74 */
75export interface AgentSettings {
76 http: Agent;
77 https: Agent;
78}
79
80/**
81 * Options to be provided while creating the client.
82 */
83export interface ServiceClientOptions {
84 /**
85 * An array of factories which get called to create the RequestPolicy pipeline used to send a HTTP
86 * request on the wire, or a function that takes in the defaultRequestPolicyFactories and returns
87 * the requestPolicyFactories that will be used.
88 */
89 requestPolicyFactories?:
90 | RequestPolicyFactory[]
91 | ((defaultRequestPolicyFactories: RequestPolicyFactory[]) => void | RequestPolicyFactory[]);
92 /**
93 * The HttpClient that will be used to send HTTP requests.
94 */
95 httpClient?: HttpClient;
96 /**
97 * The HttpPipelineLogger that can be used to debug RequestPolicies within the HTTP pipeline.
98 */
99 httpPipelineLogger?: HttpPipelineLogger;
100 /**
101 * If set to true, turn off the default retry policy.
102 */
103 noRetryPolicy?: boolean;
104 /**
105 * Gets or sets the retry timeout in seconds for AutomaticRPRegistration. Default value is 30.
106 */
107 rpRegistrationRetryTimeout?: number;
108 /**
109 * Whether or not to generate a client request ID header for each HTTP request.
110 */
111 generateClientRequestIdHeader?: boolean;
112 /**
113 * Whether to include credentials in CORS requests in the browser.
114 * See https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials for more information.
115 */
116 withCredentials?: boolean;
117 /**
118 * If specified, a GenerateRequestIdPolicy will be added to the HTTP pipeline that will add a
119 * header to all outgoing requests with this header name and a random UUID as the request ID.
120 */
121 clientRequestIdHeaderName?: string;
122 /**
123 * The content-types that will be associated with JSON or XML serialization.
124 */
125 deserializationContentTypes?: DeserializationContentTypes;
126 /**
127 * The header name to use for the telemetry header while sending the request. If this is not
128 * specified, then "User-Agent" will be used when running on Node.js and "x-ms-command-name" will
129 * be used when running in a browser.
130 */
131 userAgentHeaderName?: string | ((defaultUserAgentHeaderName: string) => string);
132 /**
133 * The string to be set to the telemetry header while sending the request, or a function that
134 * takes in the default user-agent string and returns the user-agent string that will be used.
135 */
136 userAgent?: string | ((defaultUserAgent: string) => string);
137 /**
138 * Proxy settings which will be used for every HTTP request (Node.js only).
139 */
140 proxySettings?: ProxySettings;
141 /**
142 * Options for how redirect responses are handled.
143 */
144 redirectOptions?: RedirectOptions;
145 /**
146 * HTTP and HTTPS agents which will be used for every HTTP request (Node.js only).
147 */
148 agentSettings?: AgentSettings;
149 /**
150 * If specified:
151 * - This `baseUri` becomes the base URI that requests will be made against for this ServiceClient.
152 * - If the `baseUri` matches a known resource manager endpoint and if a `TokenCredential` was passed through the constructor, this `baseUri` defines the `getToken` scope to be `${options.baseUri}/.default`. Otherwise, the scope would default to "https://management.azure.com/.default".
153 *
154 * If it is not specified:
155 * - All OperationSpecs must contain a baseUrl property.
156 * - If a `TokenCredential` was passed through the constructor, the `getToken` scope is set to be "https://management.azure.com/.default".
157 */
158 baseUri?: string;
159}
160
161/**
162 * @class
163 * Initializes a new instance of the ServiceClient.
164 */
165export class ServiceClient {
166 /**
167 * The base URI against which requests will be made when using this ServiceClient instance.
168 *
169 * This can be set either by setting the `baseUri` in the `options` parameter to the ServiceClient constructor or directly after constructing the ServiceClient.
170 * If set via the ServiceClient constructor when using the overload that takes the `TokenCredential`, and if it matches a known resource manager endpoint, this base URI sets the scope used to get the AAD token to `${baseUri}/.default` instead of the default "https://management.azure.com/.default"
171 *
172 * If it is not specified, all OperationSpecs must contain a baseUrl property.
173 */
174 protected baseUri?: string;
175
176 /**
177 * The default request content type for the service.
178 * Used if no requestContentType is present on an OperationSpec.
179 */
180 protected requestContentType?: string;
181
182 /**
183 * The HTTP client that will be used to send requests.
184 */
185 private readonly _httpClient: HttpClient;
186 private readonly _requestPolicyOptions: RequestPolicyOptionsLike;
187
188 private readonly _requestPolicyFactories: RequestPolicyFactory[];
189 private readonly _withCredentials: boolean;
190
191 /**
192 * The ServiceClient constructor
193 * @constructor
194 * @param {ServiceClientCredentials} [credentials] The credentials object used for authentication.
195 * @param {ServiceClientOptions} [options] The service client options that govern the behavior of the client.
196 */
197 constructor(
198 credentials?: ServiceClientCredentials | TokenCredential,
199 options?: ServiceClientOptions
200 ) {
201 if (!options) {
202 options = {};
203 }
204
205 if (options.baseUri) {
206 this.baseUri = options.baseUri;
207 }
208
209 let serviceClientCredentials: ServiceClientCredentials | undefined;
210 if (isTokenCredential(credentials)) {
211 let scope: string | undefined = undefined;
212 if (options?.baseUri && azureResourceManagerEndpoints.includes(options?.baseUri)) {
213 scope = `${options.baseUri}/.default`;
214 }
215 serviceClientCredentials = new AzureIdentityCredentialAdapter(credentials, scope);
216 } else {
217 serviceClientCredentials = credentials;
218 }
219
220 if (serviceClientCredentials && !serviceClientCredentials.signRequest) {
221 throw new Error("credentials argument needs to implement signRequest method");
222 }
223
224 this._withCredentials = options.withCredentials || false;
225 this._httpClient = options.httpClient || new DefaultHttpClient();
226 this._requestPolicyOptions = new RequestPolicyOptions(options.httpPipelineLogger);
227
228 let requestPolicyFactories: RequestPolicyFactory[];
229 if (Array.isArray(options.requestPolicyFactories)) {
230 requestPolicyFactories = options.requestPolicyFactories;
231 } else {
232 requestPolicyFactories = createDefaultRequestPolicyFactories(
233 serviceClientCredentials,
234 options
235 );
236 if (options.requestPolicyFactories) {
237 const newRequestPolicyFactories:
238 | void
239 | RequestPolicyFactory[] = options.requestPolicyFactories(requestPolicyFactories);
240 if (newRequestPolicyFactories) {
241 requestPolicyFactories = newRequestPolicyFactories;
242 }
243 }
244 }
245 this._requestPolicyFactories = requestPolicyFactories;
246 }
247
248 /**
249 * Send the provided httpRequest.
250 */
251 sendRequest(options: RequestPrepareOptions | WebResourceLike): Promise<HttpOperationResponse> {
252 if (options === null || options === undefined || typeof options !== "object") {
253 throw new Error("options cannot be null or undefined and it must be of type object.");
254 }
255
256 let httpRequest: WebResourceLike;
257 try {
258 if (isWebResourceLike(options)) {
259 options.validateRequestProperties();
260 httpRequest = options;
261 } else {
262 httpRequest = new WebResource();
263 httpRequest = httpRequest.prepare(options);
264 }
265 } catch (error) {
266 return Promise.reject(error);
267 }
268
269 let httpPipeline: RequestPolicy = this._httpClient;
270 if (this._requestPolicyFactories && this._requestPolicyFactories.length > 0) {
271 for (let i = this._requestPolicyFactories.length - 1; i >= 0; --i) {
272 httpPipeline = this._requestPolicyFactories[i].create(
273 httpPipeline,
274 this._requestPolicyOptions
275 );
276 }
277 }
278 return httpPipeline.sendRequest(httpRequest);
279 }
280
281 /**
282 * Send an HTTP request that is populated using the provided OperationSpec.
283 * @param {OperationArguments} operationArguments The arguments that the HTTP request's templated values will be populated from.
284 * @param {OperationSpec} operationSpec The OperationSpec to use to populate the httpRequest.
285 * @param {ServiceCallback} callback The callback to call when the response is received.
286 */
287 sendOperationRequest(
288 operationArguments: OperationArguments,
289 operationSpec: OperationSpec,
290 callback?: ServiceCallback<any>
291 ): Promise<RestResponse> {
292 if (typeof operationArguments.options === "function") {
293 callback = operationArguments.options;
294 operationArguments.options = undefined;
295 }
296
297 const httpRequest = new WebResource();
298
299 let result: Promise<RestResponse>;
300 try {
301 const baseUri: string | undefined = operationSpec.baseUrl || this.baseUri;
302 if (!baseUri) {
303 throw new Error(
304 "If operationSpec.baseUrl is not specified, then the ServiceClient must have a baseUri string property that contains the base URL to use."
305 );
306 }
307
308 httpRequest.method = operationSpec.httpMethod;
309 httpRequest.operationSpec = operationSpec;
310
311 const requestUrl: URLBuilder = URLBuilder.parse(baseUri);
312 if (operationSpec.path) {
313 requestUrl.appendPath(operationSpec.path);
314 }
315 if (operationSpec.urlParameters && operationSpec.urlParameters.length > 0) {
316 for (const urlParameter of operationSpec.urlParameters) {
317 let urlParameterValue: string = getOperationArgumentValueFromParameter(
318 this,
319 operationArguments,
320 urlParameter,
321 operationSpec.serializer
322 );
323 urlParameterValue = operationSpec.serializer.serialize(
324 urlParameter.mapper,
325 urlParameterValue,
326 getPathStringFromParameter(urlParameter)
327 );
328 if (!urlParameter.skipEncoding) {
329 urlParameterValue = encodeURIComponent(urlParameterValue);
330 }
331 requestUrl.replaceAll(
332 `{${urlParameter.mapper.serializedName || getPathStringFromParameter(urlParameter)}}`,
333 urlParameterValue
334 );
335 }
336 }
337 if (operationSpec.queryParameters && operationSpec.queryParameters.length > 0) {
338 for (const queryParameter of operationSpec.queryParameters) {
339 let queryParameterValue: any = getOperationArgumentValueFromParameter(
340 this,
341 operationArguments,
342 queryParameter,
343 operationSpec.serializer
344 );
345 if (queryParameterValue != undefined) {
346 queryParameterValue = operationSpec.serializer.serialize(
347 queryParameter.mapper,
348 queryParameterValue,
349 getPathStringFromParameter(queryParameter)
350 );
351 if (queryParameter.collectionFormat != undefined) {
352 if (queryParameter.collectionFormat === QueryCollectionFormat.Multi) {
353 if (queryParameterValue.length === 0) {
354 queryParameterValue = "";
355 } else {
356 for (const index in queryParameterValue) {
357 const item = queryParameterValue[index];
358 queryParameterValue[index] = item == undefined ? "" : item.toString();
359 }
360 }
361 } else if (
362 queryParameter.collectionFormat === QueryCollectionFormat.Ssv ||
363 queryParameter.collectionFormat === QueryCollectionFormat.Tsv
364 ) {
365 queryParameterValue = queryParameterValue.join(queryParameter.collectionFormat);
366 }
367 }
368 if (!queryParameter.skipEncoding) {
369 if (Array.isArray(queryParameterValue)) {
370 for (const index in queryParameterValue) {
371 if (
372 queryParameterValue[index] !== undefined &&
373 queryParameterValue[index] !== null
374 ) {
375 queryParameterValue[index] = encodeURIComponent(queryParameterValue[index]);
376 }
377 }
378 } else {
379 queryParameterValue = encodeURIComponent(queryParameterValue);
380 }
381 }
382 if (
383 queryParameter.collectionFormat != undefined &&
384 queryParameter.collectionFormat !== QueryCollectionFormat.Multi &&
385 queryParameter.collectionFormat !== QueryCollectionFormat.Ssv &&
386 queryParameter.collectionFormat !== QueryCollectionFormat.Tsv
387 ) {
388 queryParameterValue = queryParameterValue.join(queryParameter.collectionFormat);
389 }
390 requestUrl.setQueryParameter(
391 queryParameter.mapper.serializedName || getPathStringFromParameter(queryParameter),
392 queryParameterValue
393 );
394 }
395 }
396 }
397 httpRequest.url = requestUrl.toString();
398
399 const contentType = operationSpec.contentType || this.requestContentType;
400 if (contentType) {
401 httpRequest.headers.set("Content-Type", contentType);
402 }
403
404 if (operationSpec.headerParameters) {
405 for (const headerParameter of operationSpec.headerParameters) {
406 let headerValue: any = getOperationArgumentValueFromParameter(
407 this,
408 operationArguments,
409 headerParameter,
410 operationSpec.serializer
411 );
412 if (headerValue != undefined) {
413 headerValue = operationSpec.serializer.serialize(
414 headerParameter.mapper,
415 headerValue,
416 getPathStringFromParameter(headerParameter)
417 );
418 const headerCollectionPrefix = (headerParameter.mapper as DictionaryMapper)
419 .headerCollectionPrefix;
420 if (headerCollectionPrefix) {
421 for (const key of Object.keys(headerValue)) {
422 httpRequest.headers.set(headerCollectionPrefix + key, headerValue[key]);
423 }
424 } else {
425 httpRequest.headers.set(
426 headerParameter.mapper.serializedName ||
427 getPathStringFromParameter(headerParameter),
428 headerValue
429 );
430 }
431 }
432 }
433 }
434
435 const options: RequestOptionsBase | undefined = operationArguments.options;
436 if (options) {
437 if (options.customHeaders) {
438 for (const customHeaderName in options.customHeaders) {
439 httpRequest.headers.set(customHeaderName, options.customHeaders[customHeaderName]);
440 }
441 }
442
443 if (options.abortSignal) {
444 httpRequest.abortSignal = options.abortSignal;
445 }
446
447 if (options.timeout) {
448 httpRequest.timeout = options.timeout;
449 }
450
451 if (options.onUploadProgress) {
452 httpRequest.onUploadProgress = options.onUploadProgress;
453 }
454
455 if (options.onDownloadProgress) {
456 httpRequest.onDownloadProgress = options.onDownloadProgress;
457 }
458 }
459
460 httpRequest.withCredentials = this._withCredentials;
461
462 serializeRequestBody(this, httpRequest, operationArguments, operationSpec);
463
464 if (httpRequest.streamResponseBody == undefined) {
465 httpRequest.streamResponseBody = isStreamOperation(operationSpec);
466 }
467
468 result = this.sendRequest(httpRequest).then((res) =>
469 flattenResponse(res, operationSpec.responses[res.status])
470 );
471 } catch (error) {
472 result = Promise.reject(error);
473 }
474
475 const cb = callback;
476 if (cb) {
477 result
478 // tslint:disable-next-line:no-null-keyword
479 .then((res) => cb(null, res._response.parsedBody, res._response.request, res._response))
480 .catch((err) => cb(err));
481 }
482
483 return result;
484 }
485}
486
487export function serializeRequestBody(
488 serviceClient: ServiceClient,
489 httpRequest: WebResourceLike,
490 operationArguments: OperationArguments,
491 operationSpec: OperationSpec
492): void {
493 if (operationSpec.requestBody && operationSpec.requestBody.mapper) {
494 httpRequest.body = getOperationArgumentValueFromParameter(
495 serviceClient,
496 operationArguments,
497 operationSpec.requestBody,
498 operationSpec.serializer
499 );
500
501 const bodyMapper = operationSpec.requestBody.mapper;
502 const { required, xmlName, xmlElementName, serializedName } = bodyMapper;
503 const typeName = bodyMapper.type.name;
504 try {
505 if (httpRequest.body != undefined || required) {
506 const requestBodyParameterPathString: string = getPathStringFromParameter(
507 operationSpec.requestBody
508 );
509 httpRequest.body = operationSpec.serializer.serialize(
510 bodyMapper,
511 httpRequest.body,
512 requestBodyParameterPathString
513 );
514 const isStream = typeName === MapperType.Stream;
515 if (operationSpec.isXML) {
516 if (typeName === MapperType.Sequence) {
517 httpRequest.body = stringifyXML(
518 utils.prepareXMLRootList(
519 httpRequest.body,
520 xmlElementName || xmlName || serializedName!
521 ),
522 { rootName: xmlName || serializedName }
523 );
524 } else if (!isStream) {
525 httpRequest.body = stringifyXML(httpRequest.body, {
526 rootName: xmlName || serializedName,
527 });
528 }
529 } else if (!isStream) {
530 httpRequest.body = JSON.stringify(httpRequest.body);
531 }
532 }
533 } catch (error) {
534 throw new Error(
535 `Error "${error.message}" occurred in serializing the payload - ${JSON.stringify(
536 serializedName,
537 undefined,
538 " "
539 )}.`
540 );
541 }
542 } else if (operationSpec.formDataParameters && operationSpec.formDataParameters.length > 0) {
543 httpRequest.formData = {};
544 for (const formDataParameter of operationSpec.formDataParameters) {
545 const formDataParameterValue: any = getOperationArgumentValueFromParameter(
546 serviceClient,
547 operationArguments,
548 formDataParameter,
549 operationSpec.serializer
550 );
551 if (formDataParameterValue != undefined) {
552 const formDataParameterPropertyName: string =
553 formDataParameter.mapper.serializedName || getPathStringFromParameter(formDataParameter);
554 httpRequest.formData[formDataParameterPropertyName] = operationSpec.serializer.serialize(
555 formDataParameter.mapper,
556 formDataParameterValue,
557 getPathStringFromParameter(formDataParameter)
558 );
559 }
560 }
561 }
562}
563
564function isRequestPolicyFactory(instance: any): instance is RequestPolicyFactory {
565 return typeof instance.create === "function";
566}
567
568function getValueOrFunctionResult(
569 value: undefined | string | ((defaultValue: string) => string),
570 defaultValueCreator: () => string
571): string {
572 let result: string;
573 if (typeof value === "string") {
574 result = value;
575 } else {
576 result = defaultValueCreator();
577 if (typeof value === "function") {
578 result = value(result);
579 }
580 }
581 return result;
582}
583
584function createDefaultRequestPolicyFactories(
585 credentials: ServiceClientCredentials | RequestPolicyFactory | undefined,
586 options: ServiceClientOptions
587): RequestPolicyFactory[] {
588 const factories: RequestPolicyFactory[] = [];
589
590 if (options.generateClientRequestIdHeader) {
591 factories.push(generateClientRequestIdPolicy(options.clientRequestIdHeaderName));
592 }
593
594 if (credentials) {
595 if (isRequestPolicyFactory(credentials)) {
596 factories.push(credentials);
597 } else {
598 factories.push(signingPolicy(credentials));
599 }
600 }
601
602 const userAgentHeaderName: string = getValueOrFunctionResult(
603 options.userAgentHeaderName,
604 getDefaultUserAgentHeaderName
605 );
606 const userAgentHeaderValue: string = getValueOrFunctionResult(
607 options.userAgent,
608 getDefaultUserAgentValue
609 );
610 if (userAgentHeaderName && userAgentHeaderValue) {
611 factories.push(userAgentPolicy({ key: userAgentHeaderName, value: userAgentHeaderValue }));
612 }
613
614 const redirectOptions = {
615 ...DefaultRedirectOptions,
616 ...options.redirectOptions,
617 };
618 if (redirectOptions.handleRedirects) {
619 factories.push(redirectPolicy(redirectOptions.maxRetries));
620 }
621
622 factories.push(rpRegistrationPolicy(options.rpRegistrationRetryTimeout));
623
624 if (!options.noRetryPolicy) {
625 factories.push(exponentialRetryPolicy());
626 factories.push(systemErrorRetryPolicy());
627 factories.push(throttlingRetryPolicy());
628 }
629
630 factories.push(deserializationPolicy(options.deserializationContentTypes));
631
632 const proxySettings = options.proxySettings || getDefaultProxySettings();
633 if (proxySettings) {
634 factories.push(proxyPolicy(proxySettings));
635 }
636
637 if (options.agentSettings) {
638 factories.push(agentPolicy(options.agentSettings));
639 }
640
641 return factories;
642}
643
644export type PropertyParent = { [propertyName: string]: any };
645
646/**
647 * Get the property parent for the property at the provided path when starting with the provided
648 * parent object.
649 */
650export function getPropertyParent(parent: PropertyParent, propertyPath: string[]): PropertyParent {
651 if (parent && propertyPath) {
652 const propertyPathLength: number = propertyPath.length;
653 for (let i = 0; i < propertyPathLength - 1; ++i) {
654 const propertyName: string = propertyPath[i];
655 if (!parent[propertyName]) {
656 parent[propertyName] = {};
657 }
658 parent = parent[propertyName];
659 }
660 }
661 return parent;
662}
663
664function getOperationArgumentValueFromParameter(
665 serviceClient: ServiceClient,
666 operationArguments: OperationArguments,
667 parameter: OperationParameter,
668 serializer: Serializer
669): any {
670 return getOperationArgumentValueFromParameterPath(
671 serviceClient,
672 operationArguments,
673 parameter.parameterPath,
674 parameter.mapper,
675 serializer
676 );
677}
678
679export function getOperationArgumentValueFromParameterPath(
680 serviceClient: ServiceClient,
681 operationArguments: OperationArguments,
682 parameterPath: ParameterPath,
683 parameterMapper: Mapper,
684 serializer: Serializer
685): any {
686 let value: any;
687 if (typeof parameterPath === "string") {
688 parameterPath = [parameterPath];
689 }
690 if (Array.isArray(parameterPath)) {
691 if (parameterPath.length > 0) {
692 if (parameterMapper.isConstant) {
693 value = parameterMapper.defaultValue;
694 } else {
695 let propertySearchResult: PropertySearchResult = getPropertyFromParameterPath(
696 operationArguments,
697 parameterPath
698 );
699 if (!propertySearchResult.propertyFound) {
700 propertySearchResult = getPropertyFromParameterPath(serviceClient, parameterPath);
701 }
702
703 let useDefaultValue = false;
704 if (!propertySearchResult.propertyFound) {
705 useDefaultValue =
706 parameterMapper.required ||
707 (parameterPath[0] === "options" && parameterPath.length === 2);
708 }
709 value = useDefaultValue ? parameterMapper.defaultValue : propertySearchResult.propertyValue;
710 }
711
712 // Serialize just for validation purposes.
713 const parameterPathString: string = getPathStringFromParameterPath(
714 parameterPath,
715 parameterMapper
716 );
717 serializer.serialize(parameterMapper, value, parameterPathString);
718 }
719 } else {
720 if (parameterMapper.required) {
721 value = {};
722 }
723
724 for (const propertyName in parameterPath) {
725 const propertyMapper: Mapper = (parameterMapper as CompositeMapper).type.modelProperties![
726 propertyName
727 ];
728 const propertyPath: ParameterPath = parameterPath[propertyName];
729 const propertyValue: any = getOperationArgumentValueFromParameterPath(
730 serviceClient,
731 operationArguments,
732 propertyPath,
733 propertyMapper,
734 serializer
735 );
736 // Serialize just for validation purposes.
737 const propertyPathString: string = getPathStringFromParameterPath(
738 propertyPath,
739 propertyMapper
740 );
741 serializer.serialize(propertyMapper, propertyValue, propertyPathString);
742 if (propertyValue !== undefined) {
743 if (!value) {
744 value = {};
745 }
746 value[propertyName] = propertyValue;
747 }
748 }
749 }
750 return value;
751}
752
753interface PropertySearchResult {
754 propertyValue?: any;
755 propertyFound: boolean;
756}
757
758function getPropertyFromParameterPath(
759 parent: { [parameterName: string]: any },
760 parameterPath: string[]
761): PropertySearchResult {
762 const result: PropertySearchResult = { propertyFound: false };
763 let i = 0;
764 for (; i < parameterPath.length; ++i) {
765 const parameterPathPart: string = parameterPath[i];
766 // Make sure to check inherited properties too, so don't use hasOwnProperty().
767 if (parent != undefined && parameterPathPart in parent) {
768 parent = parent[parameterPathPart];
769 } else {
770 break;
771 }
772 }
773 if (i === parameterPath.length) {
774 result.propertyValue = parent;
775 result.propertyFound = true;
776 }
777 return result;
778}
779
780export function flattenResponse(
781 _response: HttpOperationResponse,
782 responseSpec: OperationResponse | undefined
783): RestResponse {
784 const parsedHeaders = _response.parsedHeaders;
785 const bodyMapper = responseSpec && responseSpec.bodyMapper;
786
787 const addOperationResponse = (obj: {}) =>
788 Object.defineProperty(obj, "_response", {
789 value: _response,
790 });
791
792 if (bodyMapper) {
793 const typeName = bodyMapper.type.name;
794 if (typeName === "Stream") {
795 return addOperationResponse({
796 ...parsedHeaders,
797 blobBody: _response.blobBody,
798 readableStreamBody: _response.readableStreamBody,
799 });
800 }
801
802 const modelProperties =
803 (typeName === "Composite" && (bodyMapper as CompositeMapper).type.modelProperties) || {};
804 const isPageableResponse = Object.keys(modelProperties).some(
805 (k) => modelProperties[k].serializedName === ""
806 );
807 if (typeName === "Sequence" || isPageableResponse) {
808 // We're expecting a sequece(array) make sure that the response body is in the
809 // correct format, if not make it an empty array []
810 const parsedBody = Array.isArray(_response.parsedBody) ? _response.parsedBody : [];
811 const arrayResponse = [...parsedBody] as RestResponse & any[];
812
813 for (const key of Object.keys(modelProperties)) {
814 if (modelProperties[key].serializedName) {
815 arrayResponse[key] = _response.parsedBody[key];
816 }
817 }
818
819 if (parsedHeaders) {
820 for (const key of Object.keys(parsedHeaders)) {
821 arrayResponse[key] = parsedHeaders[key];
822 }
823 }
824 addOperationResponse(arrayResponse);
825 return arrayResponse;
826 }
827
828 if (typeName === "Composite" || typeName === "Dictionary") {
829 return addOperationResponse({
830 ...parsedHeaders,
831 ..._response.parsedBody,
832 });
833 }
834 }
835
836 if (
837 bodyMapper ||
838 _response.request.method === "HEAD" ||
839 utils.isPrimitiveType(_response.parsedBody)
840 ) {
841 // primitive body types and HEAD booleans
842 return addOperationResponse({
843 ...parsedHeaders,
844 body: _response.parsedBody,
845 });
846 }
847
848 return addOperationResponse({
849 ...parsedHeaders,
850 ..._response.parsedBody,
851 });
852}