diff --git a/src/framework/types.ts b/src/framework/types.ts index 41733a9c..80a95d45 100644 --- a/src/framework/types.ts +++ b/src/framework/types.ts @@ -560,6 +560,14 @@ interface ErrorHeaders { Allow?: string; } +export class CustomError extends Error { + error!: any; + constructor(error: any) { + super(error.message || "Custom Error"); + this.error = error; + } +} + export class HttpError extends Error implements ValidationError { status!: number; path?: string; diff --git a/src/index.ts b/src/index.ts index 60a8b949..8135fce6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import * as res from './resolvers'; import { OpenApiValidator, OpenApiValidatorOpts } from './openapi.validator'; import { OpenApiSpecLoader } from './framework/openapi.spec.loader'; import { + CustomError, InternalServerError, UnsupportedMediaType, RequestEntityTooLarge, @@ -18,6 +19,7 @@ import { export const resolvers = res; export const middleware = openapiValidator; export const error = { + CustomError, InternalServerError, UnsupportedMediaType, RequestEntityTooLarge, diff --git a/src/middlewares/openapi.security.ts b/src/middlewares/openapi.security.ts index b3dbfe98..059512ef 100644 --- a/src/middlewares/openapi.security.ts +++ b/src/middlewares/openapi.security.ts @@ -6,6 +6,7 @@ import { OpenApiRequestHandler, InternalServerError, HttpError, + CustomError, } from '../framework/types'; const defaultSecurityHandler = ( @@ -20,7 +21,7 @@ type SecuritySchemesMap = { interface SecurityHandlerResult { success: boolean; status?: number; - error?: string; + error?: string | Error; } export function security( apiDoc: OpenAPIV3.Document, @@ -108,12 +109,19 @@ export function security( throw firstError; } } catch (e) { - const message = e?.error?.message || 'unauthorized'; - const err = HttpError.create({ - status: e.status, - path: path, - message: message, - }); + let err: Error; + + // Pass a custom Error instance to next if available + if (e?.error instanceof CustomError) { + err = e.error.error; + } else { + const message = e?.error?.message || 'unauthorized'; + err = HttpError.create({ + status: e.status, + path: path, + message: message, + }); + } /*const err = e.status == 500 ? new InternalServerError({ path: path, message: message }) diff --git a/test/common/app.ts b/test/common/app.ts index cbefea40..48a8b74b 100644 --- a/test/common/app.ts +++ b/test/common/app.ts @@ -8,6 +8,18 @@ import * as OpenApiValidator from '../../src'; import { startServer, routes } from './app.common'; import { OpenApiValidatorOpts } from '../../src/framework/types'; +export class JsonResponse { + public status: number; + public message: string; + public data: any; + + constructor(status: number, message: string, data: any) { + this.status = status; + this.message = message; + this.data = data; + } +} + export async function createApp( opts?: OpenApiValidatorOpts, port = 3000, @@ -31,6 +43,16 @@ export async function createApp( app.use(OpenApiValidator.middleware(opts)); + // Add custom error handler + app.use((err, req, res, next) => { + if (err instanceof JsonResponse) { + res.status(err.status); + res.json(JSON.parse(JSON.stringify(err, null, 2))); + } else { + next(err); + } + }); + if (useRoutes) { // register common routes routes(app); diff --git a/test/security.handlers.spec.ts b/test/security.handlers.spec.ts index 209f16d1..9db5481e 100644 --- a/test/security.handlers.spec.ts +++ b/test/security.handlers.spec.ts @@ -2,11 +2,12 @@ import * as path from 'path'; import * as express from 'express'; import { expect } from 'chai'; import * as request from 'supertest'; -import { createApp } from './common/app'; +import { createApp, JsonResponse } from './common/app'; import { OpenApiValidatorOpts, ValidateSecurityOpts, OpenAPIV3, + CustomError, } from '../src/framework/types'; // NOTE/TODO: These tests modify eovConf.validateSecurity.handlers @@ -67,6 +68,34 @@ describe('security.handlers', () => { ); })); + it('should return Custom Error for the security validation is provided', async () => { + + const validateSecurity = eovConf.validateSecurity; + validateSecurity.handlers.ApiKeyAuth = function (req, scopes, schema) { + const jsonResponse = new JsonResponse(418, "Just Kidding", { + data1: true, + data2: "false", + data3: null + }); + throw new CustomError(jsonResponse); + }; + + return request(app) + .get(`${basePath}/api_key`) + .set('X-API-Key', 'test') + .expect(418) + .then((r) => { + const body = r.body; + expect(body.status).to.equal(418); + expect(body.message).to.equal("Just Kidding"); + expect(body.data).to.deep.equal({ + data1: true, + data2: "false", + data3: null + }) + }); + }); + it('should return 401 if apikey handler returns false', async () => { const validateSecurity = eovConf.validateSecurity; validateSecurity.handlers.ApiKeyAuth = function (req, scopes, schema) {