Skip to content

Allow Ajv 2019 and 2020 as options #151

@jmjf

Description

@jmjf

Prerequisites

  • I have written a descriptive issue title
  • 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.

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: falsetounevaluatedProperties: 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"}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions