fastify / fastify/ajv-compiler
Allow Ajv 2019 and 2020 as options
- Dominant language
- JavaScript
- Stars
- 24
- Forks
- 14
- PR merge metrics
- No merged PRs in 30d
Description
### Prerequisites
- [x] I have written a descriptive issue title
- [x] I have searched existing issues to ensure the feature has not already been requested
### 🚀 Feature Proposal
## Feature
Allow `ValidatorCompiler` to create compilers with Ajv2019 and Ajv2020.
### Proposed approach
Use `options.mode` to select the version of Ajv to use, consistent with the current API. This choice avoids breaking changes for existing users while letting those who want 2019 or 2020 to use them.
It also means, you can choose 2019 and 2020 for Fastify using an existing option (no changes to Fastify required).
`const fastify = Fastify( { ajv: { mode: '2019' } } )`
### Note on performance
Using the example code without `additionalProperties` and `unevaluatedProperties`...
`npx autocannon -c 100 -d 5 -p 10 "http://localhost:3000/?s1Prop1=first&extra=hello"`
reports average req/sec:
- 52,118.4 (Ajv)
- 53,372.8 (Ajv2019)
- 53,161.6 (Ajv2020)
With unevaluatedProperties:
- -- (Ajv; doesn't support)
- 53,129.6 (Ajv2019)
- 53,827.2 (Ajv2020)
These results show Ajv2019 and Ajv2020 are not always slower than plain Ajv. Measure you use case and choose an answer based on data that applies to it, not unquantified generalizations.
### Motivation
When using combining keywords like `allOf`, `anyOf`, and `oneOf` the `additionalProperties` keyword causes empty query results. See examples below.
The solution to this problem is `unevaluatedProperties`, which requires Ajv2019 or Ajv2020.
### Example
Example 1 shows `additionalProperties` does not work with default Ajv.
```javascript
import Fastify from 'fastify'
const fastify = Fastify( { ajv: { mode: undefined } } )
const opts = {
schema: {
querystring: {
type: 'object',
additionalProperties: false,
allOf: [
{
oneOf: [
{
type: 'object',
properties: {
s1Prop1: { type: 'string' },
s1Prop2: { type: 'string', format: 'date' }
},
required: ['s1Prop1'],
},
{
type: 'object',
properties: {
s2Prop1: { type: 'boolean' },
s2Prop2: { type: 'string', format: 'date' }
},
required: ['s2Prop1'],
},
],
},
{
type: 'object',
properties: {
extra: { type: 'string' }
},
required: ['extra']
}
]
},
}
}
fastify.get( '/', opts, ( request, reply ) => {
reply.send( { params: request.query } ) // echo the querystring
} )
fastify.listen( { port: 3000 }, ( err ) => {
if ( err ) throw err
} )
```
`curl "localhost:3000/?s1Prop1=first&extra=hello"`
-> `{"params":{}}`
`curl "localhost:3000/?s1Prop1=first"`
-> `{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"querystring must have required property 'extra'"}`
`curl "localhost:3000/?s1Prop1=first&extra=hello&more=hi"`
-> `{"params":{}}`
## Example 2
With proposed changes to allow selecting 2019 or 2020:
- Change `mode: undefined` to `mode: '2019'` (or '2020')
- Change 'additionalProperties: false` to `unevaluatedProperties: false`
`curl "localhost:3000/?s1Prop1=first&extra=hello"`
-> `{"params":{"s1Prop1":"first","extra":"hello"}}`
`curl "localhost:3000/?s1Prop1=first"`
-> `{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"querystring must have required property 'extra'"}`
`curl "localhost:3000/?s1Prop1=first&extra=hello&more=hi"`
-> `{"statusCode":400,"code":"FST_ERR_VALIDATION","error":"Bad Request","message":"querystring must NOT have unevaluated properties"}`
Contributor guide
Assessment
This issue has not been assessed yet.