diff --git a/README.md b/README.md index 0803af5f..f3cf27bb 100644 --- a/README.md +++ b/README.md @@ -717,13 +717,17 @@ module.exports = { ### ▪️ ignorePaths (optional) -Defines a regular expression that determines whether a path(s) should be ignored. Any path that matches the regular expression will be ignored by the validator. - -The following ignores any path that ends in `/pets` e.g. `/v1/pets` +Defines a regular expression or function that determines whether a path(s) should be ignored. If it's a regular expression, any path that matches the regular expression will be ignored by the validator. If it's a function, it will ignore any paths that returns a truthy value. +The following ignores any path that ends in `/pets` e.g. `/v1/pets`. +As a regular expression: ``` ignorePaths: /.*\/pets$/ ``` +or as a function: +``` +ignorePaths: (path) => path.endsWith('/pets') +``` ### ▪️ fileUploader (optional) diff --git a/src/framework/openapi.context.ts b/src/framework/openapi.context.ts index 0bf2d8cc..ef5ff4bb 100644 --- a/src/framework/openapi.context.ts +++ b/src/framework/openapi.context.ts @@ -1,5 +1,6 @@ import { OpenAPIV3 } from './types'; import { Spec, RouteMetadata } from './openapi.spec.loader'; +import { Console } from 'console'; export interface RoutePair { @@ -12,9 +13,9 @@ export class OpenApiContext { public readonly openApiRouteMap = {}; public readonly routes: RouteMetadata[] = []; private readonly basePaths: string[]; - private readonly ignorePaths: RegExp; + private readonly ignorePaths: RegExp | Function; - constructor(spec: Spec, ignorePaths: RegExp) { + constructor(spec: Spec, ignorePaths: RegExp | Function) { this.apiDoc = spec.apiDoc; this.basePaths = spec.basePaths; this.routes = spec.routes; @@ -32,7 +33,7 @@ export class OpenApiContext { } public shouldIgnoreRoute(path: string) { - return this.ignorePaths?.test(path); + return typeof this.ignorePaths === 'function' ? this.ignorePaths(path) : this.ignorePaths?.test(path); } public routePair(route: string): RoutePair { diff --git a/src/framework/types.ts b/src/framework/types.ts index b8740f9b..a67612db 100644 --- a/src/framework/types.ts +++ b/src/framework/types.ts @@ -69,7 +69,7 @@ export interface OpenApiValidatorOpts { validateResponses?: boolean | ValidateResponseOpts; validateRequests?: boolean | ValidateRequestOpts; validateSecurity?: boolean | ValidateSecurityOpts; - ignorePaths?: RegExp; + ignorePaths?: RegExp | Function; securityHandlers?: SecurityHandlers; coerceTypes?: boolean | 'array'; unknownFormats?: true | string[] | 'ignore'; diff --git a/test/ignore.paths.spec.ts b/test/ignore.paths.spec.ts index b1a4467b..c0643201 100644 --- a/test/ignore.paths.spec.ts +++ b/test/ignore.paths.spec.ts @@ -2,9 +2,8 @@ import * as path from 'path'; import { expect } from 'chai'; import * as request from 'supertest'; import { createApp } from './common/app'; -import * as packageJson from '../package.json'; -describe(packageJson.name, () => { +describe('ignorePaths as RegExp', () => { let app = null; let basePath = null; @@ -105,3 +104,103 @@ describe(packageJson.name, () => { })); }); }); + +describe('ignorePaths as Function', () => { + let app = null; + let basePath = null; + + before(async () => { + const apiSpec = path.join('test', 'resources', 'ignore.paths.yaml'); + + app = await createApp( + { apiSpec, ignorePaths: (path) => path.endsWith('/hippies') }, + 3005, + app => { + app.all('/v1/hippies', (req, res) => { + res.json([ + { id: 1, name: 'farah' }, + { id: 2, name: 'fred' }, + ]); + }); + app.get('/v1/hippies/1', (req, res) => { + res.json({ id: 1, name: 'farah' }); + }); + app.get('/v1/pets/1', (req, res) => { + res.json({ id: 1, name: 'sparky' }); + }); + }, + ); + basePath = app.basePath; + }); + + after(() => app.server.close()); + + it('should ignore path and return 200, rather than validate', async () => + request(app) + .get(`${basePath}/hippies`) + .query({ + test: 'one', + limit: 2, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200)); + + it('should ignore path and return 200, rather than validate', async () => + request(app) + .post(`${basePath}/hippies?test`) + .query({ + test: 'one', + limit: 2, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200)); + + it('should not ignore path and return 404', async () => + request(app) + .get(`${basePath}/hippies/1`) + .query({ + test: 'one', + limit: 2, + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(404)); + + describe(`GET ${basePath}/pets/:id`, () => { + it('should validate a path within the base path that is not ignored', async () => { + const id = 'my_id'; + return request(app) + .get(`${basePath}/pets/${id}`) + .expect(400) + .then(r => { + const e = r.body.errors; + expect(e[0].path).contains('id'); + expect(e[0].message).equals('should be integer'); + }); + }); + + it('should validate a route defined in openapi but not express with invalid params', async () => + request(app) + .get(`${basePath}/route_defined_in_openapi_only`) + .expect(400) + .then(r => { + const e = r.body.errors; + expect(e[0].message).to.equal("should have required property 'id'"); + })); + + it('should return 404 if route is defined in openapi but not express and params are valid', async () => + request(app) + .get(`${basePath}/route_defined_in_openapi_only`) + .query({ id: 123 }) + .expect(404) + .then(r => { + const e = r.body; + console.log(e) + // There is no route defined by express, hence the validator verifies parameters, + // then it fails over to the express error handler. In this case returns empty + expect(e).to.be.empty; + })); + }); +});