Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core-client] Clean up serviceClient.ts by moving code #13699

Merged
4 commits merged into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions sdk/core/core-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@

export { createSerializer, MapperTypeNames } from "./serializer";
export { createSpanFunction } from "./createSpan";
export {
ServiceClient,
ServiceClientOptions,
createClientPipeline,
ClientPipelineOptions
} from "./serviceClient";
export { ServiceClient, ServiceClientOptions } from "./serviceClient";
export { createClientPipeline, ClientPipelineOptions } from "./pipeline";
export {
OperationSpec,
OperationArguments,
Expand Down
57 changes: 57 additions & 0 deletions sdk/core/core-client/src/pipeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { TokenCredential } from "@azure/core-auth";
import {
InternalPipelineOptions,
Pipeline,
createPipelineFromOptions,
bearerTokenAuthenticationPolicy
} from "@azure/core-https";
import { deserializationPolicy, DeserializationPolicyOptions } from "./deserializationPolicy";
import { serializationPolicy, SerializationPolicyOptions } from "./serializationPolicy";

/**
* Options for creating a Pipeline to use with ServiceClient.
* Mostly for customizing the auth policy (if using token auth) or
* the deserialization options when using XML.
*/
export interface ClientPipelineOptions extends InternalPipelineOptions {
/**
* Options to customize bearerTokenAuthenticationPolicy.
*/
credentialOptions?: { credentialScopes: string | string[]; credential: TokenCredential };
/**
* Options to customize deserializationPolicy.
*/
deserializationOptions?: DeserializationPolicyOptions;
/**
* Options to customize serializationPolicy.
*/
serializationOptions?: SerializationPolicyOptions;
}

/**
* Creates a new Pipeline for use with a Service Client.
* Adds in deserializationPolicy by default.
* Also adds in bearerTokenAuthenticationPolicy if passed a TokenCredential.
* @param options - Options to customize the created pipeline.
*/
export function createClientPipeline(options: ClientPipelineOptions = {}): Pipeline {
const pipeline = createPipelineFromOptions(options ?? {});
if (options.credentialOptions) {
pipeline.addPolicy(
bearerTokenAuthenticationPolicy({
credential: options.credentialOptions.credential,
scopes: options.credentialOptions.credentialScopes
})
);
}

pipeline.addPolicy(serializationPolicy(options.serializationOptions), { phase: "Serialize" });
pipeline.addPolicy(deserializationPolicy(options.deserializationOptions), {
phase: "Deserialize"
});

return pipeline;
}
130 changes: 4 additions & 126 deletions sdk/core/core-client/src/serviceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,16 @@ import {
PipelineRequest,
PipelineResponse,
Pipeline,
createPipelineRequest,
createPipelineFromOptions,
bearerTokenAuthenticationPolicy,
InternalPipelineOptions
createPipelineRequest
} from "@azure/core-https";
import {
OperationArguments,
OperationSpec,
OperationRequest,
OperationResponseMap,
FullOperationResponse,
CompositeMapper,
XmlOptions
} from "./interfaces";
import { OperationArguments, OperationSpec, OperationRequest, XmlOptions } from "./interfaces";
import { getStreamingResponseStatusCodes } from "./interfaceHelpers";
import { getRequestUrl } from "./urlHelpers";
import { isPrimitiveType } from "./utils";
import { deserializationPolicy, DeserializationPolicyOptions } from "./deserializationPolicy";
import { flattenResponse } from "./utils";
import { URL } from "./url";
import { serializationPolicy, SerializationPolicyOptions } from "./serializationPolicy";
import { getCachedDefaultHttpsClient } from "./httpClientCache";
import { getOperationRequestInfo } from "./operationHelpers";
import { createClientPipeline } from "./pipeline";

/**
* Options to be provided while creating the client.
Expand Down Expand Up @@ -244,116 +232,6 @@ function createDefaultPipeline(
});
}

/**
* Options for creating a Pipeline to use with ServiceClient.
* Mostly for customizing the auth policy (if using token auth) or
* the deserialization options when using XML.
*/
export interface ClientPipelineOptions extends InternalPipelineOptions {
/**
* Options to customize bearerTokenAuthenticationPolicy.
*/
credentialOptions?: { credentialScopes: string | string[]; credential: TokenCredential };
/**
* Options to customize deserializationPolicy.
*/
deserializationOptions?: DeserializationPolicyOptions;
/**
* Options to customize serializationPolicy.
*/
serializationOptions?: SerializationPolicyOptions;
}

/**
* Creates a new Pipeline for use with a Service Client.
* Adds in deserializationPolicy by default.
* Also adds in bearerTokenAuthenticationPolicy if passed a TokenCredential.
* @param options - Options to customize the created pipeline.
*/
export function createClientPipeline(options: ClientPipelineOptions = {}): Pipeline {
const pipeline = createPipelineFromOptions(options ?? {});
if (options.credentialOptions) {
pipeline.addPolicy(
bearerTokenAuthenticationPolicy({
credential: options.credentialOptions.credential,
scopes: options.credentialOptions.credentialScopes
})
);
}

pipeline.addPolicy(serializationPolicy(options.serializationOptions), { phase: "Serialize" });
pipeline.addPolicy(deserializationPolicy(options.deserializationOptions), {
phase: "Deserialize"
});

return pipeline;
}

function flattenResponse(
fullResponse: FullOperationResponse,
responseSpec: OperationResponseMap | undefined
): unknown {
const parsedHeaders = fullResponse.parsedHeaders;
const bodyMapper = responseSpec && responseSpec.bodyMapper;

if (bodyMapper) {
const typeName = bodyMapper.type.name;
if (typeName === "Stream") {
return {
...parsedHeaders,
blobBody: fullResponse.blobBody,
readableStreamBody: fullResponse.readableStreamBody
};
}

const modelProperties =
(typeName === "Composite" && (bodyMapper as CompositeMapper).type.modelProperties) || {};
const isPageableResponse = Object.keys(modelProperties).some(
(k) => modelProperties[k].serializedName === ""
);
if (typeName === "Sequence" || isPageableResponse) {
const arrayResponse: { [key: string]: unknown } =
fullResponse.parsedBody ?? (([] as unknown) as { [key: string]: unknown });

for (const key of Object.keys(modelProperties)) {
if (modelProperties[key].serializedName) {
arrayResponse[key] = fullResponse.parsedBody?.[key];
}
}

if (parsedHeaders) {
for (const key of Object.keys(parsedHeaders)) {
arrayResponse[key] = parsedHeaders[key];
}
}
return arrayResponse;
}

if (typeName === "Composite" || typeName === "Dictionary") {
return {
...parsedHeaders,
...fullResponse.parsedBody
};
}
}

if (
bodyMapper ||
fullResponse.request.method === "HEAD" ||
isPrimitiveType(fullResponse.parsedBody)
) {
return {
...parsedHeaders,
body: fullResponse.parsedBody
};
}

return {
...parsedHeaders,
...fullResponse.parsedBody
};
}

function getCredentialScopes(options: ServiceClientOptions): string | string[] | undefined {
if (options.credentialScopes) {
const scopes = options.credentialScopes;
Expand Down
75 changes: 75 additions & 0 deletions sdk/core/core-client/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { CompositeMapper, FullOperationResponse, OperationResponseMap } from "./interfaces";

/**
* Returns true if the given value is a basic/primitive type
* (string, number, boolean, null, undefined).
Expand Down Expand Up @@ -34,3 +36,76 @@ const validUuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F
export function isValidUuid(uuid: string): boolean {
return validUuidRegex.test(uuid);
}

/**
* Take a `FullOperationResponse` and turn it into a flat
* response object to hand back to the consumer.
* @param fullResponse - The processed response from the operation request
* @param responseSpec - The response map from the OperationSpec
*
* @internal
*/
export function flattenResponse(
fullResponse: FullOperationResponse,
responseSpec: OperationResponseMap | undefined
): unknown {
const parsedHeaders = fullResponse.parsedHeaders;
const bodyMapper = responseSpec && responseSpec.bodyMapper;

if (bodyMapper) {
const typeName = bodyMapper.type.name;
if (typeName === "Stream") {
return {
...parsedHeaders,
blobBody: fullResponse.blobBody,
readableStreamBody: fullResponse.readableStreamBody
};
}

const modelProperties =
(typeName === "Composite" && (bodyMapper as CompositeMapper).type.modelProperties) || {};
const isPageableResponse = Object.keys(modelProperties).some(
(k) => modelProperties[k].serializedName === ""
);
if (typeName === "Sequence" || isPageableResponse) {
const arrayResponse: { [key: string]: unknown } =
fullResponse.parsedBody ?? (([] as unknown) as { [key: string]: unknown });

for (const key of Object.keys(modelProperties)) {
if (modelProperties[key].serializedName) {
arrayResponse[key] = fullResponse.parsedBody?.[key];
}
}

if (parsedHeaders) {
for (const key of Object.keys(parsedHeaders)) {
arrayResponse[key] = parsedHeaders[key];
}
}
return arrayResponse;
}

if (typeName === "Composite" || typeName === "Dictionary") {
return {
...parsedHeaders,
...fullResponse.parsedBody
};
}
}

if (
bodyMapper ||
fullResponse.request.method === "HEAD" ||
isPrimitiveType(fullResponse.parsedBody)
) {
return {
...parsedHeaders,
body: fullResponse.parsedBody
};
}

return {
...parsedHeaders,
...fullResponse.parsedBody
};
}