Skip to content

Commit

Permalink
feat: provide custom ajv instance (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
dancastillo committed Jul 2, 2023
1 parent 7ab6e66 commit 4478f52
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 19 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
42 changes: 23 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

Expand Down
78 changes: 78 additions & 0 deletions test/ajv.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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\'')
})

0 comments on commit 4478f52

Please sign in to comment.