From 4478f52024a217c22858a81b1372f793e974f6de Mon Sep 17 00:00:00 2001 From: Dan Castillo Date: Sun, 2 Jul 2023 02:27:57 -0400 Subject: [PATCH] feat: provide custom ajv instance (#94) --- README.md | 17 +++++++++++ index.js | 42 ++++++++++++++------------ test/ajv.test.js | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 69c4abe..890c446 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,23 @@ await fastify.register(responseValidator, { }) ``` +You can also pass in an instance of ajv +```js +// Default configuration: +// coerceTypes: false +// useDefaults: true +// removeAdditional: true +// allErrors: true + +import responseValidator from '@fastify/response-validation' +import Ajv from 'ajv' + +// ... App setup + +const ajv = new Ajv() +await fastify.register(responseValidator, { ajv }) +``` + By default the response validation is enabled on every route that has a response schema defined. If needed you can disable it all together with `responseValidation: false`: ```js import responseValidator from '@fastify/response-validation' diff --git a/index.js b/index.js index 979e160..deb6179 100644 --- a/index.js +++ b/index.js @@ -4,26 +4,30 @@ const fp = require('fastify-plugin') const Ajv = require('ajv') function fastifyResponseValidation (fastify, opts, next) { - const { plugins: ajvPlugins, ...ajvOptions } = Object.assign({ - coerceTypes: false, - useDefaults: true, - removeAdditional: true, - allErrors: true, - plugins: [] - }, opts.ajv) - - if (!Array.isArray(ajvPlugins)) { - next(new Error(`ajv.plugins option should be an array, instead got '${typeof ajvPlugins}'`)) - return - } - - const ajv = new Ajv(ajvOptions) + let ajv + if (opts.ajv && opts.ajv instanceof Ajv) { + ajv = opts.ajv + } else { + const { plugins: ajvPlugins, ...ajvOptions } = Object.assign({ + coerceTypes: false, + useDefaults: true, + removeAdditional: true, + allErrors: true, + plugins: [] + }, opts.ajv) + + if (!Array.isArray(ajvPlugins)) { + next(new Error(`ajv.plugins option should be an array, instead got '${typeof ajvPlugins}'`)) + return + } + ajv = new Ajv(ajvOptions) - for (const plugin of ajvPlugins) { - if (Array.isArray(plugin)) { - plugin[0](ajv, plugin[1]) - } else { - plugin(ajv) + for (const plugin of ajvPlugins) { + if (Array.isArray(plugin)) { + plugin[0](ajv, plugin[1]) + } else { + plugin(ajv) + } } } diff --git a/test/ajv.test.js b/test/ajv.test.js index d1044cc..a6f252f 100644 --- a/test/ajv.test.js +++ b/test/ajv.test.js @@ -3,6 +3,9 @@ const test = require('tap').test const Fastify = require('fastify') const plugin = require('..') +const Ajv = require('ajv') +const ajvFormats = require('ajv-formats') +const ajvErrors = require('ajv-errors') test('use ajv formats', async t => { const fastify = Fastify() @@ -98,3 +101,78 @@ test('should throw an error if ajv.plugins is number', async t => { const fastify = Fastify() t.rejects(fastify.register(plugin, { ajv: { plugins: 0 } }), 'ajv.plugins option should be an array, instead got \'number\'') }) + +test('use ajv formats with Ajv instance', async t => { + const fastify = Fastify() + const ajv = new Ajv() + ajvFormats(ajv) + await fastify.register(plugin, { ajv }) + + fastify.route({ + method: 'GET', + url: '/', + schema: { + response: { + '2xx': { + type: 'object', + properties: { + answer: { type: 'number', format: 'float' } + } + } + } + }, + handler: async (req, reply) => { + return { answer: 2.4 } + } + }) + + const response = await fastify.inject({ + method: 'GET', + url: '/' + }) + + t.equal(response.statusCode, 200) + t.strictSame(JSON.parse(response.payload), { answer: 2.4 }) +}) + +test('use ajv errors with Ajv instance', async t => { + const fastify = Fastify() + const ajv = new Ajv({ allErrors: true }) + ajvErrors(ajv, { singleError: true }) + await fastify.register(plugin, { ajv }) + + fastify.route({ + method: 'GET', + url: '/', + schema: { + response: { + '2xx': { + type: 'object', + required: ['answer'], + properties: { + answer: { type: 'number' } + }, + additionalProperties: false, + errorMessage: 'should be an object with an integer property answer only' + } + } + }, + handler: async (req, reply) => { + return { notAnAnswer: 24 } + } + }) + + const response = await fastify.inject({ + method: 'GET', + url: '/' + }) + + t.equal(response.statusCode, 500) + t.equal(response.json().message, 'response should be an object with an integer property answer only') +}) + +test('should throw an error if ajv.plugins is not passed to instance and not array', async t => { + t.plan(1) + const fastify = Fastify() + t.rejects(fastify.register(plugin, { ajv: { plugins: 'invalid' } }), 'ajv.plugins option should be an array, instead got \'string\'') +})