Azure / azure-sdk-for-net

This repository is for active development of the Azure SDK for .NET. For consumers of the SDK we recommend visiting our public developer docs at https://learn.microsoft.com/dotnet/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-net.
MIT License
5.17k stars 4.53k forks source link

Core: PATCH | Support convenience layer and models for RFC7396 (application/merge-patch+json) operations #42094

Open pshao25 opened 4 months ago

pshao25 commented 4 months ago

Scenario

For the following tsp spec

model PatchModel {
  ... // some properties
}

@patch
op patch(@header contentType: "application/merge-patch+json", @body body: PatchModel): PatchModel;

Currently we only provide protocol layer for operations with verb patch and don't generate models.

We are going to add

  1. Convenience layer
  2. Model and its corresponding serialization

to such patch operations with content type "application/merge-patch+json". For other patch operations not following RFC7396 is out of the scope.

Spec Interpretation

  1. An input model in patch operation could also be in output model and other operations with different verbs. But we just generate the models defined in the spec, i.e. we don't generate special magic models for patch operation even when the model is in both patch operation and other operations.
  2. An input model for patch operation could contain both required properties and optional properties: a. Optional property property?: type should be taken as if it has | null, i.e. we could send null when serializing the explicitly optional property. If user defines property?: type| null, we should throw error. b. Required property property: type should be taken as if it is property?: type, i.e. we could omit it over the wire, but it cannot be null. c. For value of an optional array type property?: type[], its value is still not nullable, i.e. we cannot do model.Property[0] = null; d. For value of an optional dictionary type property?: Record<type>, its value is nullable, i.e. we can do model.Property["a"] = null;

Convenience layer

The expected convenience layer for the above spec is like

public virtual async Task<PatchModel> PatchAsync(PatchModel body, CancellationToken cancellationToken = default)
{
    using RequestContent content = RequestContent.Create(body, new ModelReaderWriterOptions("JMP"));
    ...    
}

We are going to call above convert function to create RequestContent.

Serialization

We want to make sure we could do patch serialization not only from RequestContent.Create but also from IPersistableModel.Write. So the serialization code for model would be adjusted to

BinaryData IPersistableModel.Write(ModelReaderWriterOptions options)
{
    var format = options.Format == "W" || options.Format == "JMP" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format;

    switch (format)
    {
        case "J":
            return ModelReaderWriter.Write(this, options);
        default:
            throw new FormatException($"The model {nameof(ResourcePatch)} does not support '{options.Format}' format.");
    }
}

void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
{
    var format = options.Format == "W" || options.Format == "JMP" ? ((IPersistableModel)this).GetFormatFromOptions(options) : options.Format;
    if (format != "J")
    {
        throw new FormatException($"The model {nameof(ResourcePatch)} does not support '{format}' format.");
    }

    // maybe has other formats 
    if (options.Format == "W")
    {
        WriteJson(writer, options);
    }
    else if (options.Format == "JMP")
    {
        WritePatch(writer);
    }
}
annelo-msft commented 3 months ago

Possible duplicate of https://github.com/Azure/azure-sdk-for-net/issues/27402