diff --git a/README.md b/README.md index 93913ac7..f756545c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/backend.test.ts b/src/backend.test.ts index 8893b380..407108fe 100644 --- a/src/backend.test.ts +++ b/src/backend.test.ts @@ -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'); + }); }); }); diff --git a/src/backend.ts b/src/backend.ts index 33b161e6..6533b5f7 100644 --- a/src/backend.ts +++ b/src/backend.ts @@ -163,10 +163,12 @@ export class OpenAPIBackend { * @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 */