Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Deep objects are not fully escaped with deepObject and explode #2659

Open
bakkerthehacker opened this issue Sep 19, 2022 · 0 comments
Open

Comments

@bakkerthehacker
Copy link

bakkerthehacker commented Sep 19, 2022

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);
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant