per operation Response types for generated clients#1040
per operation Response types for generated clients#1040cataggar merged 22 commits intoAzure:mainfrom
Response types for generated clients#1040Conversation
|
Can you discuss why this change is needed? It would be helpful to understand the need, as this is increasing the amount of boiler plate developers will need to write. Consider the existing Application Insights example: let response = client.query_client().execute(app_id, body).into_future().await?;With this change, the example looks like this: let query_results = client.query_client().execute(app_id, body).into_future().await?.into_body().await?; |
|
Thanks for the feedback @bmc-msft. I added a let response = client.query_client().execute(app_id, body).into_body().await?;The |
There was a problem hiding this comment.
This PR contains a million-line diff, which is incredibly hard to review. Can you walk us through the API changes you're proposing here? I'd like to be able to understand the proposed API design and the motivation for it.
As we discussed in the meeting on Tuesday, there are several ways which would make it easier to talk about API design changes:
- Instead of generating the changes for all files, generate a diff for a single file, or possibly even a single SDK. This allows us to zoom in on a single set of changes and discuss them in detail.
- Alternatively: instead of proposing the API changes for the auto-generated SDKs, file a PR to make the changes to the manually authored SDKs first. It makes sense to not go down this path if you're not comfortable with the manually authored SDKs.
- Author a document or PR description outlining the changes you're making with clear descriptions of the problems and proposed solutions. I would say this is a requirement for API changes, as the auto-generated and manually-authored crates should maintain similar user-facing APIs.
As @rylev mentioned on Tuesday: it's fine for the manually-authored and automatically generated SDKs to deviate in their APIs. But this should be the result of a conscious decision making process, and not because we simply didn't communicate about the design.
Thanks!
@yoshuawuyts, I opened up #1043 to make is easier to zoom in on a single set of changes for the strorage
The main difference is the SDKs combine data from the response headers and the response bodies for their responses. The generated rest clients allow type-safe access to individual response headers and response body. In #1039, the internal static AccountInfo ToAccountInfo(this ResponseWithHeaders<ServiceGetAccountInfoHeaders> response)
{
if (response == null)
{
return null;
}
return new AccountInfo
{
SkuName = response.Headers.SkuName.GetValueOrDefault(),
AccountKind = response.Headers.AccountKind.GetValueOrDefault(),
IsHierarchicalNamespaceEnabled = response.Headers.IsHierarchicalNamespaceEnabled.GetValueOrDefault()
};
} |
| @@ -24,7 +24,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | |||
|
|
|||
| let mut publishers = client | |||
| .list_publishers(&location, &subscription_id) | |||
| .into_future() | |||
| .into_body() | |||
There was a problem hiding this comment.
The convenience function that sends the request and returns the body is now into_body.
There was a problem hiding this comment.
Why is into_body necessary (into_future is temporary until std::future::IntoFuture arrives on stable - it will soon go away). Why would there be a send and an into_body? Why not always just return the Response type?
There was a problem hiding this comment.
Operations like Service_GetAccountInfo do not have response bodies. All of the retuned data is in the response headers, which are not currently accessible. The plan is make them available, probably via Response::headers(&self) which follows the AutoRest C# design. I'm not sure what into_future should map to and would prefer to add it back once we figure that out.
There was a problem hiding this comment.
When cadl replaces openapi for the specifications, it will be possible to model the responses the way we with them to be in the SDKs. I created an example for operation in #1043 (comment) .
| @@ -17,7 +17,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | |||
| let subscription_id = AzureCliCredential::get_subscription()?; | |||
| let client = azure_mgmt_network::Client::builder(credential).build(); | |||
|
|
|||
| let response = client.service_tags_client().list(location, subscription_id).into_future().await?; | |||
| let response = client.service_tags_client().list(location, subscription_id).into_body().await?; | |||
|
@rylev, I provided an overview and switched back to |
Responses for generated clientsResponse types for generated clients
bmc-msft
left a comment
There was a problem hiding this comment.
This requires the user know where the response data comes from, either from the header or body, which seems sub-optimal. The data representation from the HTTP response should be inconsequential to the API consumer.
If we consider some of the other SDKs, this is handled in a handful of different ways.
- Golang: appears to provide the parsed response
https://github.com/Azure-Samples/azure-sdk-for-go-samples/blob/d9f41170eaf6958209047f42c8ae4d0536577422/services/keyvault/examples/go-keyvault-msi-example.go#L82-L89 - Python: if you provide the argument
raw, returns the response with the expectation that the user deserializes it, otherwise the consumer gets a fully contextualized response. - Javascript: Users register a callback where you are provided the deserialized response or the base response stream.
It appears the primary concern would be addressed if we provided either the contextualized/parsed response or a raw version. Can we do something akin to std::str::parse, and rely on type inference (or turbofishing) to know which response the user wants?
That is true for SDKs, but not the generated clients. AutoRest clients make this distinction. See
The generated GetSecret is: // GetSecret - The GET operation is applicable to any secret stored in Azure Key Vault. This operation requires the secrets/get
// permission.
// If the operation fails it returns an *azcore.ResponseError type.
// Generated from API version 7.3
// name - The name of the secret.
// version - The version of the secret. This URI fragment is optional. If not specified, the latest version of the secret
// is returned.
// options - GetSecretOptions contains the optional parameters for the Client.GetSecret method.
func (client *Client) GetSecret(ctx context.Context, name string, version string, options *GetSecretOptions) (GetSecretResponse, error) {
req, err := client.getSecretCreateRequest(ctx, name, version, options)
if err != nil {
return GetSecretResponse{}, err
}
resp, err := client.pl.Do(req)
if err != nil {
return GetSecretResponse{}, err
}
if !runtime.HasStatusCode(resp, http.StatusOK) {
return GetSecretResponse{}, runtime.NewResponseError(resp)
}
return client.getSecretHandleResponse(resp)
}It returns a response type named // GetSecretResponse contains the response from method Client.GetSecret.
type GetSecretResponse struct {
SecretBundle
}The For these Rust operation Response types. The intension it to provide |
|
Looking at generated JavaScript clients, it looks like primary way to respond is with a custom response that contains both the parsed body and parsed headers. Using getStatistics as an example: function getStatistics(options?: ServiceGetStatisticsOptions): Promise<ServiceGetStatisticsResponse>
type ServiceGetStatisticsResponse = ServiceGetStatisticsHeaders &
BlobServiceStatistics & {
_response: HttpResponse & {
bodyAsText: string,
parsedBody: BlobServiceStatistics,
parsedHeaders: ServiceGetStatisticsHeaders,
},
}We could provide something similar from |
Design with Parsed ResponseHere is a proposed design that provides for parsed responses similar to the generated JavaScript clients. Each operation would have the following: If we go with this design, it can be broken up into multiple pull requests. This pull request would change to |
|
I converted this to a draft while we come to agreement on the proposed design in #1053. |
|
I recognize this is a work-in-progress, but is a step backwards in terms of usability as it stands right now. Currently, nearly all of the published generated crates are broken (fixed in #1020). Can an update be published out before merging this work? |
I left RequestBulider::into_body in place to prevent it being a step backwards. I intend to add the other features soon. I just need to get unblocked on this review.
Sure. We should come to an agreement before the September release in a week or two. |
While I have concerns with this PR as is, I understand it's a work in progress.
This implements the first part of #1053 design.