import Ajv , { ErrorObject as AjvErrorObject , Schema as AjvSchema , ValidateFunction as AjvValidationFunction , } from "ajv" ; import { APIValidatorRequest , APIValidatorError , APIValidator , } from "./apiValidator" ; export type AjvAPIValidatorConfig = { mutateWithDefaultValues : boolean ; allowUnsupportedRoute : boolean ; } ; export class AjvAPIValidator < T extends AjvSchema > implements APIValidator { private readonly validator : AjvValidationFunction ; private readonly config : AjvAPIValidatorConfig ; constructor ( config , schema ) { this . config = config ; const ajv = new Ajv ( { allowUnionTypes : true , useDefaults : config . mutateWithDefaultValues , coerceTypes : "array" , verbose : true , } ) ; this . validator = ajv . compile < T > ( schema ) ; } validateRequest ( request ) { return this . validate ( request , undefined ) ; } validateResponse ( request , response ) { return this . validate ( request , response ) ; } private validate ( { method , path , contentType , query , body , params } , response ) { const nullIfEmpty = ( obj ) => { if ( ! obj ) return null ; if ( Object . keys ( obj ) . length === 0 ) return null ; return obj ; } ; const isValid = this . validator ( { [ path ] : { [ method ] : { ... ( method === "GET" ? { query } : { body : body } ) , ... ( nullIfEmpty ( params ) && { params } ) , ... ( contentType && { contentType } ) , response , } , } , } ) ; if ( isValid || ( this . config . allowUnsupportedRoute && this . _isUnsupportedRoute ( ) ) ) { return true ; } else if ( this . validator . errors ) { return { errors : this . validator . errors . map ( this . _createAPIValidationError ) , } ; } else { return { errors : [ { message : "An unknown validation error occurred" } ] , } ; } } private _isUnsupportedRoute ( ) { if ( this . validator . errors ) { return ( this . validator . errors [ 0 ] . instancePath === "" && this . validator . errors [ 0 ] . schemaPath == "#/additionalProperties" ) ; } return false ; } private _createAPIValidationError ( error ) { const splitInstancePath = error . instancePath . split ( "/" ) ; const isolatedInstancePath = splitInstancePath . slice ( Math . min ( splitInstancePath . length - 1 , 3 ) ) . join ( "/" ) ; return { message : `${ isolatedInstancePath } ${ error . message }` , data : error . data , } ; } }