fastify / fastify/ajv-compiler

Allow Ajv 2019 and 2020 as options

Open
#151 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.