Skip to content

Commit 8182ee1

Browse files
refactor: top level schema handler (#446)
1 parent 62fe5fc commit 8182ee1

File tree

2 files changed

+44
-75
lines changed

2 files changed

+44
-75
lines changed

index.js

Lines changed: 43 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,6 @@ function build (schema, options) {
265265

266266
const serializer = new Serializer(options)
267267

268-
/* eslint no-new-func: "off" */
269-
let code = '\'use strict\''
270-
271268
let location = {
272269
schema,
273270
root: schema,
@@ -283,50 +280,26 @@ function build (schema, options) {
283280
schema.type = inferTypeByKeyword(schema)
284281
}
285282

286-
let main
287-
288-
switch (schema.type) {
289-
case 'object':
290-
main = '$main'
291-
code = buildObject(location, code, main, main)
292-
break
293-
case 'array':
294-
main = '$main'
295-
code = buildArray(location, code, main, main)
296-
schema = location.schema
297-
break
298-
case 'string':
299-
switch (schema.format) {
300-
case 'date-time': return schema.nullable ? serializer.asDatetimeNullable.bind(serializer) : serializer.asDatetime.bind(serializer)
301-
case 'date': return schema.nullable ? serializer.asDateNullable.bind(serializer) : serializer.asDate.bind(serializer)
302-
case 'time': return schema.nullable ? serializer.asTimeNullable.bind(serializer) : serializer.asTime.bind(serializer)
303-
default: return schema.nullable ? serializer.asStringNullable.bind(serializer) : serializer.asString.bind(serializer)
304-
}
305-
case 'integer':
306-
return schema.nullable ? serializer.asIntegerNullable.bind(serializer) : serializer.asInteger.bind(serializer)
307-
case 'number':
308-
return schema.nullable ? serializer.asNumberNullable.bind(serializer) : serializer.asNumber.bind(serializer)
309-
case 'boolean':
310-
return schema.nullable ? serializer.asBooleanNullable.bind(serializer) : serializer.asBoolean.bind(serializer)
311-
case 'null':
312-
return serializer.asNull.bind(serializer)
313-
case undefined:
314-
return serializer.asAny.bind(serializer)
315-
default:
316-
throw new Error(`${schema.type} unsupported`)
317-
}
318-
319-
code += `
320-
return ${main}
321-
`
283+
const { code, laterCode } = buildValue('', 'main', 'input', location, false)
284+
const contextFunctionCode = `
285+
'use strict'
286+
function main (input) {
287+
let json = ''
288+
${code}
289+
return json
290+
}
291+
${laterCode}
292+
return main
293+
`
322294

323-
const dependenciesName = ['ajv', 'serializer', code]
295+
const dependenciesName = ['ajv', 'serializer', contextFunctionCode]
324296

325297
if (options.debugMode) {
326298
return { code: dependenciesName.join('\n'), ajv: ajvInstance }
327299
}
328300

329-
const contextFunc = new Function('ajv', 'serializer', code)
301+
/* eslint no-new-func: "off" */
302+
const contextFunc = new Function('ajv', 'serializer', contextFunctionCode)
330303
const stringifyFunc = contextFunc(ajvInstance, serializer)
331304

332305
ajvInstance = null
@@ -752,7 +725,7 @@ function buildCode (location, code, laterCode, locationPath) {
752725
json += ${asString} + ':'
753726
`
754727

755-
const result = nested(laterCode, locationPath, key, mergeLocation(propertyLocation, { schema: schema.properties[key] }), undefined, false)
728+
const result = buildValue(laterCode, locationPath + key, `obj[${JSON.stringify(key)}]`, mergeLocation(propertyLocation, { schema: schema.properties[key] }), false)
756729
code += result.code
757730
laterCode = result.laterCode
758731

@@ -937,7 +910,7 @@ function buildObject (location, code, functionName, locationPath) {
937910
return code
938911
}
939912

940-
function buildArray (location, code, functionName, locationPath, key = null) {
913+
function buildArray (location, code, functionName, locationPath, isObjectProperty = false) {
941914
let schema = location.schema
942915
if (schema.$id !== undefined) {
943916
schemaReferenceMap.set(schema.$id, schema)
@@ -984,7 +957,7 @@ function buildArray (location, code, functionName, locationPath, key = null) {
984957
const accessor = '[i]'
985958
if (Array.isArray(schema.items)) {
986959
result = schema.items.reduce((res, item, i) => {
987-
const tmpRes = nested(laterCode, locationPath, accessor, mergeLocation(location, { schema: item }), i, true)
960+
const tmpRes = buildValue(laterCode, locationPath + accessor + i, 'obj[i]', mergeLocation(location, { schema: item }), true)
988961
const condition = `i === ${i} && ${buildArrayTypeCondition(item.type, accessor)}`
989962
return {
990963
code: `${res.code}
@@ -997,7 +970,7 @@ function buildArray (location, code, functionName, locationPath, key = null) {
997970
}, result)
998971

999972
if (schema.additionalItems) {
1000-
const tmpRes = nested(laterCode, locationPath, accessor, mergeLocation(location, { schema: schema.items }), undefined, true)
973+
const tmpRes = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }), true)
1001974
result.code += `
1002975
else if (i >= ${schema.items.length}) {
1003976
${tmpRes.code}
@@ -1011,13 +984,13 @@ function buildArray (location, code, functionName, locationPath, key = null) {
1011984
}
1012985
`
1013986
} else {
1014-
result = nested(laterCode, locationPath, accessor, mergeLocation(location, { schema: schema.items }), undefined, true)
987+
result = buildValue(laterCode, locationPath + accessor, 'obj[i]', mergeLocation(location, { schema: schema.items }), true)
1015988
}
1016989

1017-
if (key) {
990+
if (isObjectProperty) {
1018991
code += `
1019992
if(!Array.isArray(obj)) {
1020-
throw new TypeError(\`Property '${key}' should be of type array, received '$\{obj}' instead.\`)
993+
throw new TypeError(\`The value '$\{obj}' does not match schema definition.\`)
1021994
}
1022995
`
1023996
}
@@ -1118,9 +1091,7 @@ function generateFuncName () {
11181091
return 'anonymous' + genFuncNameCounter++
11191092
}
11201093

1121-
function nested (laterCode, locationPath, key, location, subKey, isArray) {
1122-
subKey = subKey || ''
1123-
1094+
function buildValue (laterCode, locationPath, input, location, isArray) {
11241095
let schema = location.schema
11251096

11261097
if (schema.$ref) {
@@ -1137,8 +1108,6 @@ function nested (laterCode, locationPath, key, location, subKey, isArray) {
11371108
const type = schema.type
11381109
const nullable = schema.nullable === true
11391110

1140-
const accessor = isArray ? key : `[${JSON.stringify(key)}]`
1141-
11421111
let code = ''
11431112
let funcName
11441113

@@ -1150,42 +1119,42 @@ function nested (laterCode, locationPath, key, location, subKey, isArray) {
11501119
break
11511120
case 'string': {
11521121
funcName = getStringSerializer(schema.format, nullable)
1153-
code += `json += ${funcName}(obj${accessor})`
1122+
code += `json += ${funcName}(${input})`
11541123
break
11551124
}
11561125
case 'integer':
11571126
funcName = nullable ? 'serializer.asIntegerNullable.bind(serializer)' : 'serializer.asInteger.bind(serializer)'
1158-
code += `json += ${funcName}(obj${accessor})`
1127+
code += `json += ${funcName}(${input})`
11591128
break
11601129
case 'number':
11611130
funcName = nullable ? 'serializer.asNumberNullable.bind(serializer)' : 'serializer.asNumber.bind(serializer)'
1162-
code += `json += ${funcName}(obj${accessor})`
1131+
code += `json += ${funcName}(${input})`
11631132
break
11641133
case 'boolean':
11651134
funcName = nullable ? 'serializer.asBooleanNullable.bind(serializer)' : 'serializer.asBoolean.bind(serializer)'
1166-
code += `json += ${funcName}(obj${accessor})`
1135+
code += `json += ${funcName}(${input})`
11671136
break
11681137
case 'object':
11691138
funcName = generateFuncName()
1170-
laterCode = buildObject(location, laterCode, funcName, locationPath + key + subKey)
1171-
code += `json += ${funcName}(obj${accessor})`
1139+
laterCode = buildObject(location, laterCode, funcName, locationPath)
1140+
code += `json += ${funcName}(${input})`
11721141
break
11731142
case 'array':
11741143
funcName = generateFuncName()
1175-
laterCode = buildArray(location, laterCode, funcName, locationPath + key + subKey, key)
1176-
code += `json += ${funcName}(obj${accessor})`
1144+
laterCode = buildArray(location, laterCode, funcName, locationPath, true)
1145+
code += `json += ${funcName}(${input})`
11771146
break
11781147
case undefined:
11791148
if (schema.anyOf || schema.oneOf) {
11801149
// beware: dereferenceOfRefs has side effects and changes schema.anyOf
11811150
const locations = dereferenceOfRefs(location, schema.anyOf ? 'anyOf' : 'oneOf')
11821151
locations.forEach((location, index) => {
1183-
const nestedResult = nested(laterCode, locationPath, key, location, subKey !== '' ? subKey : 'i' + index, isArray)
1152+
const nestedResult = buildValue(laterCode, locationPath + 'i' + index, input, location, isArray)
11841153
// We need a test serializer as the String serializer will not work with
11851154
// date/time ajv validations
11861155
// see: https://github.com/fastify/fast-json-stringify/issues/325
11871156
const testSerializer = getTestSerializer(location.schema.format)
1188-
const testValue = testSerializer !== undefined ? `${testSerializer}(obj${accessor}, true)` : `obj${accessor}`
1157+
const testValue = testSerializer !== undefined ? `${testSerializer}(${input}, true)` : `${input}`
11891158

11901159
// Since we are only passing the relevant schema to ajv.validate, it needs to be full dereferenced
11911160
// otherwise any $ref pointing to an external schema would result in an error.
@@ -1210,18 +1179,18 @@ function nested (laterCode, locationPath, key, location, subKey, isArray) {
12101179
`
12111180
} else if (isEmpty(schema)) {
12121181
code += `
1213-
json += JSON.stringify(obj${accessor})
1182+
json += JSON.stringify(${input})
12141183
`
12151184
} else if ('const' in schema) {
12161185
code += `
1217-
if(ajv.validate(${JSON.stringify(schema)}, obj${accessor}))
1186+
if(ajv.validate(${JSON.stringify(schema)}, ${input}))
12181187
json += '${JSON.stringify(schema.const)}'
12191188
else
1220-
throw new Error(\`Item $\{JSON.stringify(obj${accessor})} does not match schema definition.\`)
1189+
throw new Error(\`Item $\{JSON.stringify(${input})} does not match schema definition.\`)
12211190
`
12221191
} else if (schema.type === undefined) {
12231192
code += `
1224-
json += JSON.stringify(obj${accessor})
1193+
json += JSON.stringify(${input})
12251194
`
12261195
} else {
12271196
throw new Error(`${schema.type} unsupported`)
@@ -1234,46 +1203,46 @@ function nested (laterCode, locationPath, key, location, subKey, isArray) {
12341203
sortedTypes.forEach((type, index) => {
12351204
const statement = index === 0 ? 'if' : 'else if'
12361205
const tempSchema = Object.assign({}, schema, { type })
1237-
const nestedResult = nested(laterCode, locationPath, key, mergeLocation(location, { schema: tempSchema }), subKey, isArray)
1206+
const nestedResult = buildValue(laterCode, locationPath, input, mergeLocation(location, { schema: tempSchema }), isArray)
12381207
switch (type) {
12391208
case 'string': {
12401209
code += `
1241-
${statement}(obj${accessor} === null || typeof obj${accessor} === "${type}" || obj${accessor} instanceof Date || typeof obj${accessor}.toISOString === "function" || obj${accessor} instanceof RegExp || (typeof obj${accessor} === "object" && Object.hasOwnProperty.call(obj${accessor}, "toString")))
1210+
${statement}(${input} === null || typeof ${input} === "${type}" || ${input} instanceof Date || typeof ${input}.toISOString === "function" || ${input} instanceof RegExp || (typeof ${input} === "object" && Object.hasOwnProperty.call(${input}, "toString")))
12421211
${nestedResult.code}
12431212
`
12441213
break
12451214
}
12461215
case 'null': {
12471216
code += `
1248-
${statement}(obj${accessor} == null)
1217+
${statement}(${input} == null)
12491218
${nestedResult.code}
12501219
`
12511220
break
12521221
}
12531222
case 'array': {
12541223
code += `
1255-
${statement}(Array.isArray(obj${accessor}))
1224+
${statement}(Array.isArray(${input}))
12561225
${nestedResult.code}
12571226
`
12581227
break
12591228
}
12601229
case 'integer': {
12611230
code += `
1262-
${statement}(Number.isInteger(obj${accessor}) || obj${accessor} === null)
1231+
${statement}(Number.isInteger(${input}) || ${input} === null)
12631232
${nestedResult.code}
12641233
`
12651234
break
12661235
}
12671236
case 'number': {
12681237
code += `
1269-
${statement}(isNaN(obj${accessor}) === false)
1238+
${statement}(isNaN(${input}) === false)
12701239
${nestedResult.code}
12711240
`
12721241
break
12731242
}
12741243
default: {
12751244
code += `
1276-
${statement}(typeof obj${accessor} === "${type}")
1245+
${statement}(typeof ${input} === "${type}")
12771246
${nestedResult.code}
12781247
`
12791248
break

test/typesArray.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,5 +432,5 @@ test('should throw an error when type is array and object is null', (t) => {
432432
}
433433

434434
const stringify = build(schema)
435-
t.throws(() => stringify({ arr: null }), new TypeError('Property \'arr\' should be of type array, received \'null\' instead.'))
435+
t.throws(() => stringify({ arr: null }), new TypeError('The value \'null\' does not match schema definition.'))
436436
})

0 commit comments

Comments
 (0)