Skip to content

Commit

Permalink
Allowing request body to be coerced (#468)
Browse files Browse the repository at this point in the history
* allowing to coerce body

* changing readme to more informative tone

* fix readme

* add missing bit of readme

* fixed formatting

* README - made it clear that validateRequests.coerceTypes only applies to body
  • Loading branch information
MadMango committed Dec 9, 2020
1 parent 59fdf32 commit b640b75
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 15 deletions.
18 changes: 18 additions & 0 deletions README.md
Expand Up @@ -561,6 +561,24 @@ Determines whether the validator should validate requests.
responses:
200:
description: success
```

**coerceTypes:**

Determines whether the validator will coerce the request body. Request query and path params, headers, cookies are coerced by default and this setting does not affect that.

Options:

- `true` - coerce scalar data types.
- `false` - (**default**) do not coerce types. (more strict, safer)
- `"array"` - in addition to coercions between scalar types, coerce scalar data to an array with one element and vice versa (as required by the schema).

For example:

```javascript
validateRequests: {
coerceTypes: true;
}
```

### ▪️ validateResponses (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
Expand Up @@ -71,4 +71,4 @@
"ts-node": "^9.0.0",
"typescript": "^4.0.3"
}
}
}
1 change: 1 addition & 0 deletions src/framework/types.ts
Expand Up @@ -42,6 +42,7 @@ export interface RequestValidatorOptions

export type ValidateRequestOpts = {
allowUnknownQueryParameters?: boolean;
coerceTypes?: boolean | 'array';
};

export type ValidateResponseOpts = {
Expand Down
2 changes: 1 addition & 1 deletion src/middlewares/openapi.request.validator.ts
Expand Up @@ -46,7 +46,7 @@ export class RequestValidator {
this.requestOpts.allowUnknownQueryParameters =
options.allowUnknownQueryParameters;
this.ajv = createRequestAjv(apiDoc, { ...options, coerceTypes: true });
this.ajvBody = createRequestAjv(apiDoc, { ...options, coerceTypes: false });
this.ajvBody = createRequestAjv(apiDoc, options);
}

public validate(
Expand Down
14 changes: 3 additions & 11 deletions src/openapi.validator.ts
Expand Up @@ -78,6 +78,7 @@ export class OpenApiValidator {
if (options.validateRequests === true) {
options.validateRequests = {
allowUnknownQueryParameters: false,
coerceTypes: false
};
}

Expand Down Expand Up @@ -317,17 +318,7 @@ export class OpenApiValidator {
);
}

const coerceResponseTypes = options?.validateResponses?.['coerceTypes'];
if (options.coerceTypes != null && coerceResponseTypes != null) {
throw ono(
'coerceTypes and validateResponses.coerceTypes are mutually exclusive',
);
}

if (options.coerceTypes) {
if (options?.validateResponses) {
options.validateResponses['coerceTypes'] = true;
}
console.warn('coerceTypes is deprecated.');
}

Expand Down Expand Up @@ -390,12 +381,13 @@ class AjvOptions {
}

get request(): RequestValidatorOptions {
const { allowUnknownQueryParameters } = <ValidateRequestOpts>(
const { allowUnknownQueryParameters, coerceTypes } = <ValidateRequestOpts>(
this.options.validateRequests
);
return {
...this.baseOptions(),
allowUnknownQueryParameters,
coerceTypes
};
}

Expand Down
101 changes: 101 additions & 0 deletions test/request.body.validation.coerce.types.spec.ts
@@ -0,0 +1,101 @@
import * as path from 'path';
import * as request from 'supertest';
import { createApp } from './common/app';

describe('request body validation coercion', () => {
let coerceApp = null;
let nonCoerceApp = null;

const defineRoutes = app => {
app.post(`${app.basePath}/coercion_test`, (req, res) => {
res.status(200).send()
}
)
}

before(async () => {
// Set up the express app
const apiSpec = path.join('test', 'resources', 'request.bodies.ref.yaml');
coerceApp = await createApp(
{
apiSpec,
validateRequests: {
coerceTypes: true
},
},
3005,
defineRoutes,
true,
);

nonCoerceApp = await createApp(
{
apiSpec,
// not specifying coercion as it should be false by default
},
3006,
defineRoutes,
true,
);
});

after(() => {
coerceApp.server.close();
nonCoerceApp.server.close();
});

it('should return 200 if coercion is enabled and the type is correct', async () => {
return request(coerceApp)
.post(`${coerceApp.basePath}/coercion_test`)
.set('accept', 'application/json')
.set('content-type', 'application/json')
.send({
aNumberProperty: 4
})
.expect(200)
});

it('should return 200 if coercion is enabled and the type is incorrect but can be coerced', async () => {
return request(coerceApp)
.post(`${coerceApp.basePath}/coercion_test`)
.set('accept', 'application/json')
.set('content-type', 'application/json')
.send({
aNumberProperty: '4'
})
.expect(200)
});

it('should return 400 if coercion is enabled and the type is incorrect and cannot be coerced', async () => {
return request(coerceApp)
.post(`${coerceApp.basePath}/coercion_test`)
.set('accept', 'application/json')
.set('content-type', 'application/json')
.send({
aNumberProperty: 'this is a string and definitely not a number'
})
.expect(400)
});

it('should return 200 if coercion is disabled and the type is correct', async () => {
return request(nonCoerceApp)
.post(`${nonCoerceApp.basePath}/coercion_test`)
.set('accept', 'application/json')
.set('content-type', 'application/json')
.send({
aNumberProperty: 4
})
.expect(200)
});

it('should return 400 if coercion is disabled and the type is incorrect', async () => {
return request(nonCoerceApp)
.post(`${nonCoerceApp.basePath}/coercion_test`)
.set('accept', 'application/json')
.set('content-type', 'application/json')
.send({
aNumberProperty: '4'
})
.expect(400)
});
});
20 changes: 19 additions & 1 deletion test/resources/request.bodies.ref.yaml
Expand Up @@ -17,7 +17,14 @@ paths:
properties:
id:
type: string

responses:
'200':
description: OK

/coercion_test:
post:
requestBody:
$ref: '#/components/requestBodies/CoercionTestBody'
responses:
'200':
description: OK
Expand Down Expand Up @@ -81,6 +88,11 @@ components:
format: phone-number
required:
- testPropertyTwo
CoercionTest:
type: object
properties:
aNumberProperty:
type: number

requestBodies:
TestBody:
Expand All @@ -107,3 +119,9 @@ components:
'*/*':
schema:
type: string
CoercionTestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CoercionTest'

0 comments on commit b640b75

Please sign in to comment.