lukeautry / tsoa

Build OpenAPI-compliant REST APIs using TypeScript and Node
MIT License
3.56k stars 503 forks source link

Support RFC6570-style arrays and objects in query and header #517

Open WoH opened 5 years ago

WoH commented 5 years ago

Sorting

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.

github-actions[bot] commented 4 years ago

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

E-gy commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

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.

WoH commented 4 years ago

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.

quaos commented 2 years ago

Hi, Any updates on this?

gregbonney-rgare commented 2 years ago

Hi. Any plans for this? Thanks.