@@ -9,8 +9,8 @@ const { randomUUID } = require('crypto')
99
1010const validate = require ( './schema-validator' )
1111const Serializer = require ( './serializer' )
12+ const Validator = require ( './validator' )
1213const RefResolver = require ( './ref-resolver' )
13- const buildAjv = require ( './ajv' )
1414
1515let largeArraySize = 2e4
1616let largeArrayMechanism = 'default'
@@ -75,51 +75,31 @@ const arrayItemsReferenceSerializersMap = new Map()
7575const objectReferenceSerializersMap = new Map ( )
7676
7777let rootSchemaId = null
78- let ajvInstance = null
7978let refResolver = null
79+ let validator = null
8080let contextFunctions = null
8181
8282function build ( schema , options ) {
83- schema = clone ( schema )
84-
8583 arrayItemsReferenceSerializersMap . clear ( )
8684 objectReferenceSerializersMap . clear ( )
8785
8886 contextFunctions = [ ]
8987 options = options || { }
9088
91- ajvInstance = buildAjv ( options . ajv )
9289 refResolver = new RefResolver ( )
90+ validator = new Validator ( options . ajv )
91+
9392 rootSchemaId = schema . $id || randomUUID ( )
9493
9594 isValidSchema ( schema )
96- extendDateTimeType ( schema )
97- ajvInstance . addSchema ( schema , rootSchemaId )
95+ validator . addSchema ( schema , rootSchemaId )
9896 refResolver . addSchema ( schema , rootSchemaId )
9997
10098 if ( options . schema ) {
101- const externalSchemas = clone ( options . schema )
102-
103- for ( const key of Object . keys ( externalSchemas ) ) {
104- const externalSchema = externalSchemas [ key ]
105- isValidSchema ( externalSchema , key )
106- extendDateTimeType ( externalSchema )
107-
108- let schemaKey = externalSchema . $id || key
109- if ( externalSchema . $id !== undefined && externalSchema . $id [ 0 ] === '#' ) {
110- schemaKey = key + externalSchema . $id // relative URI
111- }
112-
113- if ( refResolver . getSchema ( schemaKey ) === undefined ) {
114- refResolver . addSchema ( externalSchema , key )
115- }
116-
117- if (
118- ajvInstance . refs [ schemaKey ] === undefined &&
119- ajvInstance . schemas [ schemaKey ] === undefined
120- ) {
121- ajvInstance . addSchema ( externalSchema , schemaKey )
122- }
99+ for ( const key of Object . keys ( options . schema ) ) {
100+ isValidSchema ( options . schema [ key ] , key )
101+ validator . addSchema ( options . schema [ key ] , key )
102+ refResolver . addSchema ( options . schema [ key ] , key )
123103 }
124104 }
125105
@@ -160,28 +140,28 @@ function build (schema, options) {
160140 return main
161141 `
162142
163- const dependenciesName = [ 'ajv ' , 'serializer' , contextFunctionCode ]
143+ const dependenciesName = [ 'validator ' , 'serializer' , contextFunctionCode ]
164144
165145 if ( options . debugMode ) {
166146 options . mode = 'debug'
167147 }
168148
169149 if ( options . mode === 'debug' ) {
170- return { code : dependenciesName . join ( '\n' ) , ajv : ajvInstance }
150+ return { code : dependenciesName . join ( '\n' ) , validator }
171151 }
172152
173153 if ( options . mode === 'standalone' ) {
174154 // lazy load
175155 const buildStandaloneCode = require ( './standalone' )
176- return buildStandaloneCode ( options , ajvInstance , contextFunctionCode )
156+ return buildStandaloneCode ( options , validator , contextFunctionCode )
177157 }
178158
179159 /* eslint no-new-func: "off" */
180- const contextFunc = new Function ( 'ajv ' , 'serializer' , contextFunctionCode )
181- const stringifyFunc = contextFunc ( ajvInstance , serializer )
160+ const contextFunc = new Function ( 'validator ' , 'serializer' , contextFunctionCode )
161+ const stringifyFunc = contextFunc ( validator , serializer )
182162
183- ajvInstance = null
184163 refResolver = null
164+ validator = null
185165 rootSchemaId = null
186166 contextFunctions = null
187167 arrayItemsReferenceSerializersMap . clear ( )
@@ -345,9 +325,8 @@ function buildCode (location) {
345325 const propertiesLocation = mergeLocation ( location , 'properties' )
346326 Object . keys ( schema . properties || { } ) . forEach ( ( key ) => {
347327 let propertyLocation = mergeLocation ( propertiesLocation , key )
348- if ( schema . properties [ key ] . $ref ) {
349- propertyLocation = resolveRef ( location , schema . properties [ key ] . $ref )
350- schema . properties [ key ] = propertyLocation . schema
328+ if ( propertyLocation . $ref ) {
329+ propertyLocation = resolveRef ( location , propertyLocation . $ref )
351330 }
352331
353332 // Using obj['key'] !== undefined instead of obj.hasOwnProperty(prop) for perf reasons,
@@ -364,7 +343,7 @@ function buildCode (location) {
364343
365344 code += buildValue ( propertyLocation , `obj[${ JSON . stringify ( key ) } ]` )
366345
367- const defaultValue = schema . properties [ key ] . default
346+ const defaultValue = propertyLocation . schema . default
368347 if ( defaultValue !== undefined ) {
369348 code += `
370349 } else {
@@ -479,24 +458,14 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
479458 mergedSchema . anyOf . push ( ...allOfSchema . anyOf )
480459 }
481460
482- if ( allOfSchema . fjs_type !== undefined ) {
483- if (
484- mergedSchema . fjs_type !== undefined &&
485- mergedSchema . fjs_type !== allOfSchema . fjs_type
486- ) {
487- throw new Error ( 'allOf schemas have different fjs_type values' )
488- }
489- mergedSchema . fjs_type = allOfSchema . fjs_type
490- }
491-
492461 if ( allOfSchema . allOf !== undefined ) {
493462 mergeAllOfSchema ( location , allOfSchema , mergedSchema )
494463 }
495464 }
496465 delete mergedSchema . allOf
497466
498467 mergedSchema . $id = `merged_${ randomUUID ( ) } `
499- ajvInstance . addSchema ( mergedSchema )
468+ validator . addSchema ( mergedSchema )
500469 refResolver . addSchema ( mergedSchema )
501470 location . schemaId = mergedSchema . $id
502471 location . jsonPointer = '#'
@@ -526,7 +495,7 @@ function addIfThenElse (location) {
526495 const ifSchemaRef = ifLocation . schemaId + ifLocation . jsonPointer
527496
528497 let code = `
529- if (ajv .validate("${ ifSchemaRef } ", obj)) {
498+ if (validator .validate("${ ifSchemaRef } ", obj)) {
530499 `
531500
532501 const thenLocation = mergeLocation ( location , 'then' )
@@ -800,22 +769,26 @@ function buildValue (location, input) {
800769 location . schema = mergedSchema
801770 }
802771
803- let type = schema . type
772+ const type = schema . type
804773 const nullable = schema . nullable === true
805774
806775 let code = ''
807776 let funcName
808777
809- if ( schema . fjs_type === 'string' && schema . format === undefined && Array . isArray ( schema . type ) && schema . type . length === 2 ) {
810- type = 'string'
811- }
812-
813778 switch ( type ) {
814779 case 'null' :
815780 code += 'json += serializer.asNull()'
816781 break
817782 case 'string' : {
818- funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
783+ if ( schema . format === 'date-time' ) {
784+ funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
785+ } else if ( schema . format === 'date' ) {
786+ funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
787+ } else if ( schema . format === 'time' ) {
788+ funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
789+ } else {
790+ funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
791+ }
819792 code += `json += ${ funcName } (${ input } )`
820793 break
821794 }
@@ -832,15 +805,7 @@ function buildValue (location, input) {
832805 code += `json += ${ funcName } (${ input } )`
833806 break
834807 case 'object' :
835- if ( schema . format === 'date-time' ) {
836- funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
837- } else if ( schema . format === 'date' ) {
838- funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
839- } else if ( schema . format === 'time' ) {
840- funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
841- } else {
842- funcName = buildObject ( location )
843- }
808+ funcName = buildObject ( location )
844809 code += `json += ${ funcName } (${ input } )`
845810 break
846811 case 'array' :
@@ -858,7 +823,7 @@ function buildValue (location, input) {
858823 const schemaRef = optionLocation . schemaId + optionLocation . jsonPointer
859824 const nestedResult = buildValue ( optionLocation , input )
860825 code += `
861- ${ index === 0 ? 'if' : 'else if' } (ajv .validate("${ schemaRef } ", ${ input } ))
826+ ${ index === 0 ? 'if' : 'else if' } (validator .validate("${ schemaRef } ", ${ input } ))
862827 ${ nestedResult }
863828 `
864829 }
@@ -872,7 +837,7 @@ function buildValue (location, input) {
872837 `
873838 } else if ( 'const' in schema ) {
874839 code += `
875- if(ajv .validate(${ JSON . stringify ( schema ) } , ${ input } ))
840+ if(validator .validate(${ JSON . stringify ( schema ) } , ${ input } ))
876841 json += '${ JSON . stringify ( schema . const ) } '
877842 else
878843 throw new Error(\`Item $\{JSON.stringify(${ input } )} does not match schema definition.\`)
@@ -906,7 +871,7 @@ function buildValue (location, input) {
906871 switch ( type ) {
907872 case 'string' : {
908873 code += `
909- ${ statement } (${ input } === null || typeof ${ input } === "${ type } " || ${ input } instanceof RegExp || (typeof ${ input } === "object" && Object.hasOwnProperty.call(${ input } , "toString")))
874+ ${ statement } (${ input } === null || typeof ${ input } === "${ type } " || ${ input } instanceof RegExp || ${ input } instanceof Date || (typeof ${ input } === "object" && Object.hasOwnProperty.call(${ input } , "toString")))
910875 ${ nestedResult }
911876 `
912877 break
@@ -926,17 +891,10 @@ function buildValue (location, input) {
926891 break
927892 }
928893 case 'object' : {
929- if ( schema . fjs_type ) {
930- code += `
931- ${ statement } (${ input } instanceof Date || ${ input } === null)
932- ${ nestedResult }
933- `
934- } else {
935- code += `
936- ${ statement } (typeof ${ input } === "object" || ${ input } === null)
937- ${ nestedResult }
938- `
939- }
894+ code += `
895+ ${ statement } (typeof ${ input } === "object" || ${ input } === null)
896+ ${ nestedResult }
897+ `
940898 break
941899 }
942900 default : {
@@ -965,30 +923,6 @@ function buildValue (location, input) {
965923 return code
966924}
967925
968- // Ajv does not support js date format. In order to properly validate objects containing a date,
969- // it needs to replace all occurrences of the string date format with a custom keyword fjs_type.
970- // (see https://github.com/fastify/fast-json-stringify/pull/441)
971- function extendDateTimeType ( schema ) {
972- if ( schema === null ) return
973-
974- if ( schema . type === 'string' ) {
975- schema . fjs_type = 'string'
976- schema . type = [ 'string' , 'object' ]
977- } else if (
978- Array . isArray ( schema . type ) &&
979- schema . type . includes ( 'string' ) &&
980- ! schema . type . includes ( 'object' )
981- ) {
982- schema . fjs_type = 'string'
983- schema . type . push ( 'object' )
984- }
985- for ( const property in schema ) {
986- if ( typeof schema [ property ] === 'object' ) {
987- extendDateTimeType ( schema [ property ] )
988- }
989- }
990- }
991-
992926function isEmpty ( schema ) {
993927 // eslint-disable-next-line
994928 for ( var key in schema ) {
@@ -1003,9 +937,9 @@ module.exports = build
1003937
1004938module . exports . validLargeArrayMechanisms = validLargeArrayMechanisms
1005939
1006- module . exports . restore = function ( { code, ajv } ) {
940+ module . exports . restore = function ( { code, validator } ) {
1007941 const serializer = new Serializer ( )
1008942 // eslint-disable-next-line
1009- return ( Function . apply ( null , [ 'ajv ' , 'serializer' , code ] )
1010- . apply ( null , [ ajv , serializer ] ) )
943+ return ( Function . apply ( null , [ 'validator ' , 'serializer' , code ] )
944+ . apply ( null , [ validator , serializer ] ) )
1011945}
0 commit comments