Loopback-next: Array[number] query params is NOT correctly parsed.

Created on 20 Jun 2020  ·  6Comments  ·  Source: strongloop/loopback-next

Steps to reproduce

I create HelloController, it can get request parameters by array query strings

export class HelloController {
  constructor() {}

  @get("/hello_array_parse", {
    responses: {
      "200": HELLO_RESPONSE,
    },
  })
  async helloArrayParse(
    @param.array("numberArray", "query", {
      description: "number array",
      items: {
        type: "number",
        pattern: "[0-9]{4}",
      },
    }) numberArray: number[],
    @param.array("stringArray", "query", {
      description: "string array",
      items: {
        type: "string",
        pattern: "[a-zA-Z]{4}",
      },
    }) stringArray: string[],
  ): Promise<object> {
    return {
      numberArray: numberArray,
      stringArray: stringArray,
    };
  }

  @get("/hello_comma_array", {
    responses: {
      "200": HELLO_RESPONSE,
    },
  })
  async helloCommaArray(
    // ref. https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#style-values
    @param({
      name: "numberArray",
      in: "query",
      description: "number array",
      schema: {
        type: "array",
        items: {
          type: "number",
          pattern: "[0-9]{4}",
        },
      },
      style: "form",
      explode: false,
    }) numberArray: number[],
    @param({
      name: "stringArray",
      in: "query",
      description: "string array",
      schema: {
        type: "array",
        items: {
          type: "string",
          pattern: "[a-zA-Z]{4}",
        },
      },
      style: "form",
      explode: false,
    }) stringArray: string[],
  ): Promise<object> {
    return {
      numberArray: numberArray,
      stringArray: stringArray,
    };
  }
}

In detail: https://github.com/shamisonn/reproduce-lb4-bug-oas-array

Current Behavior

case1: request query by array params

request for /hello_array_parse

http://[::1]:3000/hello_array_parse?numberArray=12345&numberArray=123&stringArray=abcde&stringArray=abc

response

{
  "numberArray": [
    "12345",
    "123"
  ],
  "stringArray": [
    "abcde",
    "abc"
  ]
}

case2: request query by array params which delimited by comma

request for /hello_comma_parse

http://[::1]:3000/hello_comma_array?numberArray=12345,678&stringArray=abcde,fgh

response

{
  "numberArray": "12345,678",
  "stringArray": "abcde,fgh"
}

Expected Behavior

both of case1 and case2 are same expected like below, if pattern option is nothing.
I expected numberArray is parsed type of array[number].

{
  "numberArray": [
    12345,
    123
  ],
  "stringArray": [
    "abcde",
    "abc"
  ]
}

if pattern is there, expected error.

Link to reproduction sandbox

In guideline, Modify the selected example project. but I wrote by orginal.
if you ok, check my original example.

Additional information

$ node -e 'console.log(process.platform, process.arch, process.versions.node)'
darwin x64 14.3.0
$ npm ls --prod --depth 0 | grep loopback
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]

I don't have one myself, but I was hoping we could implement it here: https://github.com/strongloop/loopback-next/blob/master/packages/rest/src/coercion/coerce-parameter.ts#L58-L81
like this:

...
case 'array':
  return coerceArray(data, spec);

Related Issues

I can't find. if duplicate , close this.

Thank you for reading!

_See Reporting Issues for more tips on writing good issues_

Acceptance Criteria

OpenAPI bug

Most helpful comment

There is another issue I noticed that if you only pass one value e.g. ?numberArray=123 then it throws the error with the code INVALID_PARAMETER_VALUE and in details with the message should be array, however ?numberArray=123&numberArray=12345 works just fine.

I also noticed that ?numberArray[]=123 works but thats different from how it used to work and also is not how the api explorer sends the values.

Seems like there is a general issue with the @param.array with parameter location query

All 6 comments

Hi @shamisonn, thanks for the detailed issue! There seems to be 3 issues here:

1. Param array coercion does not work

@param.array() ignores the TypeScript type and OAS3 type: 'number'. This is unexpected behaviour.

I've managed to replicate the issue from my end with the sandbox.

2. Accidental nested array

@param.array() will automatically transpose the object into items. So there's no need to use items unless if you're creating a nested array.

For example, the generated OAS3 spec from the sandbox is:

...
"parameters": [
  {
    "name": "numberArray",
    "in": "query",
    "schema": {
      "type": "array",
      "items": {
        "description": "number array",
        "items": {
          "type": "number",
          "pattern": "[0-9]{4}"
        }
      }
    }
  },
  {
    "name": "stringArray",
    "in": "query",
    "schema": {
      "type": "array",
      "items": {
        "description": "string array",
        "items": {
          "type": "string",
          "pattern": "[a-zA-Z]{4}"
        }
      }
    }
  }
],
...

Notice the nested items.items. Frankly, I'm surprised that this considered valid OAS3 spec, and that the API Explorer accepts it. It may be an area we might also want to investigate.

To resolve, we can remove the items object:

    @param.array('numberArray', 'query', {
      description: 'number array',
-     items: {
        type: 'number',
        pattern: '[0-9]{4}',
-     },
    })
    numberArray: number[],
    @param.array('stringArray', 'query', {
      description: 'string array',
-     items: {
        type: 'string',
        pattern: '[a-zA-Z]{4}',
-     },
    })
    stringArray: string[],

This would result in the following OAS3 spec:

...
"parameters": [
  {
    "name": "numberArray",
    "in": "query",
    "schema": {
      "type": "array",
      "items": {
        "description": "number array",
        "type": "number",
        "pattern": "[0-9]{4}"
      }
    }
  },
  {
    "name": "stringArray",
    "in": "query",
    "schema": {
      "type": "array",
      "items": {
        "description": "string array",
        "type": "string",
        "pattern": "[a-zA-Z]{4}"
      }
    }
  }
],
...

Notice that there's no more nested items.items key.

This, however, does not resolve the first issue.

This also reveals a third issue:

3. Certain properties are placed in items

description (along with some other properties) should not be placed inside of items. I'm also surprised that the API explorer accepts this as valid OAS3 spec.

This ties in with #4645,

@achrinza

thank you for early reply!

@param.array() will automatically transpose the object into items.
...
description (along with some other properties) should not be placed inside of items

oh, I'll fix the sandbox. thank you for details.

(edited at 2020/06/20) fixed sandbox => https://github.com/shamisonn/reproduce-lb4-bug-oas-array/commit/d7bd2bdeb0746be4ebeea8bafc86667a002edf76

Thank you @achrinza and @shamisonn Good catch. This is loosely related to the story I am fixing in https://github.com/strongloop/loopback-next/issues/4992

Let me summarize the bug here. I tried with Explorer inputs, not working either.

Description

Parsing array value from argument decorated by @param.array() has bug, it doesn't respect the item type specified for each item.

Given controller method

 async helloArrayParse(
    @param.array("numberArray", "query", {
      type: "number",
      pattern: "[0-9]{4}",
    }) numberArray: number[],
    @param.array("stringArray", "query", {
      type: "string",
      pattern: "[a-zA-Z]{4}",
    }) stringArray: string[],
  ): Promise<object> {
    return {
      numberArray: numberArray,
      stringArray: stringArray,
    };
  }

Current Behaviour

Provide some number inputs for the first array param

Screen Shot 2020-06-22 at 1 37 20 PM

The returned data is not coerced to number, but a string instead.

Screen Shot 2020-06-22 at 1 37 26 PM

Expected Behaviour

{
  "numberArray": [
    // DIFFERENCE: The values should be numbers instead of strings.
    12345,
    123
  ],
  "stringArray": [
    "12345",
    "123"
  ]
}

There is another issue I noticed that if you only pass one value e.g. ?numberArray=123 then it throws the error with the code INVALID_PARAMETER_VALUE and in details with the message should be array, however ?numberArray=123&numberArray=12345 works just fine.

I also noticed that ?numberArray[]=123 works but thats different from how it used to work and also is not how the api explorer sends the values.

Seems like there is a general issue with the @param.array with parameter location query

There is another issue I noticed that if you only pass one value e.g. ?numberArray=123 then it throws the error with the code INVALID_PARAMETER_VALUE and in details with the message should be array, however ?numberArray=123&numberArray=12345 works just fine.

I also noticed that ?numberArray[]=123 works but thats different from how it used to work and also is not how the api explorer sends the values.

Seems like there is a general issue with the @param.array with parameter location query

Also notice the same issue as you mention.

There is another issue I noticed that if you only pass one value e.g. ?numberArray=123 then it throws the error with the code INVALID_PARAMETER_VALUE and in details with the message should be array, however ?numberArray=123&numberArray=12345 works just fine.

I also noticed that ?numberArray[]=123 works but thats different from how it used to work and also is not how the api explorer sends the values.

Seems like there is a general issue with the @param.array with parameter location query

This one is resolved. I tried to reproduce the issue above and am not getting it. Will have a look again

Was this page helpful?
0 / 5 - 0 ratings