Skip to content

Commit f408e3c

Browse files
authored
fix(execute): explode array parameters for combined schemas (#3826)
1 parent 5a4f9a3 commit f408e3c

File tree

4 files changed

+350
-18
lines changed

4 files changed

+350
-18
lines changed

src/execute/index.js

+35-13
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ import { serialize as serializeCookie } from '../helpers/cookie.js';
2020

2121
const arrayOrEmpty = (ar) => (Array.isArray(ar) ? ar : []);
2222

23-
const findObjectSchema = (schema, { recurse = true, depth = 1 } = {}) => {
23+
const findObjectOrArraySchema = (schema, { recurse = true, depth = 1 } = {}) => {
2424
if (!isPlainObject(schema)) return undefined;
2525

26-
// check if the schema is an object type
27-
if (schema.type === 'object' || (Array.isArray(schema.type) && schema.type.includes('object'))) {
26+
// check if the schema is an object or array type
27+
if (
28+
schema.type === 'object' ||
29+
schema.type === 'array' ||
30+
(Array.isArray(schema.type) &&
31+
(schema.type.includes('object') || schema.type.includes('array')))
32+
) {
2833
return schema;
2934
}
3035

@@ -33,14 +38,18 @@ const findObjectSchema = (schema, { recurse = true, depth = 1 } = {}) => {
3338
if (recurse) {
3439
// traverse oneOf keyword first
3540
const oneOfResult = Array.isArray(schema.oneOf)
36-
? schema.oneOf.find((subschema) => findObjectSchema(subschema, { recurse, depth: depth + 1 }))
41+
? schema.oneOf.find((subschema) =>
42+
findObjectOrArraySchema(subschema, { recurse, depth: depth + 1 })
43+
)
3744
: undefined;
3845

3946
if (oneOfResult) return oneOfResult;
4047

4148
// traverse anyOf keyword second
4249
const anyOfResult = Array.isArray(schema.anyOf)
43-
? schema.anyOf.find((subschema) => findObjectSchema(subschema, { recurse, depth: depth + 1 }))
50+
? schema.anyOf.find((subschema) =>
51+
findObjectOrArraySchema(subschema, { recurse, depth: depth + 1 })
52+
)
4453
: undefined;
4554

4655
if (anyOfResult) return anyOfResult;
@@ -49,18 +58,18 @@ const findObjectSchema = (schema, { recurse = true, depth = 1 } = {}) => {
4958
return undefined;
5059
};
5160

52-
const parseJsonObject = ({ value, silentFail = false }) => {
61+
const parseJsonObjectOrArray = ({ value, silentFail = false }) => {
5362
try {
5463
const parsedValue = JSON.parse(value);
55-
if (typeof parsedValue === 'object') {
64+
if (isPlainObject(parsedValue) || Array.isArray(parsedValue)) {
5665
return parsedValue;
5766
}
5867
if (!silentFail) {
59-
throw new Error('Expected JSON serialized object');
68+
throw new Error('Expected JSON serialized object or array');
6069
}
6170
} catch {
6271
if (!silentFail) {
63-
throw new Error('Could not parse object parameter value string as JSON Object');
72+
throw new Error('Could not parse parameter value string as JSON Object or JSON Array');
6473
}
6574
}
6675
return value;
@@ -303,10 +312,23 @@ export function buildRequest(options) {
303312
}
304313

305314
if (specIsOAS3 && typeof value === 'string') {
306-
if (has('type', parameter.schema) && findObjectSchema(parameter.schema, { recurse: false })) {
307-
value = parseJsonObject({ value, silentFail: false });
308-
} else if (findObjectSchema(parameter.schema, { recurse: true })) {
309-
value = parseJsonObject({ value, silentFail: true });
315+
if (
316+
has('type', parameter.schema) &&
317+
typeof parameter.schema.type === 'string' &&
318+
findObjectOrArraySchema(parameter.schema, { recurse: false })
319+
) {
320+
value = parseJsonObjectOrArray({ value, silentFail: false });
321+
} else if (
322+
has('type', parameter.schema) &&
323+
Array.isArray(parameter.schema.type) &&
324+
findObjectOrArraySchema(parameter.schema, { recurse: false })
325+
) {
326+
value = parseJsonObjectOrArray({ value, silentFail: true });
327+
} else if (
328+
!has('type', parameter.schema) &&
329+
findObjectOrArraySchema(parameter.schema, { recurse: true })
330+
) {
331+
value = parseJsonObjectOrArray({ value, silentFail: true });
310332
}
311333
}
312334

test/execute/openapi-3-1.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ describe('given OpenAPI 3.1.0 definition', () => {
138138
const response = await SwaggerClient.execute({
139139
spec,
140140
operationId: 'getUserList',
141-
parameters: { q: 'search string' },
141+
parameters: { q: ['search string'] },
142142
securities: { authorized: { BearerAuth: '3492342948239482398' } },
143143
});
144144

@@ -150,7 +150,7 @@ describe('given OpenAPI 3.1.0 definition', () => {
150150
spec,
151151
pathName: '/users',
152152
method: 'get',
153-
parameters: { q: 'search string' },
153+
parameters: { q: ['search string'] },
154154
securities: { authorized: { BearerAuth: '3492342948239482398' } },
155155
});
156156

@@ -164,7 +164,7 @@ describe('given OpenAPI 3.1.0 definition', () => {
164164
});
165165
const response = await client.execute({
166166
operationId: 'getUserList',
167-
parameters: { q: 'search string' },
167+
parameters: { q: ['search string'] },
168168
});
169169

170170
expect(response.body).toEqual([{ id: 'value' }]);
@@ -174,7 +174,7 @@ describe('given OpenAPI 3.1.0 definition', () => {
174174
const request = SwaggerClient.buildRequest({
175175
spec,
176176
operationId: 'getUserList',
177-
parameters: { q: 'search string' },
177+
parameters: { q: ['search string'] },
178178
securities: { authorized: { BearerAuth: '3492342948239482398' } },
179179
responseContentType: 'application/json',
180180
});
@@ -195,7 +195,7 @@ describe('given OpenAPI 3.1.0 definition', () => {
195195
const request = SwaggerClient.buildRequest({
196196
spec,
197197
operationId: 'getUserList',
198-
parameters: { q: 'search string' },
198+
parameters: { q: ['search string'] },
199199
securities: { authorized: { BearerAuth: '3492342948239482398' } },
200200
responseContentType: 'application/json',
201201
baseURL,

test/oas3/execute/main.js

+76
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,82 @@ describe('buildRequest - OpenAPI Specification 3.0', () => {
573573
});
574574

575575
describe('`schema` parameters', () => {
576+
test('should build a request when parameter type is `string` and value is not a stringified object', () => {
577+
// Given
578+
const spec = {
579+
openapi: '3.1.0',
580+
paths: {
581+
'/users': {
582+
get: {
583+
operationId: 'myOperation',
584+
parameters: [
585+
{
586+
in: 'query',
587+
name: 'parameters',
588+
schema: {
589+
type: 'string',
590+
},
591+
},
592+
],
593+
},
594+
},
595+
},
596+
};
597+
// when
598+
const req = buildRequest({
599+
spec,
600+
operationId: 'myOperation',
601+
parameters: {
602+
parameters: 'test',
603+
},
604+
});
605+
606+
expect(req).toEqual({
607+
method: 'GET',
608+
url: `/users?parameters=test`,
609+
credentials: 'same-origin',
610+
headers: {},
611+
});
612+
});
613+
614+
it('should build a request when parameter type is `string` and value is a stringified object', () => {
615+
// Given
616+
const spec = {
617+
openapi: '3.1.0',
618+
paths: {
619+
'/users': {
620+
get: {
621+
operationId: 'myOperation',
622+
parameters: [
623+
{
624+
in: 'query',
625+
name: 'parameters',
626+
schema: {
627+
type: 'string',
628+
},
629+
},
630+
],
631+
},
632+
},
633+
},
634+
};
635+
// when
636+
const req = buildRequest({
637+
spec,
638+
operationId: 'myOperation',
639+
parameters: {
640+
parameters: '{"bar":{"baz":"qux"}}',
641+
},
642+
});
643+
644+
expect(req).toEqual({
645+
method: 'GET',
646+
url: `/users?parameters=${escape('{"bar":{"baz":"qux"}}')}`,
647+
credentials: 'same-origin',
648+
headers: {},
649+
});
650+
});
651+
576652
it('should encode JSON values provided as objects', () => {
577653
const req = buildRequest({
578654
spec: {

0 commit comments

Comments
 (0)