tsoa icon indicating copy to clipboard operation
tsoa copied to clipboard

Support RFC6570-style arrays and objects in query and header

Open WoH opened this issue 6 years ago • 8 comments

Sorting

  • I'm submitting a ...

    • [ ] bug report
    • [x] feature request
    • [ ] support request
  • I confirm that I

    • [x] used the search to make sure that a similar issue hasn't already been submit

There are closed issues, none that offer a potential solution afaik.

Expected Behavior

@Route('')
export class TestController {
  @Get('test')
  public async test(
    @Header('tokens') tokens: string[]
  ): Promise<string> {
    return 'Hello, World';
  }
}

could automatically generate:

2.0:

name: tokens
in: header
required: true
type: array
items:
  type: string
collectionFormat: csv

3.0:

name: tokens
in: header
required: true
schema:
  type: array
  items:
    type: string
style: simple

Current Behavior

It throws:

Error: @Header('inUid') Can't support 'array' type. 
in 'TestControllerController.test'

Possible Solution

According to the OpenAPI 3 Spec, it's possible to pass complex data types [array and object] as a standardized format.

Some examples (from the Spec): Assume a parameter named color has one of the following values:

   string -> "blue"
   array -> ["blue","black","brown"]
   object -> { "R": 100, "G": 200, "B": 150 }

The following table shows 2 examples of rendering different formats that would give support for query, path, (cookie) and header. Explode is false (default setting) for both:

style empty string array object usable in
form color= color=blue color=blue,black,brown color=R,100,G,200,B,150 query,cookie
simple n/a blue blue,black,brown R,100,G,200,B,150 path, header

Swagger 2 afaik only supports schema objects in body. Arrays should be fine, objects without references might work, but I'm not sure.

form should be collectionFormat: csv in OpenAPI 2.0. simple should be collectionFormat: csv in 2.0

Context

OpenAPI 3.0.2 Parameter Object

Swagger 2.0 Parameter Object

RFC6570

Detailed Description

Ideally, we would document and eventually extract arrays and objects from header, query and path strings before passing the param to the ValidationService.

I have not fully fleshed out this idea, but I'd say it's ok to discuss this at least in theory for now.

Breaking change?

Not sure, I'd say since we would not support validating these, this is probably safer to do in the context of a major release.

WoH avatar Oct 19 '19 19:10 WoH

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days

github-actions[bot] avatar Nov 19 '19 00:11 github-actions[bot]

OpenAPI 3 Serialization spec, notably (for Query) the spaceDelimited and pipeDelimited styles have corresponding Swagger 2 collectionFormats.


Object construction & validation:

To (re)use exisiting validation,

  1. generate the model (refObject/refAlias/...) for the Query/Header param type, same way as it would be for body parameters
  2. when the request is received, construct (deserialize) the object from parameters entirely
  3. then validate it against the model

This would allow tsoa to reuse existing model validation for validating objects in query parameters, but it'd have to be altered to generate validation errors in the relevant format.

One potential problem i see with this is impossibility at the time of object construction to determine whether numerical-indexed path refers to array or numerical object property. I.e. for query deepObject style with parameter id[articles][0][name]=Potatoes, is id.articles an array or an object with numerical properties(?). This isn't the problem if type information from TS is retained for parameter object deserialization, ...or with the assumption that numerical keys necessarily deserialize into an array.

But then the question arises on how to handle partially received arrays (unless it was already clarified in the OpenAPI spec), i.e. with id[articles][5][topic] received for parameter id, should tsoa fill id.articles[0..4] with undefined? unshift 6th element to 1st?...

E-gy avatar Jun 03 '20 16:06 E-gy

As TSOA currently does with the parsing bodies, deserializing object parameters in query can/should be done by the user with a middleware. This would effectively free TSOA of writing & maintaining deserialization code.

~~Notably, qs is "the go-to" for parsing nested objects withing query strings.~~

E-gy avatar Jun 03 '20 17:06 E-gy

Ok, so, express itself uses qs to parse query parameters, meaning that

  • with using express (but probably other middleware too)
  • discarding TSOA type checks :( with any
  • @Query() parameter can and will receive constructed object, when such is sent
  • obviously without any validation, as it had to be discarded

Example:

@Get('users/find')
public async findUsers(@Query() filter: any){
    console.log(JSON.stringify(foo));
}

Requesting .../users/find?filter[bar][baz]=potato&filter[bar] Receives, and therefore prints

{"bar":{"baz":"potato","boo":"21"},"nyee":"woooooo"}

So in the end, to [properly] support object query parameters, "the only" thing tsoa should do is generate docs and validation.

E-gy avatar Jun 03 '20 17:06 E-gy

I'm not sure if koa/hapi use qs, that's definitely something we should check.

E: https://github.com/koajs/qs

For Queries: Arrays work fine as they are, for Objects, we should use one style, I'd prefer deepObject aswell, which makes sense with the array format tsoa uses (form).

Right now, you can grab the query off the request and add

     "specMerging": "recursive",
      "spec": {
        "paths": {
          "/path/to/endpoint": {
            "parameters": [
              {
                "name": "myDeepObjectQueryParam",
                "in": "query",
                "style": "deepObject",
                "explode": true,
                "description": "Accepts an object",
                "examples": {
                  "one": {
                    "description": "Add a deep object,
                    "value": { "deep": 1 }
                  }
                },
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            ],
// ...

Not sure if we should apply that format to headers aswell, but seems reasonable.

WoH avatar Jun 03 '20 18:06 WoH

Hi, Any updates on this?

quaos avatar Jun 21 '22 01:06 quaos

Hi. Any plans for this? Thanks.

gregbonney-rgare avatar Oct 18 '22 23:10 gregbonney-rgare