OpenAPITools / OpenAPITools/openapi-generator

[BUG] csharp generichost issue with generated sort and filter query parameters

Open
#19,893 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Bug Report Checklist
  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
Description

I am not able to specify a sort or filter query parameter in the required form using the generated code.

I am not sure if it's a bug in the generator or simply a limitation of the OpenAPI V2 Definition File or the spec. If this is the case, then maybe there is a work around that I can implement?

Background:

The API that I am working with shows that sort and filter can be used as follows:

sort_by[<field>]=<value>

where:
 <field> is the name of the field to sort by, placed within square brackets []
 <value> is asc or desc.

<filter>[<field>]=<value>

where:
 <filter> is the filter type to use;
 <field>is the name of the field to filter by, placed within square brackets [];
 <value> is the field value that the filter should apply to.

As an example, this is how the sort and filter is generated for the following method in GroupsApi.cs:

public async Task<IGetGroupsApiResponse> GetGroupsAsync(Option<string> cursor = default, Option<int> perPage = default, Option<Object> sortBy = default, Option<Object> filter = default, Option<Object> filterPrefix = default, Option<string> ids = default, System.Threading.CancellationToken cancellationToken = default)
{
  ...
     System.Collections.Specialized.NameValueCollection parseQueryStringLocalVar = System.Web.HttpUtility.ParseQueryString(string.Empty);

   if (cursor.IsSet)
       parseQueryStringLocalVar["cursor"] = ClientUtils.ParameterToString(cursor.Value);

   if (perPage.IsSet)
       parseQueryStringLocalVar["per_page"] = ClientUtils.ParameterToString(perPage.Value);

   if (sortBy.IsSet)
       parseQueryStringLocalVar["sort_by"] = ClientUtils.ParameterToString(sortBy.Value);

   if (filter.IsSet)
       parseQueryStringLocalVar["filter"] = ClientUtils.ParameterToString(filter.Value);

   if (filterPrefix.IsSet)
       parseQueryStringLocalVar["filter_prefix"] = ClientUtils.ParameterToString(filterPrefix.Value);

   if (ids.IsSet)
       parseQueryStringLocalVar["ids"] = ClientUtils.ParameterToString(ids.Value);

   uriBuilderLocalVar.Query = parseQueryStringLocalVar.ToString();

   httpRequestMessageLocalVar.RequestUri = uriBuilderLocalVar.Uri;
...
}

The OpenAPI V2 Definition File defines this method as follows:

    "/groups": {
      "get": {
        "summary": "List Groups",
        "description": "List Groups",
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "in": "query",
            "name": "cursor",
            "description": "Used for pagination.  When a list request has more records available, cursors are provided in the response headers `X-Files-Cursor-Next` and `X-Files-Cursor-Prev`.  Send one of those cursor value here to resume an existing list from the next available record.  Note: many of our SDKs have iterator methods that will automatically handle cursor-based pagination.",
            "type": "string",
            "required": false,
            "x-ms-summary": "Used for pagination.  When a list request has more records available, cursors are provided in the response headers `X-Files-Cursor-Next` and `X-Files-Cursor-Prev`.  Send one of those cursor value here to resume an existing list from the next available record.  Note: many of our SDKs have iterator methods that will automatically handle cursor-based pagination."
          },
          {
            "in": "query",
            "name": "per_page",
            "description": "Number of records to show per page.  (Max: 10,000, 1,000 or less is recommended).",
            "type": "integer",
            "format": "int32",
            "required": false,
            "x-ms-summary": "Number of records to show per page.  (Max: 10,000, 1,000 or less is recommended)."
          },
          {
            "in": "query",
            "name": "sort_by",
            "description": "If set, sort records by the specified field in either `asc` or `desc` direction. Valid fields are `name`.",
            "type": "object",
            "required": false,
            "x-example": {
              "name": "desc"
            },
            "x-ms-summary": "If set, sort records by the specified field in either `asc` or `desc` direction. Valid fields are `name`."
          },
          {
            "in": "query",
            "name": "filter",
            "description": "If set, return records where the specified field is equal to the supplied value. Valid fields are `name`.",
            "type": "object",
            "required": false,
            "x-example": {
              "name": "test"
            },
            "x-ms-summary": "If set, return records where the specified field is equal to the supplied value. Valid fields are `name`."
          },
          {
            "in": "query",
            "name": "filter_prefix",
            "description": "If set, return records where the specified field is prefixed by the supplied value. Valid fields are `name`.",
            "type": "object",
            "required": false,
            "x-example": {
              "name": "test"
            },
            "x-ms-summary": "If set, return records where the specified field is prefixed by the supplied value. Valid fields are `name`."
          },
          {
            "in": "query",
            "name": "ids",
            "description": "Comma-separated list of group ids to include in results.",
            "type": "string",
            "required": false,
            "x-ms-summary": "Comma-separated list of group ids to include in results."
          }
        ],
        "responses": {
          "200": {
            "description": "A list of Groups objects.",
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/definitions/GroupEntity"
              }
            },
            "x-ms-summary": "A list of Groups objects."
          },
          "400": {
            "description": "Bad Request",
            "x-ms-summary": "Bad Request"
          },
          "401": {
            "description": "Unauthorized",
            "x-ms-summary": "Unauthorized"
          },
          "403": {
            "description": "Forbidden",
            "x-ms-summary": "Forbidden"
          },
          "404": {
            "description": "Not Found",
            "x-ms-summary": "Not Found"
          },
          "405": {
            "description": "Method Not Allowed",
            "x-ms-summary": "Method Not Allowed"
          },
          "409": {
            "description": "Conflict",
            "x-ms-summary": "Conflict"
          },
          "412": {
            "description": "Precondition Failed",
            "x-ms-summary": "Precondition Failed"
          },
          "422": {
            "description": "Unprocessable Entity",
            "x-ms-summary": "Unprocessable Entity"
          },
          "423": {
            "description": "Locked",
            "x-ms-summary": "Locked"
          },
          "429": {
            "description": "Too Many Requests",
            "x-ms-summary": "Too Many Requests"
          }
        },
        "tags": [
          "groups",
          "Groups"
        ],
        "operationId": "GetGroups",
        "x-authentication": [
          "folder_admin"
        ],
        "x-category": [
          "user_accounts"
        ],
        "x-included-in-ipaas": true,
        "x-sortable_columns": [
          "name"
        ],
        "x-filter_sort_combinations_with_types": {
          "filter_columns": {
            "name": {
              "type": "pattern",
              "sort": [
                "name"
              ]
            }
          },
          "filter_combinations": []
        }
      }
    },
openapi-generator version

7.9.0

OpenAPI declaration file content or url
Generation Details

csharp.config

{
    "apiName": "FilesComApi",
    "library": "generichost",
    "netCoreProjectFile": true,
    "nullableReferenceTypes": true,
    "optionalAssemblyInfo": true,
    "packageName": "Files.Com.Core",
    "targetFramework": "net8.0",
    "sourceFolder": "src"
}
Steps to reproduce
  • Create csharp.config file
  • Execute the following:
nvm install latest
nvm use latest
npm install -g npm@latest
npm install @openapitools/openapi-generator-cli -D
curl -o filescom-v1.json https://app.files.com/api/rest/v1/swagger_doc.json
npx @openapitools/openapi-generator-cli generate -i .\filescom-v1.json -o .\generated -g csharp -c csharp.config

Build

cd generated
dotnet new globaljson --sdk-version 8.0.400 --roll-forward feature
dotnet build
Related issues/PRs

#19626
#19892

Suggest a fix

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by generating the C# client from filescom-v1.json with the provided csharp.config, then inspect generated GroupsApi.cs and the OpenAPI query-parameter definitions for sort_by, filter, and filter_prefix. Compare the generated query construction with the documented bracketed parameter form and run dotnet build. Done means the generated client can express these parameters in the required form without breaking the generated project.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, openapi
Domain
api, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.