OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.97k stars 6.59k forks source link

[REQ] [csharp] [generichost] Support query parameters with style: deepObject and explode: true #19970

Open leorg99 opened 1 month ago

leorg99 commented 1 month ago

Is your feature request related to a problem? Please describe.

The OpenAPI spec allows you to define parameters that can be rendered as color[R]=100&color[G]=200&color[B]=150.

This can be written in the OpenApi yaml file as follows:

parameters:
  - name: color
    in: query
    style: deepObject
    explode: true
    schema:
      title: ColorParameter
      type: object
      properties:
        key:
          type: string
        value:
          type: string

This is related to the issue in #19893

Describe the solution you'd like

To implement this, we need to modify several files.

Dealing with explode

First, we need to fix the signature of parameters with 'explode: true'. The spec states that when this is true:

Parameter values of type array or object generate separate parameters for each value of the array or key-value pair of the map.

For a query parameter with name: color, this would look like

/foo&color=blue&color=black&color=orange

Since the parameter can be used 0 or more times, we need to change the parameter to a collection type (like List<T>). This can be done by modifying Operation.Signature.mustache to add a {{#isExplode}} ... {{/isExplode}} section.

Next, we have to modify the following to add all the values in the List<T> parameter to NameValueCollection https://github.com/OpenAPITools/openapi-generator/blob/008c1a42ef39100b38a86fc67344ea44d4f32a8f/modules/openapi-generator/src/main/resources/csharp/libraries/generichost/api.mustache#L408-L443

Support deepObject style

This along with explode:true allows us to specify query parameters as someParam[field1]=value1&someParam[field2]=value2&someParam[field3]=value3

Since the parameter defines a schema with type: object and properties, the generator generates a model to represent this type. For deepObject styles, it only makes sense to define two properties: the field or column and the value. I think what we can do here is then generate a model that has

// from parameter.name
public string Name {get; set;}

// from parameter.schema.properties[0]
public string Column {get; set;}  // or Field

// from parameter.schema.properties[1]
public string Value {get; set;}

Then we can generate the query parameter as Name[Column]=Value

Building on top of the changes we made previously,

Describe alternatives you've considered

Additional context

leorg99 commented 1 month ago

@devhl-labs

As it relates to #19893, I wrote a small script to transformed the filter* query parameters to style: deepObject , explode: true, and replaced the schema to have two properties.

output

using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Readers;
using Microsoft.OpenApi.Writers;

OpenApiDocument document;

using (var httpClient = new HttpClient())
{
    var stream = await httpClient.GetStreamAsync("https://app.files.com/api/rest/v1/swagger_doc.json");
    var reader = new OpenApiStreamReader();
    document = reader.Read(stream, out var diagnostic);
}

foreach (var (path, item) in document.Paths)
{
    foreach (var (operationType, operation) in item.Operations)
    {
        for (var i = 0; i < operation.Parameters.Count; i++)
        {
            var parameter = operation.Parameters[i];

            if (parameter.Name.StartsWith("filter"))
            {
                parameter.Style = ParameterStyle.DeepObject;
                parameter.Explode = true;
                parameter.Schema = new OpenApiSchema
                {
                    Title = "FilterParameter",
                    Type = "object",
                    Properties = new Dictionary<string, OpenApiSchema>()
                    {
                        ["field"] = new OpenApiSchema() { Type = "string" },
                        ["value"] = new OpenApiSchema() { Type = "string" }
                    }
                };
            }
        }
    }

    // Serialize and save the modified OpenAPI document to a new file
    await using var streamWriter = new StreamWriter("updated.yaml");
    var writer = new OpenApiYamlWriter(streamWriter);
    document.SerializeAsV3(writer);
}