Skip to content

Commit d1e0534

Browse files
authored
Make sure to resolve ref and detect array types when parsing query parameters (#11)
1 parent 21e1c03 commit d1e0534

2 files changed

Lines changed: 148 additions & 1 deletion

File tree

src/compileOperation.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,15 @@ export function compileOperation(
143143

144144
const schemaFn = compileValueSchema(compiler, parameter.schema);
145145

146-
const isArrayType = 'type' in parameter.schema && parameter.schema.type === 'array';
146+
let resolvedSchema = parameter.schema;
147+
while (
148+
typeof resolvedSchema === 'object' &&
149+
resolvedSchema !== null &&
150+
'$ref' in resolvedSchema
151+
) {
152+
resolvedSchema = compiler.resolveRef(resolvedSchema);
153+
}
154+
const isArrayType = 'type' in resolvedSchema && resolvedSchema.type === 'array';
147155

148156
// Assign the query parameter to a variable
149157
nodes.push(
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { expect, test } from 'bun:test';
2+
import { Compiler } from '../compiler';
3+
import type { OpenAPISpec } from '../types';
4+
5+
type ValidateRequest = (request: {
6+
method: string;
7+
path: string;
8+
query: Record<string, string | string[]>;
9+
headers: Record<string, string>;
10+
body?: unknown;
11+
}) => {
12+
operationId?: string;
13+
query: Record<string, unknown>;
14+
};
15+
16+
function buildSpec(): OpenAPISpec {
17+
return {
18+
paths: {
19+
'/inline-array': {
20+
get: {
21+
operationId: 'inlineArray',
22+
parameters: [
23+
{
24+
name: 'addons',
25+
in: 'query',
26+
schema: {
27+
type: 'array',
28+
items: {
29+
type: 'string',
30+
},
31+
},
32+
},
33+
],
34+
},
35+
},
36+
'/ref-array': {
37+
get: {
38+
operationId: 'refArray',
39+
parameters: [
40+
{
41+
name: 'addons',
42+
in: 'query',
43+
schema: {
44+
$ref: '#/components/schemas/AddonsQuery',
45+
},
46+
},
47+
],
48+
},
49+
},
50+
'/non-array': {
51+
get: {
52+
operationId: 'nonArray',
53+
parameters: [
54+
{
55+
name: 'addons',
56+
in: 'query',
57+
schema: {
58+
type: 'string',
59+
},
60+
},
61+
],
62+
},
63+
},
64+
},
65+
components: {
66+
schemas: {
67+
AddonsQuery: {
68+
type: 'array',
69+
items: {
70+
type: 'string',
71+
},
72+
},
73+
},
74+
},
75+
};
76+
}
77+
78+
function compileValidator(spec: OpenAPISpec) {
79+
const code = new Compiler(spec).compile();
80+
const runtimeCode = code.replace(/^export /gm, '');
81+
const factory = new Function(`${runtimeCode}\nreturn { validateRequest };`) as () => {
82+
validateRequest: ValidateRequest;
83+
};
84+
85+
return {
86+
code,
87+
validateRequest: factory().validateRequest,
88+
};
89+
}
90+
91+
test('query string is coerced to array for inline array schema', () => {
92+
const { validateRequest } = compileValidator(buildSpec());
93+
const result = validateRequest({
94+
path: '/inline-array',
95+
method: 'get',
96+
headers: {},
97+
query: {
98+
addons: 'translation_words',
99+
},
100+
});
101+
102+
expect(result.query).toEqual({ addons: ['translation_words'] });
103+
});
104+
105+
test('query string is coerced to array for $ref array schema', () => {
106+
const { validateRequest } = compileValidator(buildSpec());
107+
const result = validateRequest({
108+
path: '/ref-array',
109+
method: 'get',
110+
headers: {},
111+
query: {
112+
addons: 'translation_words',
113+
},
114+
});
115+
116+
expect(result.query).toEqual({ addons: ['translation_words'] });
117+
});
118+
119+
test('query string is not coerced for non-array schema', () => {
120+
const { validateRequest } = compileValidator(buildSpec());
121+
const result = validateRequest({
122+
path: '/non-array',
123+
method: 'get',
124+
headers: {},
125+
query: {
126+
addons: 'translation_words',
127+
},
128+
});
129+
130+
expect(result.query).toEqual({ addons: 'translation_words' });
131+
});
132+
133+
test('compiled validator includes coercion for inline and $ref array schemas only', () => {
134+
const { code } = compileValidator(buildSpec());
135+
const coercionMatches = code.match(/queryParam0 = \[queryParam0\];/g) ?? [];
136+
137+
expect(code).toContain("if (typeof queryParam0 === 'string')");
138+
expect(coercionMatches).toHaveLength(2);
139+
});

0 commit comments

Comments
 (0)