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

Provide design for PATCH operation models from TypeSpec #2492

Closed
annelo-msft opened this issue Jul 26, 2022 · 22 comments
Closed

Provide design for PATCH operation models from TypeSpec #2492

annelo-msft opened this issue Jul 26, 2022 · 22 comments
Assignees
Labels
Can-Customize Client This issue points to a problem in the data-plane of the library. DPG/RLC v2.1 Post Gallium work DPG Epic: Model Generation https://github.com/Azure/cadl-azure/issues/1944 GA-Required v3 Version 3 of AutoRest C# generator. WS: Code Generation

Comments

@annelo-msft
Copy link
Member

annelo-msft commented Jul 26, 2022

The August MVP target service has PATCH operations. We need to determine whether we will provide special model support for PATCH operations.

This may have already been decided in Azure/azure-sdk#3520 (comment).
There is a relevant document from @JeffreyRichter as well.

@annelo-msft annelo-msft added v3 Version 3 of AutoRest C# generator. Client This issue points to a problem in the data-plane of the library. DPG/RLC v2.0b1 DPG/RLC August MVP labels Jul 26, 2022
@annelo-msft annelo-msft self-assigned this Jul 26, 2022
@annelo-msft annelo-msft changed the title Provide design for PATCH operation models Provide design for PATCH operation models from Cadl Jul 26, 2022
@lmazuel lmazuel added the DPG label Jul 29, 2022
@annelo-msft
Copy link
Member Author

  • Can we have models that are used in both create and update, but that allow you to set the property for create but not for update?

@JeffreyRichter
Copy link
Member

JeffreyRichter commented Aug 4, 2022 via email

@lirenhe
Copy link
Member

lirenhe commented Oct 12, 2022

CADL is defining the merge-patch body in https://github.com/Azure/cadl-azure/blob/main/packages/cadl-azure-core/lib/operations.cadl#L49-L64

Foundations.ResourceCreateOrUpdateModel applies to the resource type, inside the definition, CADL remove properties with read visibility and make all the other required properties as optional.

https://github.com/Azure/cadl-azure/blob/main/packages/cadl-azure-core/lib/foundations.cadl#L123-L125

TypeScript plan to generate the model type as

interface TResourceMergeAndPatch = Partial<Exclude<TResource, "">>;
interface TResourcePatch = Exclude<TResource, "">;

We shall decide how we could handle this case in dotnet.

@lirenhe
Copy link
Member

lirenhe commented Feb 3, 2023

Move out of GA as this is not high priority for now

@chunyu3
Copy link
Member

chunyu3 commented May 17, 2023

We will use DynamicData to implement patch operation and operation with merge-patch content-type. DynamicData will be supported in Azure.Core end of June (maybe). We will delay supporting patch operation and merge-patch until DynamicData is released in Azure.Core.

@annelo-msft
Copy link
Member Author

@chunyu3, what is your timeline for needing to support this? We intend to release DynamicData in the next Core release, but it will not have patch support in that release.

@lirenhe
Copy link
Member

lirenhe commented Jun 9, 2023

I found a couple of patch operations in another service (LoadTest): https://github.com/Azure/azure-rest-api-specs/blob/feature/loadtesting/specification/loadtestservice/routes.tsp

Though we could still generate protocal methods for the user but that may impact the usablilty, as this is a missing feature, I hope the Patch support could be ready ASAP.

cc @mikekistler @m-nash

@lirenhe
Copy link
Member

lirenhe commented Jun 12, 2023

ContentSafety also contains the patch API. Currently, we only generate protocal methods for them.

#3344

@m-nash
Copy link
Member

m-nash commented Jun 13, 2023

@lirenhe can you write up what code a customer would have to do for various patching scenarios to work with protocol methods? I think this would help everyone understand our current state and how good or bad it currently is.

@lirenhe
Copy link
Member

lirenhe commented Jun 14, 2023

@lirenhe can you write up what code a customer would have to do for various patching scenarios to work with protocol methods? I think this would help everyone understand our current state and how good or bad it currently is.

I am not the expert in this area, but below are what are service teams shows how to use the protocol patch operation:

https://github.com/Azure/azure-sdk-for-net/blob/8962d4f567cc7acc60cda3308766cdfef2e69b8c/sdk/contentsafety/Azure.AI.ContentSafety/tests/Samples/Sample3_ManageBlocklist.cs#L34

https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/loadtestservice/Azure.Developer.LoadTesting/samples/Sample3_CreateOrUpdateAppComponent.md

@annelo-msft
Copy link
Member Author

Thank you for these, @lirenhe!

Since DynamicData has released, I'd like to start sketching out this design now. It would be good for me to get a holistic understanding of how PATCH operations show up in our generated libraries.

  • Are these two libraries - Azure.AI.ContentSafety and Azure.Developer.LoadTesting the only two we're currently tracking that have PATCH operations, or are there others?
  • Are they primarily used for CreateOrUpdate operations, or do they show up in any other ways?
  • Is our goal to generate convenience methods for these, or is the expectation that they will continue to be protocol-only?

@lirenhe
Copy link
Member

lirenhe commented Jun 20, 2023

Thank you for these, @lirenhe!

Since DynamicData has released, I'd like to start sketching out this design now. It would be good for me to get a holistic understanding of how PATCH operations show up in our generated libraries.

  • Are these two libraries - Azure.AI.ContentSafety and Azure.Developer.LoadTesting the only two we're currently tracking that have PATCH operations, or are there others?
  • Are they primarily used for CreateOrUpdate operations, or do they show up in any other ways?
  • Is our goal to generate convenience methods for these, or is the expectation that they will continue to be protocol-only?

Thanks @annelo-msft to follow up on this.

  1. Yes, those are the 2 libraries we found that use PATCH operations. I am not aware of other service using this now.

  2. As I know, they are used only in CreateOrUpdate operations, so I think we could just support 'application/merge-patch+json'.
    Not sure whether we should also support 'application/json-patch+json' today. @m-nash?

  3. Our goal is to generate convenience methods for PATCH operation. Today, we asked service team to use protocol and do not do any customization mainly because we don't know how the convenience method would look like.

@mikekistler
Copy link
Member

The API Stewardship Board discourages 'application/json-patch+json'. There are some teams that support it so it should be "possible" but not "easy" -- so that teams must make a conscious choice to use it.

+1 on convenience methods for PATCH.

@pshao25
Copy link
Member

pshao25 commented Jul 31, 2023

Scenario 1: normal patch operation with all the properties from required to optional

model Test {
  required: string;
  optional?: string;
}
@patch op method(@body body: Test): void;

Expected: we could send any combination of properties like:

var test = new Test();
test.Optional = "a"; // Not set required property
var response = someClass.Method(test); // OK.

Scenario 2: Same model in different places

model Test {
  required: string;
  optional?: string;
}
@patch op patch(@body body: Test): Test;
@post op post(@body body: Test): void;

Expected:

var response = someClass.patch(new Test() {Optional = "a"}); // The input is OK. But output if return `{optional: "b"}` should fail because it should contain `required`
var response = someClass.post(new Test() {Optional = "a"}); // Should fail because it should contain `required`

@annelo-msft
Copy link
Member Author

annelo-msft commented Jul 31, 2023

Thanks for this use-case, @pshao25! Can you help me understand what a corresponding API is in an existing service library? It will help me understand the use case better if I can understand where a service is using this.

Another piece of information that would be helpful for the design is the intended use case for such a model.

For example, in the above, what is the user doing on a POST vs. on a PATCH? Is POST creation of a new model and PATCH is an update? Can PATCH be used to create a new instance of the resource on the service, or not?

@pshao25
Copy link
Member

pshao25 commented Aug 1, 2023

@annelo-msft This is an example that same model TextBlocklist is used in both get and patch. So 1) when calling getTextBlocklist, if response payload does not contain required property of TextBlocklist, call should fail, 2) when calling createOrUpdateTextBlocklist, every properties in TextBlocklist could leave empty because it is patch.

For model in both post and patch, I didn't find a real case. But I think it makes sense that POST is creation of a new model and PATCH is an update. Or we could think this way, if you think same model shouldn't be in both of post and patch, then compiler should block this, at least linter should block this. If we don't block this, then it means this scenario makes sense.

@annelo-msft
Copy link
Member Author

That is a great example, thank you @pshao25! I will try to get back to you soon with a proposal for how we can address it.

@annelo-msft
Copy link
Member Author

annelo-msft commented Aug 2, 2023

Hi @pshao25, I have a clarifying question - I am looking at the model definition for TextBlocklist in the models file here: https://github.com/Azure/azure-rest-api-specs/blob/14d24d17491d8c2bde24532cb8cc2d663c0ffd9f/specification/cognitiveservices/ContentSafety/models.tsp#L134-L147

How can you tell that a property of the model is required for GET and not for PATCH?

From their REST API documents, it looks like blocklistName is needed by URI, so should be taken by the model constructor to create the request, but not be written to the body by the serialize operation. Specifically, I am suggesting that the client's service methods would have the following signatures:

public virtual Response<TextBlocklist> CreateOrUpdateTextBlocklist(TextBlocklist blocklist, CancellationToken cancellationToken = default);
public virtual Response CreateOrUpdateTextBlocklist(string blocklistName, RequestContent content, RequestContext context = null);
public virtual Task<Response<TextBlocklist>> CreateOrUpdateTextBlocklistAsync(TextBlocklist blocklist, CancellationToken cancellationToken = default);
public virtual Task<Response> CreateOrUpdateTextBlocklistAsync(string blocklistName, RequestContent content, RequestContext context = null);

Do you know if there are any cases where we have required properties that aren't used in the URI? I would like to understand these to make sure our design accounts for those. I think it is possible, we just need to understand the different cases to call out in the generator.

Update: I added an example of this to the proposal PR, here.

This illustrates the case where a property is not required (or allowed?) in the Merge Patch request body, but is needed by the model to format the request URI, so should be required in the model constructor. The important piece here is that properties that shouldn't appear in the Merge Patch JSON need to be in the initial JSON that is parsed to create the MutableJsonElement. If they are added after the initial creation, they will go on the changelist and be written out in the WritePatch operation.

@pshao25
Copy link
Member

pshao25 commented Aug 3, 2023

@annelo-msft I'm not sure if that is the correct signature. I'm contacting typespec team to figure out some expected behavior about this scenario. Anyway the signature is not my emphasis in this issue.

How can you tell that a property of the model is required for GET and not for PATCH?

Because when a model is used in a PATCH operation, all of its properties automatically become optional, right? You could set any part of them and leave any required property unset. That is PATCH!

Do you know if there are any cases where we have required properties that aren't used in the URI?

I haven't find one. Once I got one, I'll let you know. But I believe it is very easy to understand this case. For example,

@doc("Text Blocklist.")
@resource("text/blocklists")
model TextBlocklist {
  @key("blocklistName")
  blocklistName: string;
  description?: string;
  count: int;
}
createOrUpdateTextBlocklist is ResourceCreateOrUpdate<TextBlocklist>;

The added count could leave empty in a PATCH body like {"description": "d"} so that only description is updated. Could you update your example with this "though made up, very common I think" model?

And I just realized in this case we cannot set count to null because a required property cannot be deleted.

@annelo-msft
Copy link
Member Author

annelo-msft commented Aug 4, 2023

You could set any part of them and leave any required property unset.

If a property is needed to format the request URI, then I believe it needs to be required even on a PATCH model.

Do you know if there are any cases where we have required properties that aren't used in the URI?
I haven't find one.

I would be interested to know this. I can imagine that there are cases where some properties are required for GET and not for PATCH, for example a creation time, or some value set by the service that the user is intended to read and not update. For these, we would make these read-only properties on the model, so the user cannot modify them for a PATCH operation. For GET operations, they would be populated by the internal serialization constructor, not a public constructor that the end-user has access to.

In general, that would be the pattern, and I think it works for all pairs of GET/PATCH operations, by the following logic:

  • If a model is an output model (e.g. returned from a GET operation), it has an internal serialization constructor
  • If a model is an input model (e.g. taken as input to PATCH), it can have a public constructor (it doesn't have to, but it can)
  • If an input model has a public constructor, this must take all fields required to create the create/update/replace request
    • If a required field isn't allowed in the request body, it should be parsed when the initial JSON element is created
    • If a required field is required for Merge Patch body, the model constructor must add it to the change list
  • Properties that are required for the output model but not by the input model are read-only properties

I think this will only run into problems if where there are multiple input operations using the same model, and where those input operations have different (required/optional) or (read-only/read-write) for the same property. If we run across cases where this is happening, it would be good to understand why they exist -- i.e. what specific scenarios they are addressing -- so we can make sure our model design reflects the user intent.

@chunyu3
Copy link
Member

chunyu3 commented Sep 21, 2023

duplicate with #3648

@lirenhe lirenhe changed the title Provide design for PATCH operation models from Cadl Provide design for PATCH operation models from TypeSpec Jan 22, 2024
@pshao25
Copy link
Member

pshao25 commented May 9, 2024

We will track in #3648

@pshao25 pshao25 closed this as not planned Won't fix, can't repro, duplicate, stale May 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Can-Customize Client This issue points to a problem in the data-plane of the library. DPG/RLC v2.1 Post Gallium work DPG Epic: Model Generation https://github.com/Azure/cadl-azure/issues/1944 GA-Required v3 Version 3 of AutoRest C# generator. WS: Code Generation
Projects
None yet
Development

No branches or pull requests

8 participants