Skip to content

Commit 4ce0f89

Browse files
authored
fix: correctly handle default values of deepObject query params (#557)
- especially optional deepObject parameters with required fields Co-authored-by: ownagedj <>
1 parent 305d5db commit 4ce0f89

File tree

4 files changed

+141
-3
lines changed

4 files changed

+141
-3
lines changed

src/middlewares/parsers/req.parameter.mutator.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export class RequestParameterMutator {
8787
this.parseJsonAndMutateRequest(req, parameter.in, name);
8888
this.handleFormExplode(req, name, <SchemaObject>schema, parameter);
8989
} else if (style === 'deepObject') {
90-
this.handleDeepObject(req, queryString, name);
90+
this.handleDeepObject(req, queryString, name, schema);
9191
} else {
9292
this.parseJsonAndMutateRequest(req, parameter.in, name);
9393
}
@@ -103,9 +103,33 @@ export class RequestParameterMutator {
103103
});
104104
}
105105

106-
private handleDeepObject(req: Request, qs: string, name: string): void {
106+
private handleDeepObject(req: Request, qs: string, name: string, schema: SchemaObject): void {
107+
const getDefaultSchemaValue = () => {
108+
let defaultValue;
109+
110+
if (schema.default !== undefined) {
111+
defaultValue = schema.default
112+
} else {
113+
['allOf', 'oneOf', 'anyOf'].forEach((key) => {
114+
if (schema[key]) {
115+
schema[key].forEach((s) => {
116+
if (s.$ref) {
117+
const compiledSchema = this.ajv.getSchema(s.$ref);
118+
// as any -> https://stackoverflow.com/a/23553128
119+
defaultValue = defaultValue === undefined ? (compiledSchema.schema as any).default : defaultValue;
120+
} else {
121+
defaultValue = defaultValue === undefined ? s.default : defaultValue;
122+
}
123+
});
124+
}
125+
});
126+
}
127+
128+
return defaultValue;
129+
};
130+
107131
if (!req.query?.[name]) {
108-
req.query[name] = {};
132+
req.query[name] = getDefaultSchemaValue();
109133
}
110134
this.parseJsonAndMutateRequest(req, 'query', name);
111135
// TODO handle url encoded?
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
openapi: "3.0.2"
2+
info:
3+
version: 1.0.0
4+
title: Request Query Serialization
5+
description: Request Query Serialization Test
6+
7+
servers:
8+
- url: /v1/
9+
10+
paths:
11+
/deep_object:
12+
x-vendorExtension1: accounts
13+
get:
14+
x-vendorExtension2: accounts
15+
summary: "retrieve a deep object"
16+
operationId: getDeepObject
17+
parameters:
18+
- in: query
19+
style: deepObject
20+
name: settings
21+
schema:
22+
type: object
23+
required:
24+
- state
25+
properties:
26+
tag_ids:
27+
type: array
28+
items:
29+
type: integer
30+
minimum: 0
31+
minItems: 1
32+
state:
33+
type: string
34+
enum: ["default", "validated", "pending"]
35+
default: "default"
36+
description: "Filter the tags by their validity. The default value ('default') stands for no filtering."
37+
greeting:
38+
type: string
39+
default: "hello"
40+
responses:
41+
"200":
42+
description: the object
43+
44+
components:
45+
schemas:
46+
Deep:
47+
type: object
48+
properties:
49+
tag_ids:
50+
type: array
51+
items:
52+
type: integer
53+
minimum: 0
54+
minItems: 1
55+
state:
56+
type: string
57+
enum: ["default", "validated", "pending"]
58+
default: "default"
59+
description: "Filter the tags by their validity. The default value ('default') stands for no filtering."
60+
greeting:
61+
type: string
62+
default: "hello"

test/resources/serialized.objects.defaults.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ components:
1414
default: 25
1515
type: integer
1616
type: object
17+
default: {}
1718
Sorting:
1819
properties:
1920
field:
@@ -29,6 +30,7 @@ components:
2930
- DESC
3031
type: string
3132
type: object
33+
default: {}
3234
info:
3335
description: API
3436
title: API
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import * as path from 'path';
2+
import * as express from 'express';
3+
import * as request from 'supertest';
4+
import * as packageJson from '../package.json';
5+
import { expect } from 'chai';
6+
import { createApp } from './common/app';
7+
8+
describe(packageJson.name, () => {
9+
let app = null;
10+
11+
before(async () => {
12+
// Set up the express app
13+
const apiSpec = path.join('test', 'resources', 'serialized-deep-object.objects.yaml');
14+
app = await createApp({ apiSpec }, 3005, (app) =>
15+
app.use(
16+
`${app.basePath}`,
17+
express
18+
.Router()
19+
.get(`/deep_object`, (req, res) => res.json(req.query))
20+
),
21+
);
22+
});
23+
24+
after(() => {
25+
app.server.close();
26+
});
27+
28+
it('should explode deepObject query params', async () =>
29+
request(app)
30+
.get(`${app.basePath}/deep_object?settings[state]=default`)
31+
.expect(200)
32+
.then((r) => {
33+
const expected = {
34+
settings: {
35+
greeting: 'hello',
36+
state: 'default'
37+
}
38+
};
39+
expect(r.body).to.deep.equals(expected);
40+
}));
41+
42+
it('should explode deepObject query params (optional query param)', async () =>
43+
request(app)
44+
.get(`${app.basePath}/deep_object`)
45+
.expect(200)
46+
.then((r) => {
47+
const expected = {};
48+
expect(r.body).to.deep.equals(expected);
49+
}));
50+
});

0 commit comments

Comments
 (0)