Skip to content

[core-client] Updated ServiceClient implementation to work with core-https#9854

Merged
xirzec merged 20 commits intoAzure:masterfrom
xirzec:newServiceClient
Jul 27, 2020
Merged

[core-client] Updated ServiceClient implementation to work with core-https#9854
xirzec merged 20 commits intoAzure:masterfrom
xirzec:newServiceClient

Conversation

@xirzec
Copy link
Member

@xirzec xirzec commented Jul 2, 2020

This PR accomplishes most of the work of lifting the old ServiceClient implementation (and dependent code) out of core-http and updates it to work with core-https.

The only thing not yet supported is XML, because I need to create another (very small) package to provide the helpers and pull in the heavy XML dependency.

Otherwise, this should be in a good enough state for us to start updating AutoRest.TypeScript to work with it.

I cleaned up the serialization/deserialization code a bit, but I'm sure there are much more opportunities to do make it even cleaner. Please feel free to suggest whatever you think would be an improvement towards readability / simplicity.

I think I've looked at it too long at this point to be able to think of non-breaking cleanups. 😅

Also remember that the eventual goal is to deprecate most of this code so that we can have codegen directly compose custom serializers.

Fixes #8615

@xirzec xirzec added Client This issue points to a problem in the data-plane of the library. Azure.Core labels Jul 2, 2020
@xirzec xirzec requested a review from ramya-rao-a as a code owner July 2, 2020 01:05
@xirzec xirzec self-assigned this Jul 2, 2020
Copy link
Contributor

@chradek chradek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I like the direction this is going in! One question I had was why isn't serialization in it's own policy? I liked that deserialization was moved into one.

// Licensed under the MIT license.

// eslint-disable-next-line @azure/azure-sdk/ts-no-namespaces
declare global {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@richardpark-msft could have used this recently 😉

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I wonder whether these could be useful for other libraries too. I've seen similar functions storage (for testing though). On the other hand they are small so probably fine to have copies.

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/// <reference lib="dom" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remind me because I'm fuzzy...this let's us reference DOM types as core-client developers right? But it doesn't actually pull in DOM for external consumers of this library? Does this make it so consumers don't have to specify DOM themselves as well when using this library?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this from core-http because I was getting annoying compile errors, but I think it won't require external users to import that lib since they're not compiling our source and we don't re-export types that are part of dom.

It just is supposed to give us typing for things that are from dom when we feature detect them (e.g. in serializer.ts when it checks if Blob exists.)

/cc @willmtemple who knows a thing or two because he's seen a thing or two

PipelineRequest
} from "@azure/core-https";

export interface OperationRequest extends PipelineRequest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Missing docs on OperationRequest.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a great comment because it made me realize that extending the request this way didn't work great with clone() on the request. I fixed that bug by moving these props into a grab-bag option that clone() can persist!

*/
export type QueryCollectionFormat = "CSV" | "SSV" | "TSV" | "Pipes" | "Multi";

export type ParameterPath = string | string[] | { [propertyName: string]: ParameterPath };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: missing docs

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

// import { assert } from "chai";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a tease...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, this gets tested indirectly through the service client tests, though it might be useful to add more tests specifically later.

Copy link
Member

@joheredi joheredi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks great! I really like how much more readable this is 😄


// @public
export const MapperTypeNames: {
readonly Base64Url: "Base64Url";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sorting these 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take credit but really I just replaced a crazy utility function that made a fake enum with a simple const object. 😅

if (operationSpec.isXML && responseSpec.bodyMapper.type.name === MapperTypeNames.Sequence) {
valueToDeserialize =
typeof valueToDeserialize === "object"
? valueToDeserialize[responseSpec.bodyMapper.xmlElementName!]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to validate that xmlElementName exists instead of ! it? We may be able to provide a better error message if we check for it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tweaked this logic slightly and had it fallback to the empty array when the element name doesn't exist.

* @param credential The credentials used for authentication with the service.
* @param options The service client options that govern the behavior of the client.
*/
constructor(options: ServiceClientOptions = {}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the new constructor!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried very hard to get rid of the old cruft. Do you know if we can ditch ServiceClientCredentials yet?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppConfiguration library still uses it. Not sure why their AppConfigurationCredential is not used via a request policy though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think we can move this to using custom policies with AzureKeyCredential

const expectedStatusCodes = Object.keys(operationSpec.responses);
const hasNoExpectedStatusCodes =
expectedStatusCodes.length === 0 ||
(expectedStatusCodes.length === 1 && expectedStatusCodes[0] === "default");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note, and probably doesn't make sense to add support until we switch to the new way of serializing/deserializing.

Autorest supports x-ms-error-response which allows users to mark responses as errors in Swagger and still be able to provide alternate deserialization rules to "default", currently we'd just see those as just another success response.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this logic is terrible and unchanged from the old stuff. I'm fine adding support here, but maybe you can file an issue to do it in a follow-up PR that is more scoped to the behavior change?

@xirzec
Copy link
Member Author

xirzec commented Jul 10, 2020

Overall I like the direction this is going in! One question I had was why isn't serialization in it's own policy? I liked that deserialization was moved into one.

deserialization was its own policy before in core-http. The problem with serialization is that serviceClients call sendOperationRequest which does all the serialization and makes the pipeline request at which point there's nothing for a policy to do.

@chradek
Copy link
Contributor

chradek commented Jul 13, 2020

deserialization was its own policy before in core-http. The problem with serialization is that serviceClients call sendOperationRequest which does all the serialization and makes the pipeline request at which point there's nothing for a policy to do.

Is this something we can change in a future PR then, if not in this one? (Is there already an issue to track doing this?) It seems weird that we'd allow deserialization to be replaceable, but not serialization. We also have examples of APIs today that serialize very differently based on the content-type of the body (e.g. binary versus text) and it'd be convenient if in the code generator we could decide which serialization to apply without having to make changes to this library to support this use case.

@xirzec
Copy link
Member Author

xirzec commented Jul 13, 2020

Is this something we can change in a future PR then, if not in this one? (Is there already an issue to track doing this?) It seems weird that we'd allow deserialization to be replaceable, but not serialization. We also have examples of APIs today that serialize very differently based on the content-type of the body (e.g. binary versus text) and it'd be convenient if in the code generator we could decide which serialization to apply without having to make changes to this library to support this use case.

My understanding is that future codgen will create custom mappers/deserializers using functional composition, so this layer will really just be "process the string form into an object model and call the appropriate function".

I think we could take the approach the other way too, so instead of building the request from an OperationSpec, we would have a way to pack the raw inputs (operation args) into headers/url query params/body contents.

If we are able to separate out the logic correctly, the actual code we need to keep in the policies/service client will be pretty minimal.

/cc @joheredi for his thoughts.

@xirzec xirzec merged commit a5fda32 into Azure:master Jul 27, 2020
@xirzec xirzec deleted the newServiceClient branch July 27, 2020 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Azure.Core Client This issue points to a problem in the data-plane of the library.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[core-http] Separate out autorest client and mappers into separate package

4 participants