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.29k stars 6.44k forks source link

[BUG][CSHARP] DeepObject query params are not sent in the Api when additional properties are enable #19350

Open alfredo-accuris opened 1 month ago

alfredo-accuris commented 1 month ago

Bug Report Checklist

Description

Currently there is support of deepObjects on query params which produces parameters with square brackets. Example: Query param named fields can produce /endpoint?fields["key_1"]=value_1&fields["key_2"]=value2

When the query param is deepobject and additional properties is allowed, the generated code creates APIs that does nothing to the query param.

 - name: fields
          in: query
          description: Holds types of fields and its values
          style: deepObject
          explode: true
          schema:
            type: object
            additionalProperties:
              type: string
            description: Dictionary that holds the optional query filters of a request called fields. This dictionary is case insensitive.

Generated API

localVarRequestOptions.PathParameters.Add("userId", Sdk.Client.ClientUtils.ParameterToString(userId)); // path parameter
            if (fields != null)
            {
            }
Expected behavior

When the deepobject query-param with additional properties is found, the API generated code should be

if (fields != null)
{
    localVarRequestOptions.QueryParameters.Add(Sdk.Simple.Client.ClientUtils.ParameterToMultiMap("deepObject", "fields", fields));
}

Note: Support for deepObject is already there but when object only support "additional objects" it does nothing.

openapi-generator version

v7.7.0

OpenAPI declaration file content or url

Use the following swagger to generate the API code in csharp

openapi: 3.0.1
info:
  title: Testing single
  version: 0.0.1
paths:
  /single/session/{userId}:
    get:
      summary: Single endpoint session
      parameters:
        - name: fields
          in: query
          description: Holds types of fields and its values
          style: deepObject
          explode: true
          schema:
            type: object
            additionalProperties:
              type: string
            description: Dictionary that holds the optional query filters of a request called fields. This dictionary is case insensitive.
        - name: userId
          in: path
          description: The identity of the user.
          required: true
          schema:
            type: string

Complete swagger in: https://gist.github.com/alfredo-accuris/65e5961ff763e00bc7dd6718b192b8a9

Generation Details

docker run --rm openapitools/openapi-generator-cli:v7.7.0 generate -i ./deepobject-swagger.json --package-name Simple -g csharp -o ./Sdk/Simple --additional-properties targetFramework=net8.0

Steps to reproduce

Execute the cli with the specific swagger.

Related issues/PRs
Suggest a fix

Going through the code I can see on org.openapitools.codegen.DefaultCodegen that has a property loadDeepObjectIntoItems=true that always tries to load specify keys for deepobjects but if the variable is set to false, then the generated code works as expected generating:

if (fields != null)
{
    localVarRequestOptions.QueryParameters.Add(Sdk.Simple.Client.ClientUtils.ParameterToMultiMap("deepObject", "fields", fields));
}

I suggest make the loadDeepObjectIntoItems configurable to support both options.

alfredo-accuris commented 4 weeks ago

After some testing I found that the template only supports defined properties, but the code is capable to handle additional properties.

Case 1. If the deep object has only defined properties like foo, then the created api works as expected, generating the code to handle the foo variable.

 parameters:
    - name: fields
      in: query
      description: Holds types of fields and its values
      style: deepObject
      explode: true
      schema:
        type: object
        properties:
          foo:
            type: string
        description: Dictionary that holds the optional query filters of a request called fields. This dictionary is case insensitive.

Generated API code for query-params:

  if (fields != null)
  {
      if (fields.Foo != null)
      {
          localVarRequestOptions.QueryParameters.Add(Client.ClientUtils.ParameterToMultiMap("", "fields[foo]", fields.Foo));
      }
  }

Case 2. If the schema only supports additional properties without a defined property, the code generated is incorrect.

 parameters:
    - name: fields
      in: query
      description: Holds types of fields and its values
      style: deepObject
      explode: true
      schema:
        type: object
        additionalProperties:
          type: string
        description: Dictionary that holds the optional query filters of a request called fields. This dictionary is case insensitive.

Generated API code for query-params does not add additinal properties.

  if (fields != null)
  {
  }

Expected API code for query-params:

  if (fields != null)
  {
    localVarRequestOptions.QueryParameters.Add(Client.ClientUtils.ParameterToMultiMap("deepObject", "fields", fields));
  }

Case 3. If the schema support defined properties as well as additional property.

 parameters:
    - name: fields
      in: query
      description: Holds types of fields and its values
      style: deepObject
      explode: true
      schema:
        type: object
        additionalProperties:
          type: string
        properties:
          foo:
            type: string
        description: Dictionary that holds the optional query filters of a request called fields. This dictionary is case insensitive.

Generated API code for query-params is missing the additional properties.

   if (fields != null)
  {
      if (fields.Foo != null)
      {
          localVarRequestOptions.QueryParameters.Add(Client.ClientUtils.ParameterToMultiMap("", "fields[foo]", fields.Foo));
      }
  }

Expected API code for query-params: It pushes the defined properties but also the additional properties that may come in the dictionary the is created inside the model.

  if (fields != null)
  {
    if (fields.Foo != null)
    {
      localVarRequestOptions.QueryParameters.Add(ClientUtils.ParameterToMultiMap("", "fields[foo]", fields.Foo));
    }
     localVarRequestOptions.QueryParameters.Add(Client.ClientUtils.ParameterToMultiMap("deepObject", "fields", fields.AdditionalProperties));
  }

I found that this problem can be solved by changing the chsarp api.mustache template to handle the additional properties.

{{#isDeepObject}}
{{#items.hasVars}}
{{#items.vars}}
if ({{paramName}}.{{name}} != null)
{
    localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{paramName}}[{{#lambda.camelcase_sanitize_param}}{{name}}{{/lambda.camelcase_sanitize_param}}]", {{paramName}}.{{name}}));
}
{{/items.vars}}
{{#additionalProperties}}
localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{paramName}}", {{paramName}}.AdditionalProperties));
{{/additionalProperties}}
{{/items.hasVars}}
{{^items.hasVars}}
{{#additionalProperties}}
localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("deepObject", "{{paramName}}", {{paramName}}));
{{/additionalProperties}}
{{/items.hasVars}}
{{/isDeepObject}}
{{^isDeepObject}}
localVarRequestOptions.QueryParameters.Add({{packageName}}.Client.ClientUtils.ParameterToMultiMap("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
{{/isDeepObject}}