@@ -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 , ajv : validator . ajv }
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 const sanitized = JSON . stringify ( key )
@@ -364,8 +343,7 @@ function buildCode (location) {
364343
365344 code += buildValue ( propertyLocation , `obj[${ JSON . stringify ( key ) } ]` )
366345
367- const defaultValue = schema . properties [ key ] . default
368-
346+ const defaultValue = propertyLocation . schema . default
369347 if ( defaultValue !== undefined ) {
370348 code += `
371349 } else {
@@ -480,24 +458,14 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
480458 mergedSchema . anyOf . push ( ...allOfSchema . anyOf )
481459 }
482460
483- if ( allOfSchema . fjs_type !== undefined ) {
484- if (
485- mergedSchema . fjs_type !== undefined &&
486- mergedSchema . fjs_type !== allOfSchema . fjs_type
487- ) {
488- throw new Error ( 'allOf schemas have different fjs_type values' )
489- }
490- mergedSchema . fjs_type = allOfSchema . fjs_type
491- }
492-
493461 if ( allOfSchema . allOf !== undefined ) {
494462 mergeAllOfSchema ( location , allOfSchema , mergedSchema )
495463 }
496464 }
497465 delete mergedSchema . allOf
498466
499467 mergedSchema . $id = `merged_${ randomUUID ( ) } `
500- ajvInstance . addSchema ( mergedSchema )
468+ validator . addSchema ( mergedSchema )
501469 refResolver . addSchema ( mergedSchema )
502470 location . schemaId = mergedSchema . $id
503471 location . jsonPointer = '#'
@@ -527,7 +495,7 @@ function addIfThenElse (location) {
527495 const ifSchemaRef = ifLocation . schemaId + ifLocation . jsonPointer
528496
529497 let code = `
530- if (ajv .validate("${ ifSchemaRef } ", obj)) {
498+ if (validator .validate("${ ifSchemaRef } ", obj)) {
531499 `
532500
533501 const thenLocation = mergeLocation ( location , 'then' )
@@ -801,16 +769,12 @@ function buildValue (location, input) {
801769 location . schema = mergedSchema
802770 }
803771
804- let type = schema . type
772+ const type = schema . type
805773 const nullable = schema . nullable === true || ( Array . isArray ( type ) && type . includes ( 'null' ) )
806774
807775 let code = ''
808776 let funcName
809777
810- if ( schema . fjs_type === 'string' && schema . format === undefined && Array . isArray ( schema . type ) && schema . type . length === 2 ) {
811- type = 'string'
812- }
813-
814778 if ( 'const' in schema ) {
815779 if ( nullable ) {
816780 code += `
@@ -827,7 +791,15 @@ function buildValue (location, input) {
827791 code += 'json += serializer.asNull()'
828792 break
829793 case 'string' : {
830- funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
794+ if ( schema . format === 'date-time' ) {
795+ funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
796+ } else if ( schema . format === 'date' ) {
797+ funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
798+ } else if ( schema . format === 'time' ) {
799+ funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
800+ } else {
801+ funcName = nullable ? 'serializer.asStringNullable.bind(serializer)' : 'serializer.asString.bind(serializer)'
802+ }
831803 code += `json += ${ funcName } (${ input } )`
832804 break
833805 }
@@ -844,15 +816,7 @@ function buildValue (location, input) {
844816 code += `json += ${ funcName } (${ input } )`
845817 break
846818 case 'object' :
847- if ( schema . format === 'date-time' ) {
848- funcName = nullable ? 'serializer.asDateTimeNullable.bind(serializer)' : 'serializer.asDateTime.bind(serializer)'
849- } else if ( schema . format === 'date' ) {
850- funcName = nullable ? 'serializer.asDateNullable.bind(serializer)' : 'serializer.asDate.bind(serializer)'
851- } else if ( schema . format === 'time' ) {
852- funcName = nullable ? 'serializer.asTimeNullable.bind(serializer)' : 'serializer.asTime.bind(serializer)'
853- } else {
854- funcName = buildObject ( location )
855- }
819+ funcName = buildObject ( location )
856820 code += `json += ${ funcName } (${ input } )`
857821 break
858822 case 'array' :
@@ -870,7 +834,7 @@ function buildValue (location, input) {
870834 const schemaRef = optionLocation . schemaId + optionLocation . jsonPointer
871835 const nestedResult = buildValue ( optionLocation , input )
872836 code += `
873- ${ index === 0 ? 'if' : 'else if' } (ajv .validate("${ schemaRef } ", ${ input } ))
837+ ${ index === 0 ? 'if' : 'else if' } (validator .validate("${ schemaRef } ", ${ input } ))
874838 ${ nestedResult }
875839 `
876840 }
@@ -882,6 +846,13 @@ function buildValue (location, input) {
882846 code += `
883847 json += JSON.stringify(${ input } )
884848 `
849+ } else if ( 'const' in schema ) {
850+ code += `
851+ if(validator.validate(${ JSON . stringify ( schema ) } , ${ input } ))
852+ json += '${ JSON . stringify ( schema . const ) } '
853+ else
854+ throw new Error(\`Item $\{JSON.stringify(${ input } )} does not match schema definition.\`)
855+ `
885856 } else if ( schema . type === undefined ) {
886857 code += `
887858 json += JSON.stringify(${ input } )
@@ -914,6 +885,7 @@ function buildValue (location, input) {
914885 ${ statement } (
915886 typeof ${ input } === "string" ||
916887 ${ input } === null ||
888+ ${ input } instanceof Date ||
917889 ${ input } instanceof RegExp ||
918890 (
919891 typeof ${ input } === "object" &&
@@ -941,17 +913,10 @@ function buildValue (location, input) {
941913 break
942914 }
943915 case 'object' : {
944- if ( schema . fjs_type ) {
945- code += `
946- ${ statement } (${ input } instanceof Date || ${ input } === null)
947- ${ nestedResult }
948- `
949- } else {
950- code += `
951- ${ statement } (typeof ${ input } === "object" || ${ input } === null)
952- ${ nestedResult }
953- `
954- }
916+ code += `
917+ ${ statement } (typeof ${ input } === "object" || ${ input } === null)
918+ ${ nestedResult }
919+ `
955920 break
956921 }
957922 default : {
@@ -980,30 +945,6 @@ function buildValue (location, input) {
980945 return code
981946}
982947
983- // Ajv does not support js date format. In order to properly validate objects containing a date,
984- // it needs to replace all occurrences of the string date format with a custom keyword fjs_type.
985- // (see https://github.com/fastify/fast-json-stringify/pull/441)
986- function extendDateTimeType ( schema ) {
987- if ( schema === null ) return
988-
989- if ( schema . type === 'string' ) {
990- schema . fjs_type = 'string'
991- schema . type = [ 'string' , 'object' ]
992- } else if (
993- Array . isArray ( schema . type ) &&
994- schema . type . includes ( 'string' ) &&
995- ! schema . type . includes ( 'object' )
996- ) {
997- schema . fjs_type = 'string'
998- schema . type . push ( 'object' )
999- }
1000- for ( const property in schema ) {
1001- if ( typeof schema [ property ] === 'object' ) {
1002- extendDateTimeType ( schema [ property ] )
1003- }
1004- }
1005- }
1006-
1007948function isEmpty ( schema ) {
1008949 // eslint-disable-next-line
1009950 for ( var key in schema ) {
@@ -1018,9 +959,9 @@ module.exports = build
1018959
1019960module . exports . validLargeArrayMechanisms = validLargeArrayMechanisms
1020961
1021- module . exports . restore = function ( { code, ajv } ) {
962+ module . exports . restore = function ( { code, validator } ) {
1022963 const serializer = new Serializer ( )
1023964 // eslint-disable-next-line
1024- return ( Function . apply ( null , [ 'ajv ' , 'serializer' , code ] )
1025- . apply ( null , [ ajv , serializer ] ) )
965+ return ( Function . apply ( null , [ 'validator ' , 'serializer' , code ] )
966+ . apply ( null , [ validator , serializer ] ) )
1026967}
0 commit comments