⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 193 additions & 44 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

const { RefResolver } = require('json-schema-ref-resolver')

const Serializer = require('./lib/serializer')
const Validator = require('./lib/validator')
const Location = require('./lib/location')
const validate = require('./lib/schema-validator')
Expand All @@ -15,20 +14,171 @@ const SINGLE_TICK = /'/g
let largeArraySize = 2e4
let largeArrayMechanism = 'default'

const serializerFns = `
const {
asString,
asNumber,
asBoolean,
asDateTime,
asDate,
asTime,
asUnsafeString
} = serializer
function inlineAsInteger (options, input) {
let roundingFn = 'Math.trunc'
if (options && options.rounding) {
switch (options.rounding) {
case 'floor':
roundingFn = 'Math.floor'
break
case 'ceil':
roundingFn = 'Math.ceil'
break
case 'round':
roundingFn = 'Math.round'
break
}
}

return `
// #region inlineAsInteger
if (Number.isInteger(${input})) {
json += ${input}
} else if (typeof ${input} === 'bigint') {
json += ${input}.toString()
} else {
const integer = ${roundingFn}(${input})
if (integer === Infinity || integer === -Infinity || integer !== integer) {
throw new Error('The value "' + ${input} + '" cannot be converted to an integer.')
}
json += integer
}
// #endregion inlineAsInteger
`
}

function inlineAsNumber (input) {
return `
// #region inlineAsNumber
const num = Number(${input})
if (num !== num) {
throw new Error('The value "' + ${input} + '" cannot be converted to a number.')
} else if (num === Infinity || num === -Infinity) {
json += JSON_STR_NULL
} else {
json += num
}
// #endregion inlineAsNumber
`
}

function inlineAsBoolean (input) {
return `// #region inlineAsBoolean
json += ${input} ? 'true' : 'false'
// #endregion inlineAsBoolean`
}

function inlineAsDateTime (input) {
return `
// #region inlineAsDateTime
if (${input} === null) {
json += JSON_STR_EMPTY_STRING
} else if (${input} instanceof Date) {
json += JSON_STR_QUOTE + ${input}.toISOString() + JSON_STR_QUOTE
} else if (typeof ${input} === 'string') {
json += JSON_STR_QUOTE + ${input} + JSON_STR_QUOTE
} else {
throw new Error('The value "' + ${input} + '" cannot be converted to a date-time.')
}
// #endregion inlineAsDateTime
`
}

function inlineAsDate (input) {
return `
// #region inlineAsDate
if (${input} === null) {
json += JSON_STR_EMPTY_STRING
} else if (${input} instanceof Date) {
json += JSON_STR_QUOTE + new Date(${input}.getTime() - (${input}.getTimezoneOffset() * 60000)).toISOString().slice(0, 10) + JSON_STR_QUOTE
} else if (typeof ${input} === 'string') {
json += JSON_STR_QUOTE + ${input} + JSON_STR_QUOTE
} else {
throw new Error('The value "' + ${input} + '" cannot be converted to a date.')
}
// #endregion inlineAsDate
`
}

const asInteger = serializer.asInteger.bind(serializer)
function inlineAsTime (input) {
return `
// #region inlineAsTime
if (${input} === null) {
json += JSON_STR_EMPTY_STRING
} else if (${input} instanceof Date) {
json += JSON_STR_QUOTE + new Date(${input}.getTime() - (${input}.getTimezoneOffset() * 60000)).toISOString().slice(11, 19) + JSON_STR_QUOTE
} else if (typeof ${input} === 'string') {
json += JSON_STR_QUOTE + ${input} + JSON_STR_QUOTE
} else {
throw new Error('The value "' + ${input} + '" cannot be converted to a time.')
}
// #endregion inlineAsTime
`
}

`
function inlineAsString (input) {
return `
// #region inlineAsString
if (typeof ${input} !== 'string') {
if (${input} === null) {
json += JSON_STR_EMPTY_STRING
} else if (${input} instanceof Date) {
json += JSON_STR_QUOTE + ${input}.toISOString() + JSON_STR_QUOTE
} else if (${input} instanceof RegExp) {
const _str = ${input}.source
${inlineAsStringInternal('_str')}
} else {
const _str = String(${input})
${inlineAsStringInternal('_str')}
}
} else {
${inlineAsStringInternal(input)}
}
// #endregion inlineAsString
`
}

function inlineAsStringInternal (input) {
return `
// #region inlineAsStringInternal
{
const len = ${input}.length
if (len === 0) {
json += JSON_STR_EMPTY_STRING
} else if (len < 42) {
let result = ''
let last = -1
let point = 255
for (let i = 0; i < len; i++) {
point = ${input}.charCodeAt(i)
if (point === 0x22 || point === 0x5c) {
last === -1 && (last = 0)
result += ${input}.slice(last, i) + '\\\\'
last = i
} else if (point < 32 || (point >= 0xD800 && point <= 0xDFFF)) {
json += JSON.stringify(${input})
result = null
break
}
}
if (result !== null) {
json += JSON_STR_QUOTE + (last === -1 ? ${input} : (result + ${input}.slice(last))) + JSON_STR_QUOTE
}
} else if (len < 5000 && STR_ESCAPE.test(${input}) === false) {
json += JSON_STR_QUOTE + ${input} + JSON_STR_QUOTE
} else {
json += JSON.stringify(${input})
}
}
// #endregion inlineAsStringInternal
`
}

function inlineAsUnsafeString (input) {
return `// #region inlineAsUnsafeString
json += JSON_STR_QUOTE + ${input} + JSON_STR_QUOTE
// #endregion inlineAsUnsafeString`
}

const validRoundingMethods = new Set([
'floor',
Expand Down Expand Up @@ -172,7 +322,7 @@ function build (schema, options) {
const code = buildValue(context, location, 'input')

let contextFunctionCode = `
${serializerFns}
const STR_ESCAPE = /[\\u0000-\\u001f\\u0022\\u005c\\ud800-\\udfff]/
const JSON_STR_BEGIN_OBJECT = '{'
const JSON_STR_END_OBJECT = '}'
const JSON_STR_BEGIN_ARRAY = '['
Expand Down Expand Up @@ -209,7 +359,6 @@ function build (schema, options) {
`
}

const serializer = new Serializer(options)
const validator = new Validator(options.ajv)

for (const schemaId of context.validatorSchemasIds) {
Expand All @@ -229,7 +378,7 @@ function build (schema, options) {
if (options.mode === 'debug') {
return {
validator,
serializer,
serializer: { getState: () => options },
code: `validator\nserializer\n${contextFunctionCode}`,
ajv: validator.ajv
}
Expand All @@ -240,10 +389,10 @@ function build (schema, options) {

if (options.mode === 'standalone') {
const buildStandaloneCode = require('./lib/standalone')
return buildStandaloneCode(contextFunc, context, serializer, validator)
return buildStandaloneCode(contextFunc, context, { getState: () => options }, validator)
}

return contextFunc(validator, serializer)
return contextFunc(validator, null)
}

const objectKeywords = [
Expand Down Expand Up @@ -324,7 +473,8 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma, objV
code += `
if (/${propertyKey.replace(/\\*\//g, '\\/')}/.test(key)) {
${addComma}
json += asString(key) + JSON_STR_COLONS
${inlineAsString('key')}
json += JSON_STR_COLONS
${buildValue(context, propertyLocation, 'value')}
continue
}
Expand All @@ -339,13 +489,15 @@ function buildExtraObjectPropertiesSerializer (context, location, addComma, objV
if (additionalPropertiesSchema === true) {
code += `
${addComma}
json += asString(key) + JSON_STR_COLONS + JSON.stringify(value)
${inlineAsString('key')}
json += JSON_STR_COLONS + JSON.stringify(value)
`
} else {
const propertyLocation = location.getPropertyLocation('additionalProperties')
code += `
${addComma}
json += asString(key) + JSON_STR_COLONS
${inlineAsString('key')}
json += JSON_STR_COLONS
${buildValue(context, propertyLocation, 'value')}
`
}
Expand Down Expand Up @@ -428,7 +580,9 @@ function buildInnerObject (context, location, objVar) {
}

if (schema.patternProperties || schema.additionalProperties) {
code += '// #region extraObjectProperties\n'
code += buildExtraObjectPropertiesSerializer(context, location, addComma, objVar)
code += '// #endregion extraObjectProperties\n'
}

code += `
Expand Down Expand Up @@ -526,8 +680,9 @@ function buildObject (context, location, input) {
const obj = ${toJSON('input')}
if (obj === null) return ${nullable ? 'JSON_STR_NULL' : 'JSON_STR_EMPTY_OBJECT'}
let json = ''

// #region buildInnerObject
${buildInnerObject(context, location, 'obj')}
// #endregion buildInnerObject
return json
}
`
Expand All @@ -543,7 +698,9 @@ function buildObject (context, location, input) {
if (${objVar} === null) {
json += ${nullable ? 'JSON_STR_NULL' : 'JSON_STR_EMPTY_OBJECT'}
} else {
// #region buildInnerObject
${buildInnerObject(context, location, objVar)}
// #endregion buildInnerObject
}
`
context.buildingSet.delete(schema)
Expand Down Expand Up @@ -864,37 +1021,23 @@ function buildSingleTypeSerializer (context, location, input) {
return 'json += JSON_STR_NULL'
case 'string': {
if (schema.format === 'date-time') {
return `json += asDateTime(${input})`
return inlineAsDateTime(input)
} else if (schema.format === 'date') {
return `json += asDate(${input})`
return inlineAsDate(input)
} else if (schema.format === 'time') {
return `json += asTime(${input})`
return inlineAsTime(input)
} else if (schema.format === 'unsafe') {
return `json += asUnsafeString(${input})`
return inlineAsUnsafeString(input)
} else {
return `
if (typeof ${input} !== 'string') {
if (${input} === null) {
json += JSON_STR_EMPTY_STRING
} else if (${input} instanceof Date) {
json += JSON_STR_QUOTE + ${input}.toISOString() + JSON_STR_QUOTE
} else if (${input} instanceof RegExp) {
json += asString(${input}.source)
} else {
json += asString(${input}.toString())
}
} else {
json += asString(${input})
}
`
return inlineAsString(input)
}
}
case 'integer':
return `json += asInteger(${input})`
return inlineAsInteger(context.options, input)
case 'number':
return `json += asNumber(${input})`
return inlineAsNumber(input)
case 'boolean':
return `json += asBoolean(${input})`
return inlineAsBoolean(input)
case 'object': {
return buildObject(context, location, input)
}
Expand Down Expand Up @@ -1215,11 +1358,17 @@ function buildValue (context, location, input) {
}

if (schema.const !== undefined) {
code += '// #region buildConstSerializer\n'
code += buildConstSerializer(location, input)
code += '// #endregion buildConstSerializer\n'
} else if (Array.isArray(type)) {
code += '// #region buildMultiTypeSerializer\n'
code += buildMultiTypeSerializer(context, location, input)
code += '// #endregion buildMultiTypeSerializer\n'
} else {
code += '// #region buildSingleTypeSerializer\n'
code += buildSingleTypeSerializer(context, location, input)
code += '// #endregion buildSingleTypeSerializer\n'
}

if (nullable) {
Expand Down
Loading