Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,31 @@ cookie) or requestPayload don't match the request.

The context object `c` gets a `validation` property with the [validation result](https://github.com/openapistack/openapi-backend/blob/main/DOCS.md#validationresult-object).

### Controlling when requests get validated

Request validation is enabled by default. Pass `validate: false` to turn it off entirely, which also skips building the
Ajv validators at startup.

```javascript
const api = new OpenAPIBackend({ definition: './petstore.yml', validate: false });
```

You can also pass a predicate to decide per request. It receives the context object followed by the same handler
arguments you pass to `handleRequest`, and validation runs only when it returns `true`.

```javascript
const api = new OpenAPIBackend({
definition: './petstore.yml',
// skip validation for internal traffic, validate everything else
validate: (c, req, res) => !req.headers['x-internal-request'],
});

api.handleRequest(req, req, res);
```

Note that type coercion happens as part of validation, so when `coerceTypes` is enabled, requests your predicate skips
won't have their path and query parameters coerced either.

## Response validation

OpenAPIBackend doesn't automatically perform response validation for your handlers, but you can register a
Expand Down
63 changes: 63 additions & 0 deletions src/backend.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,69 @@ describe('OpenAPIBackend', () => {
expect(operationHandler).not.toBeCalled();
expect(res).toBe('validation-failed');
});

test('validates requests by default', async () => {
const api = new OpenAPIBackend({ definition: validationDefinition });
const operationHandler = jest.fn(() => 'operation-response');
const validationFailHandler = jest.fn(() => 'validation-failed');
api.register('getPetById', operationHandler);
api.register('validationFail', validationFailHandler);
await api.init();

const request = {
method: 'get',
path: '/pets/not-an-integer',
headers: {},
};
const res = await api.handleRequest(request);

expect(validationFailHandler).toBeCalledTimes(1);
expect(operationHandler).not.toBeCalled();
expect(res).toBe('validation-failed');
});

test('validates requests when validate is explicitly undefined', async () => {
const api = new OpenAPIBackend({ definition: validationDefinition, validate: undefined });
const operationHandler = jest.fn(() => 'operation-response');
const validationFailHandler = jest.fn(() => 'validation-failed');
api.register('getPetById', operationHandler);
api.register('validationFail', validationFailHandler);
await api.init();

const request = {
method: 'get',
path: '/pets/not-an-integer',
headers: {},
};
const res = await api.handleRequest(request);

expect(validationFailHandler).toBeCalledTimes(1);
expect(operationHandler).not.toBeCalled();
expect(res).toBe('validation-failed');
});

test('skips validation and validator init when validate is false', async () => {
const api = new OpenAPIBackend({ definition: validationDefinition, validate: false });
const operationHandler = jest.fn(() => 'operation-response');
const validationFailHandler = jest.fn(() => 'validation-failed');
api.register('getPetById', operationHandler);
api.register('validationFail', validationFailHandler);
await api.init();

// no Ajv validators are built when validation is disabled
expect(api.validator).toBeUndefined();

const request = {
method: 'get',
path: '/pets/not-an-integer',
headers: {},
};
const res = await api.handleRequest(request);

expect(validationFailHandler).not.toBeCalled();
expect(operationHandler).toBeCalledTimes(1);
expect(res).toBe('operation-response');
});
});
});

Expand Down
6 changes: 4 additions & 2 deletions src/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,12 @@ export class OpenAPIBackend<D extends Document = Document> {
* @param {string} opts.apiRoot - the root URI of the api. all paths are matched relative to apiRoot
* @param {boolean} opts.strict - strict mode, throw errors or warn on OpenAPI spec validation errors (default: false)
* @param {boolean} opts.quick - quick startup, attempts to optimise startup; might break things (default: false)
* @param {boolean | ContextPredicate} opts.validate - whether to validate requests with Ajv (default: true)
* @param {boolean | ContextPredicate} opts.validate - whether to validate requests with Ajv, or a predicate called per
* request to decide (default: true)
* @param {boolean} opts.ignoreTrailingSlashes - whether to ignore trailing slashes when routing (default: true)
* @param {boolean} opts.ajvOpts - default ajv opts to pass to the validator
* @param {boolean} opts.coerceTypes - enable coerce typing of request path and query parameters. Requires validate to be enabled. (default: false)
* @param {boolean} opts.coerceTypes - enable coerce typing of request path and query parameters. Coercion happens as
* part of validation, so it only applies to requests that get validated. (default: false)
* @param {{ [operationId: string]: Handler | ErrorHandler }} opts.handlers - Operation handlers to be registered
* @memberof OpenAPIBackend
*/
Expand Down
Loading