Skip to content

Commit 952c32c

Browse files
authored
Merge pull request #135 from cdimascio/allow-unknown-q-params
Rejecting unknown query parameters should be optional #133
2 parents f927042 + 3645043 commit 952c32c

File tree

9 files changed

+127
-13
lines changed

9 files changed

+127
-13
lines changed

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,20 @@ Determines whether the validator should validate requests.
352352

353353
- `true` (**default**) - validate requests.
354354
- `false` - do not validate requests.
355+
- `{ ... }` - validate requests with options
356+
357+
**allowUnknownQueryParameters:**
358+
359+
- `true` - enables unknown/undeclared query parameters to pass validation
360+
- `false` - (**default**) fail validation if an unknown query parameter is present
361+
362+
For example:
363+
364+
```javascript
365+
validateRequests: {
366+
allowUnknownQueryParameters: true
367+
}
368+
```
355369

356370
### ▪️ validateResponses (optional)
357371

@@ -361,7 +375,7 @@ Determines whether the validator should validate responses. Also accepts respons
361375
- `false` (**default**) - do not validate responses
362376
- `{ ... }` - validate responses with options
363377

364-
**removeAdditional**
378+
**removeAdditional:**
365379

366380
- `"failing"` - additional properties that fail schema validation are automatically removed from the response.
367381

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"ts-log": "^2.1.4"
3737
},
3838
"devDependencies": {
39+
"@types/ajv": "^1.0.0",
3940
"@types/cookie-parser": "^1.4.1",
4041
"@types/express": "^4.17.0",
4142
"@types/mocha": "^5.2.7",

src/framework/types.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Request, Response, NextFunction } from 'express';
22
import { Logger } from 'ts-log';
33
import BasePath from './base.path';
4+
import ajv = require('ajv');
45
export {
56
OpenAPIFrameworkArgs,
67
OpenAPIFrameworkConstructorArgs,
@@ -19,17 +20,25 @@ export type SecurityHandlers = {
1920
) => boolean | Promise<boolean>;
2021
};
2122

23+
export interface RequestValidatorOptions
24+
extends ajv.Options,
25+
ValidateRequestOpts {}
26+
27+
export type ValidateRequestOpts = {
28+
allowUnknownQueryParameters?: boolean;
29+
};
30+
2231
export type ValidateResponseOpts = {
2332
removeAdditional?: string | boolean;
2433
};
2534

2635
export interface OpenApiValidatorOpts {
2736
apiSpec: OpenAPIV3.Document | string;
2837
validateResponses?: boolean | ValidateResponseOpts;
29-
validateRequests?: boolean;
38+
validateRequests?: boolean | ValidateRequestOpts;
3039
securityHandlers?: SecurityHandlers;
3140
coerceTypes?: boolean;
32-
unknownFormats?: string[] | string | boolean;
41+
unknownFormats?: true | string[] | 'ignore';
3342
multerOpts?: {};
3443
}
3544

src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Application, Response, NextFunction } from 'express';
55
import { OpenApiContext } from './framework/openapi.context';
66
import {
77
OpenApiValidatorOpts,
8+
ValidateRequestOpts,
89
ValidateResponseOpts,
910
OpenApiRequest,
1011
OpenApiRequestHandler,
@@ -29,6 +30,12 @@ export class OpenApiValidator {
2930
};
3031
}
3132

33+
if (options.validateRequests === true) {
34+
options.validateRequests = {
35+
allowUnknownQueryParameters: false,
36+
};
37+
}
38+
3239
this.options = options;
3340
this.context = new OpenApiContext({
3441
apiDoc: options.apiSpec,
@@ -101,7 +108,10 @@ export class OpenApiValidator {
101108
}
102109

103110
private installRequestValidationMiddleware(): void {
104-
const { coerceTypes, unknownFormats } = this.options;
111+
const { coerceTypes, unknownFormats, validateRequests } = this.options;
112+
const { allowUnknownQueryParameters } = <ValidateRequestOpts>(
113+
validateRequests
114+
);
105115
const requestValidator = new middlewares.RequestValidator(
106116
this.context.apiDoc,
107117
{
@@ -110,6 +120,7 @@ export class OpenApiValidator {
110120
removeAdditional: false,
111121
useDefaults: true,
112122
unknownFormats,
123+
allowUnknownQueryParameters,
113124
},
114125
);
115126
const requestValidationHandler: OpenApiRequestHandler = (req, res, next) =>

src/middlewares/ajv/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import * as Ajv from 'ajv';
22
import * as draftSchema from 'ajv/lib/refs/json-schema-draft-04.json';
33
import { formats } from './formats';
44
import { OpenAPIV3 } from '../../framework/types';
5+
import ajv = require('ajv');
56

67
const TYPE_JSON = 'application/json';
78

89
export function createRequestAjv(
910
openApiSpec: OpenAPIV3.Document,
10-
options: any = {},
11+
options: ajv.Options = {},
1112
): Ajv.Ajv {
1213
return createAjv(openApiSpec, options);
1314
}

src/middlewares/openapi.request.validator.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as ajv from 'ajv';
12
import { createRequestAjv } from './ajv';
23
import {
34
ContentType,
@@ -7,7 +8,12 @@ import {
78
} from './util';
89
import ono from 'ono';
910
import { NextFunction, Response } from 'express';
10-
import { OpenAPIV3, OpenApiRequest } from '../framework/types';
11+
import {
12+
OpenAPIV3,
13+
OpenApiRequest,
14+
RequestValidatorOptions,
15+
ValidateRequestOpts,
16+
} from '../framework/types';
1117
import { Ajv } from 'ajv';
1218

1319
const TYPE_JSON = 'application/json';
@@ -16,10 +22,16 @@ export class RequestValidator {
1622
private _middlewareCache;
1723
private _apiDocs: OpenAPIV3.Document;
1824
private ajv: Ajv;
25+
private _requestOpts: ValidateRequestOpts = {};
1926

20-
constructor(apiDocs: OpenAPIV3.Document, options = {}) {
27+
constructor(
28+
apiDocs: OpenAPIV3.Document,
29+
options: RequestValidatorOptions = {},
30+
) {
2131
this._middlewareCache = {};
2232
this._apiDocs = apiDocs;
33+
this._requestOpts.allowUnknownQueryParameters =
34+
options.allowUnknownQueryParameters;
2335
this.ajv = createRequestAjv(apiDocs, options);
2436
}
2537

@@ -106,11 +118,13 @@ export class RequestValidator {
106118

107119
const validator = this.ajv.compile(schema);
108120
return (req, res, next) => {
109-
this.rejectUnknownQueryParams(
110-
req.query,
111-
schema.properties.query,
112-
securityQueryParameter,
113-
);
121+
if (!this._requestOpts.allowUnknownQueryParameters) {
122+
this.rejectUnknownQueryParams(
123+
req.query,
124+
schema.properties.query,
125+
securityQueryParameter,
126+
);
127+
}
114128

115129
const shouldUpdatePathParams =
116130
Object.keys(req.openapi.pathParams).length > 0;

test/common/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import * as logger from 'morgan';
66

77
import { OpenApiValidator } from '../../src';
88
import { startServer, routes } from './app.common';
9+
import { OpenApiValidatorOpts } from '../../src/framework/types';
910

1011
export async function createApp(
11-
opts?: any,
12+
opts?: OpenApiValidatorOpts,
1213
port: number = 3000,
1314
customRoutes: (app) => void = () => {},
1415
useRoutes: boolean = true,
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as path from 'path';
2+
import * as express from 'express';
3+
import * as request from 'supertest';
4+
import { createApp } from './common/app';
5+
6+
const packageJson = require('../package.json');
7+
8+
describe(packageJson.name, () => {
9+
let app = null;
10+
let basePath = null;
11+
12+
before(async () => {
13+
// Set up the express app
14+
const apiSpec = path.join('test', 'resources', 'query.params.yaml');
15+
app = await createApp(
16+
{ apiSpec, validateRequests: { allowUnknownQueryParameters: true } },
17+
3005,
18+
app =>
19+
app.use(
20+
`${app.basePath}`,
21+
express
22+
.Router()
23+
.post(`/pets/nullable`, (req, res) => res.json(req.body)),
24+
),
25+
);
26+
});
27+
28+
after(() => {
29+
app.server.close();
30+
});
31+
32+
it('should pass if known query params are specified', async () =>
33+
request(app)
34+
.get(`${app.basePath}/pets`)
35+
.query({
36+
tags: 'one,two,three',
37+
limit: 10,
38+
breed: 'german_shepherd',
39+
owner_name: 'carmine',
40+
})
41+
.expect(200));
42+
43+
it('should not fail if unknown query param is specified', async () =>
44+
request(app)
45+
.get(`${app.basePath}/pets`)
46+
.query({
47+
tags: 'one,two,three',
48+
limit: 10,
49+
breed: 'german_shepherd',
50+
owner_name: 'carmine',
51+
unknown_prop: 'test',
52+
})
53+
.expect(200));
54+
});

0 commit comments

Comments
 (0)