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

Query parameter serialization issue between TSED versions #2899

Open
orlandorode97 opened this issue Nov 25, 2024 · 3 comments
Open

Query parameter serialization issue between TSED versions #2899

orlandorode97 opened this issue Nov 25, 2024 · 3 comments
Assignees

Comments

@orlandorode97
Copy link

Describe the bug

Description

We recently upgraded from TSED version v7.67.5 to v7.84.1 and are experiencing a change in the way query parameters are serialized, specifically for nested objects in the query string.

Actual Behavior

In version v7.84.1, the query parameters for the nested object are being serialized as a single string, like this:

/api/orders?filter[settled_on]={"gte":"2024-11-25T20:16:16.975Z","lte":"2024-11-25T20:16:16.975Z","gt":"2024-11-25T20:16:16.975Z","lt":"2024-11-25T20:16:16.975Z"}&filter[sale]=ABSD_23

This is causing issues with parsing the query parameters correctly in the backend.

To Reproduce

  1. Use the following code in a TSED controller using the v7.84.1
import { Controller } from "@tsed/di";
import { QueryParams } from "@tsed/platform-params";
import { Format, Get, Name, Nullable } from "@tsed/schema";

class DateFilter {
  @Nullable(Date)
  @Format("date-time")
  gte: Date | null;

  @Format("date-time")
  @Nullable(Date)
  lte: Date | null;

  @Format("date-time")
  @Nullable(Date)
  gt: Date | null;

  @Format("date-time")
  @Nullable(Date)
  lt: Date | null;
}

class OrderFilter {
  @Nullable(DateFilter)
  settled_on: DateFilter | null;

  @Name("sale")
  sale: string;
}

@Controller("/orders")
export class OrderController {
  @Get("/")
  get(@QueryParams("filter") filter: OrderFilter) {
    return;
  }
}
  1. Use Swagger UI to make a request to /orders with the following query parameters as filter:
{
  "settled_on": {
    "gte": "2024-11-25T20:16:16.975Z",
    "lte": "2024-11-25T20:16:16.975Z",
    "gt": "2024-11-25T20:16:16.975Z",
    "lt": "2024-11-25T20:16:16.975Z"
  },
  "sale": "ABSD_23"
}
  1. The API sends the query parameters in the following format
/api/orders?filter[settled_on]={"gte":"2024-11-25T20:16:16.975Z","lte":"2024-11-25T20:16:16.975Z","gt":"2024-11-25T20:16:16.975Z","lt":"2024-11-25T20:16:16.975Z"}&filter[sale]=ABSD_23
  1. Use the same code snippet now using the version v.7.67.5
  2. Use Swagger UI to make the same request to /orders with the same query parameters object.
  3. The API sends the query parameters in the following format
/api/orders?filter[settled_on][gte]=2024-11-25T20:16:16.975Z&filter[settled_on][lte]=2024-11-25T20:16:16.975Z&filter[settled_on][gt]=2024-11-25T20:16:16.975Z&filter[settled_on][lt]=2024-11-25T20:16:16.975Z&filter[sale]=ABSD_23
  1. Notice the difference in how query parameters are serialized between v7.67.5 and v7.84.1.

Expected behavior

Expected Behavior

In version v7.67.5, the query parameters for a nested object (e.g., settled_on in OrderFilter) are serialized as multiple query parameters, such as:

/api/orders?filter[settled_on][gte]=2024-11-25T20:16:16.975Z&filter[settled_on][lte]=2024-11-25T20:16:16.975Z&filter[settled_on][gt]=2024-11-25T20:16:16.975Z&filter[settled_on][lt]=2024-11-25T20:16:16.975Z&filter[sale]=ABSD_23

This behavior is working as expected in v7.67.5 so I expect the same behavior when using the latest version of TSED v.7.84.1

Code snippets

No response

Repository URL example

No response

OS

macOS

Node version

Node 18.18.1

Library version

v7.84.1

Additional context

I tried to downgrade the package @tsed/swagger from v7.84.1 to v7.67.5 to fix this behavior, however, according to TSED framework Docs it is recommendable to have all @tsed/* packages in the same version. https://tsed.io/getting-started/#update-dependencies

@Romakita
Copy link
Collaborator

Hello @orlandorode97
Have you tried using postman?
Because it seems to be an issue with swagger ui and the generated swagger.json. I suggest you to compare the swagger.json between two version.

I’ll check the commit history, to see if there is a related change.

see you

@Romakita
Copy link
Collaborator

Hi @orlandorode97
I don't see pertinent change in the commit history in Ts.ED accepted the swagger-ui bump version dependency.

That I known, deep query parameters style is supported by Ts.ED since a long time (v6.65.0) with Open API 3:

/api/orders?filter[settled_on][gte]=2024-11-25T20:16:16.975Z&filter[settled_on][lte]=2024-11-25T20:16:16.975Z&filter[settled_on][gt]=2024-11-25T20:16:16.975Z&filter[settled_on][lt]=2024-11-25T20:16:16.975Z&filter[sale]=ABSD_23

See documentation here:
https://rc.tsed.dev/docs/model.html#deep-object-on-query or https://tsed.io/docs/model.html#deep-object-on-query

While your version isn't expected and described on Ts.ED documentation. deepObject: style by default since v6.65.0, I don't know how you didn't have the problem before.

Express.js/Koa.js support deepObject: style query parameters parsing and it shouldn't be a problem. I recently used this feature, (and there are some integration test) which proves that it works as expected.

So, I encourage you to create a small project using Ts.ED CLI to have the latest project configuration, adding your models and controller, then start the project and log the received model in the controller method.

You'll see that the instance is correctly parsed. You've have probably something wrong in your side ;)

I change the label of this issue (not a bug), isn't a bug, because deepObject style is enabled since v6.65.0, parsing works, and your version isn't covered by the documentation.

See you

@Romakita Romakita changed the title [BUG]: Query parameter serialization issue between TSED versions Query parameter serialization issue between TSED versions Nov 26, 2024
@stale stale bot removed the wontfix label Nov 26, 2024
@orlandorode97
Copy link
Author

Hello @Romakita
Thanks a lot for the detailed response!

I followed your suggestion and created a new small Ts.ED project using the CLI. After testing with Postman, I can confirm that the query parameters are parsed correctly, and everything works as expected.

The issue, however, seems to be specific to Swagger UI. When I try to test the query parameters directly through Swagger UI, I still face issues with the parsing. I have a few clients that use my API through Swagger UI

The swagger.json generated is different between the versions v7.84.1 and v.7.67.5

v7.84.1:

{
  "openapi": "3.0.1",
  "info": { "title": "Api documentation", "version": "1.0.0" },
  "paths": {
    "/rest/orders": {
      "get": {
        "responses": { "200": { "description": "Success" } },
        "parameters": [
          {
            "in": "query",
            "name": "filter",
            "required": false,
            "style": "deepObject",
            "schema": { "$ref": "#/components/schemas/OrderFilter" }
          }
        ],
        "tags": ["OrderController"],
        "operationId": "orderControllerGet"
      }
    }
  },
  "tags": [{ "name": "OrderController" }],
  "components": {
    "schemas": {
      "OrderFilter": {
        "type": "object",
        "properties": {
          "settled_on": {
            "anyOf": [{ "$ref": "#/components/schemas/DateFilter" }], <--- Here uses anyOf
            "nullable": true
          },
          "sale": { "type": "string" }
        }
      },
      "DateFilter": {
        "type": "object",
        "properties": {
          "gte": { "type": "string", "format": "date-time", "nullable": true },
          "lte": { "type": "string", "format": "date-time", "nullable": true },
          "gt": { "type": "string", "format": "date-time", "nullable": true },
          "lt": { "type": "string", "format": "date-time", "nullable": true }
        }
      }
    }
  }
}

v7.67.5:

{
  "openapi": "3.0.1",
  "info": { "title": "Api documentation", "version": "1.0.0" },
  "paths": {
    "/rest/orders": {
      "get": {
        "responses": { "200": { "description": "Success" } },
        "parameters": [
          {
            "in": "query",
            "name": "filter",
            "required": false,
            "style": "deepObject",
            "schema": { "$ref": "#/components/schemas/OrderFilter" }
          }
        ],
        "tags": ["OrderController"],
        "operationId": "orderControllerGet"
      }
    }
  },
  "tags": [{ "name": "OrderController" }],
  "components": {
    "schemas": {
      "OrderFilter": {
        "type": "object",
        "properties": {
          "settled_on": {
            "nullable": true,
            "oneOf": [{ "$ref": "#/components/schemas/DateFilter" }] <- Here uses oneOf
          },
          "sale": { "type": "string" }
        }
      },
      "DateFilter": {
        "type": "object",
        "properties": {
          "gte": { "type": "string", "format": "date-time", "nullable": true },
          "lte": { "type": "string", "format": "date-time", "nullable": true },
          "gt": { "type": "string", "format": "date-time", "nullable": true },
          "lt": { "type": "string", "format": "date-time", "nullable": true }
        }
      }
    }
  }
}

Would you happen to have any suggestions on how to get Swagger UI to properly handle these parameters?

Thanks again for your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants