swagger-api / swagger-api/swagger-client

Deep objects are not fully escaped with `deepObject` and `explode`

Open
#2,659 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
2.7k
Forks
765
Avg merge
1d 1h
Merged PRs (30d)
6

Description

When using parameters with deepObject and explode, the parameters are only partially escaped.

Q&A (please complete the following information)
  • OS: linux mint 20.3
  • Environment: node v14.19.2
  • Method of installation: npm install
  • Swagger-Client version: 3.18.5
  • Swagger/OpenAPI version: OpenAPI 3.0.3
Content & configuration

Swagger/OpenAPI definition:

const spec = {
  openapi: '3.0.3',
  paths: {
    '/export': {
      post: {
        operationId: 'exportProducts',
        parameters: [
          {
            name: 'filters',
            in: 'query',
            style: 'deepObject',
            explode: true,
            schema: { type: 'object', additionalProperties: true },
          },
          {
            name: 'filtersDeep',
            in: 'query',
            style: 'deepObject',
            explode: true,
            schema: { type: 'object', additionalProperties: true },
          },
        ],
      },
    },
  },
};

Swagger-Client usage:

const parameters = {
  filters: { 'a+b+c': '123=456' },
  filtersDeep: { 'a+b+c': { 'd+e+f': '123=456' } },
};

const SwaggerClient = require('swagger-client');

const request = SwaggerClient.buildRequest({ spec, operationId: 'exportProducts', parameters });

console.log(request.url);
// prints:
// /export?filters%5Ba%2Bb%2Bc%5D=123%3D456&filtersDeep%5Ba%2Bb%2Bc%5D[d+e+f]=123=456
Describe the bug you're encountering

When using parameters with deepObject and explode, the parameters are only partially escaped. If an object with only 1 depth is provided, it is escaped correctly. But objects with 2 or more depth levels are not escaped correctly. In deeper objects, only the parameter name and 1st level of keys are escaped. Nothing else is escaped including the value.

To reproduce...

Steps to reproduce the behavior:

  1. Create spec with deepObject and explode parameters.
  2. Pass in a 1 depth object as a parameter, observe it is fully escaped.
  3. Pass in a 2 depth object as a parameter, observe it is not fully escaped.
Expected behavior

I expect deep objects passed as parameters with deepObject and explode to be fully escaped, including all the keys and values.

Additional context or thoughts

I see that there are other tickets/issues/prs for deepObject/explode support so I'm not sure how finalized these parameters are. However, the behaviour here doesn't seem correct in any situation.

I have a hacky solution to apply a fix through the parameterBuilders system. It is a bit hacky, both because it grabs the default un-exported oas3 builder and it modifies the value using qs before the http escaping system happens. Changes to serializationOption had no effect.

Click for fix with parameterBuilders
// npm install swagger-client@3.18.5
// npm install qs@6.11.0

const parameters = {
  filters: { 'a+b+c': '123=456' },
  filtersDeep: { 'a+b+c': { 'd+e+f': '123=456' } },
};

// QS version:
const qs = require('qs');

console.log('QS Version:');
console.log(`/export?${qs.stringify(parameters)}`);

// Swagger version:
const SwaggerClient = require('swagger-client');

const spec = {
  openapi: '3.0.3',
  paths: {
    '/export': {
      post: {
        operationId: 'exportProducts',
        parameters: [
          {
            name: 'filters',
            in: 'query',
            style: 'deepObject',
            explode: true,
            schema: { type: 'object', additionalProperties: true },
          },
          {
            name: 'filtersDeep',
            in: 'query',
            style: 'deepObject',
            explode: true,
            schema: { type: 'object', additionalProperties: true },
          },
        ],
      },
    },
  },
};

const request = SwaggerClient.buildRequest({ spec, operationId: 'exportProducts', parameters });

console.log('Swagger Version:');
console.log(request.url);

// Customize with parameterBuilders
const oas3Builder = require('swagger-client/lib/execute/oas3/parameter-builders');

const requestCustomBuilder = SwaggerClient.buildRequest({
  spec,
  operationId: 'exportProducts',
  parameters,
  parameterBuilders: {
    body: oas3Builder.body,
    header: oas3Builder.header,
    path: oas3Builder.path,
    formData: oas3Builder.formData,
    cookie: oas3Builder.cookie,
    query: ({ req, value, parameter }) => {
      if (parameter.style === 'deepObject' && parameter.explode) {
        const flatParams = [...new URLSearchParams(qs.stringify({ value }))];
        const newValue = Object.fromEntries(flatParams.map(
          ([key, val]) => [key.replace(/^value\[/, '').replace(/]$/, ''), val],
        ));
        oas3Builder.query({ req, value: newValue, parameter });
      } else {
        oas3Builder.query({ req, value, parameter });
      }
    },
  },
});

console.log('Swagger Custom Builder Version:');
console.log(requestCustomBuilder.url);

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 at lib/execute/oas3/parameter-builders, especially the query builder used by SwaggerClient.buildRequest, and reproduce the deepObject/explode example from the issue. Trace how nested keys and values become the request URL; done means every nested key and value is correctly escaped, matching the expected behavior shown.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.