Skip to content

Commit

Permalink
#99 option to remove additional props from responses
Browse files Browse the repository at this point in the history
  • Loading branch information
Carmine DiMascio committed Nov 2, 2019
1 parent 150279b commit 1edde3c
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 10 deletions.
22 changes: 18 additions & 4 deletions README.md
Expand Up @@ -312,7 +312,7 @@ new OpenApiValidator(options).install({
validateRequests: true,
validateResponses: true,
unknownFormats: ['phone-number', 'uuid'],
multerOpts: { ... },
multerOpts: { ... },
securityHandlers: {
ApiKeyAuth: (req, scopes, schema) => {
throw { status: 401, message: 'sorry' }
Expand All @@ -333,7 +333,7 @@ apiSpec: './path/to/my-openapi-spec.yaml'
or

```javascript
apiSpec: {
apiSpec: {
openapi: '3.0.1',
info: {...},
servers: [...],
Expand All @@ -355,10 +355,24 @@ Determines whether the validator should validate requests.

### ▪️ validateResponses (optional)

Determines whether the validator should validate responses.
Determines whether the validator should validate responses. Also accepts response validation options.

- `true` - validate responses
- `true` - validate responses in 'strict' mode i.e. responses MUST match the schema.
- `false` (**default**) - do not validate responses
- `{ ... }` - validate responses with options

**removeAdditional**

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

For example:

```json
validateResponses: {
removeAdditional: 'failing'
}
```


### ▪️ unknownFormats (optional)

Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "express-openapi-validator",
"version": "2.13.0",
"version": "2.14.0",
"description": "Automatically validate API requests and responses with OpenAPI 3 and Express.",
"main": "dist/index.js",
"scripts": {
Expand Down
24 changes: 22 additions & 2 deletions src/index.ts
Expand Up @@ -22,9 +22,12 @@ export type SecurityHandlers = {
schema: OpenAPIV3.SecuritySchemeObject,
) => boolean | Promise<boolean>;
};
export type ValidateResponseOpts = {
removeAdditional?: string | boolean;
};
export interface OpenApiValidatorOpts {
apiSpec: OpenAPIV3.Document | string;
validateResponses?: boolean;
validateResponses?: boolean | ValidateResponseOpts;
validateRequests?: boolean;
securityHandlers?: SecurityHandlers;
coerceTypes?: boolean;
Expand All @@ -42,6 +45,18 @@ export class OpenApiValidator {
if (options.unknownFormats == null) options.unknownFormats === true;
if (options.coerceTypes == null) options.coerceTypes = true;
if (options.validateRequests == null) options.validateRequests = true;
if (options.validateResponses == null) options.validateResponses = false;
if (!options.validateRequests) throw Error('validateRequests must be true');

if (!options.validateResponses) {
} else if (
options.validateResponses === true ||
options.validateResponses === 'strict'
) {
options.validateResponses = {
removeAdditional: false,
};
}

this.options = options;

Expand Down Expand Up @@ -95,11 +110,15 @@ export class OpenApiValidator {
const requestValidatorMw: OpenApiRequestHandler = (req, res, next) =>
requestValidator.validate(req, res, next);

const removeAdditional =
this.options.validateResponses &&
(<ValidateResponseOpts>this.options.validateResponses).removeAdditional;

const responseValidator = new middlewares.ResponseValidator(
this.context.apiDoc,
{
coerceTypes,
removeAdditional: false,
removeAdditional,
unknownFormats,
},
);
Expand All @@ -124,6 +143,7 @@ export class OpenApiValidator {

private validateOptions(options: OpenApiValidatorOpts): void {
if (!options.apiSpec) throw ono('apiSpec required');

const securityHandlers = options.securityHandlers;
if (securityHandlers != null) {
if (
Expand Down
113 changes: 113 additions & 0 deletions test/response.validation.options.spec.ts
@@ -0,0 +1,113 @@
import * as path from 'path';
import * as express from 'express';
import { expect } from 'chai';
import * as request from 'supertest';
import { createApp } from './common/app';

const packageJson = require('../package.json');
const apiSpecPath = path.join('test', 'resources', 'response.validation.yaml');

describe(packageJson.name, () => {
let app = null;

before(async () => {
// set up express app
app = await createApp(
{
apiSpec: apiSpecPath,
validateResponses: {
removeAdditional: 'failing',
},
},
3005,
app => {
app.get(`${app.basePath}/users`, (req, res) => {
const json = ['user1', 'user2', 'user3'];
return res.json(json);
});
app.get(`${app.basePath}/pets`, (req, res) => {
let json = {};
if ((req.query.mode = 'bad_type')) {
json = [{ id: 'bad_id', name: 'name', tag: 'tag' }];
}
return res.json(json);
});
app.post(`${app.basePath}/no_additional_props`, (req, res) => {
res.json(req.body);
});
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
message: err.message,
code: err.status || 500,
});
});
},
false,
);
});

after(() => {
app.server.close();
});

it('should fail if response field has a value of incorrect type', async () =>
request(app)
.get(`${app.basePath}/pets?mode=bad_type`)
.expect(500)
.then((r: any) => {
expect(r.body.message).to.contain('should be integer');
expect(r.body)
.to.have.property('code')
.that.equals(500);
}));

it('should remove additional properties when set false', async () =>
request(app)
.post(`${app.basePath}/no_additional_props`)
.send({
token_type: 'token',
expires_in: 1000,
access_token: 'token',
refresh_token: 'refresh_token',
user: {
id: 10,
},
some_invalid_prop: 'test',
})
.expect(200)
.then((r: any) => {
const body = r.body;
expect(body).to.have.property('token_type');
expect(body).to.not.have.property('some_invalid_prop');
}));

it('should remove nested additional prop if additionalProperties is false', async () =>
request(app)
.post(`${app.basePath}/no_additional_props`)
.send({
token_type: 'token',
expires_in: 1000,
access_token: 'token',
refresh_token: 'refresh_token',
user: {
id: 10,
extra_prop: true,
},
})
.expect(200)
.then((r: any) => {
const body = r.body;
expect(body.user).to.have.property('id');
expect(body.user).to.not.have.property('extra_prop');
}));

it('should pass if response is a list', async () =>
request(app)
.get(`${app.basePath}/users`)
.expect(200)
.then((r: any) => {
expect(r.body)
.is.an('array')
.with.length(3);
}));
});
6 changes: 4 additions & 2 deletions test/response.validation.spec.ts
Expand Up @@ -13,7 +13,10 @@ describe(packageJson.name, () => {
before(async () => {
// set up express app
app = await createApp(
{ apiSpec: apiSpecPath, validateResponses: true },
{
apiSpec: apiSpecPath,
validateResponses: true,
},
3005,
app => {
app.get(`${app.basePath}/users`, (req, res) => {
Expand All @@ -31,7 +34,6 @@ describe(packageJson.name, () => {
res.json(req.body);
});
app.use((err, req, res, next) => {
console.log('-------', err);
res.status(err.status || 500).json({
message: err.message,
code: err.status || 500,
Expand Down

0 comments on commit 1edde3c

Please sign in to comment.