Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@exodus/schemasafe

Package Overview
Dependencies
Maintainers
37
Versions
30
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@exodus/schemasafe - npm Package Compare versions

Comparing version 1.0.0-beta.2 to 1.0.0-beta.3

2

package.json
{
"name": "@exodus/schemasafe",
"version": "1.0.0-beta.2",
"version": "1.0.0-beta.3",
"description": "JSON Safe Parser & Schema Validator",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -5,3 +5,3 @@ # `@exodus/schemasafe`

Supports [draft-04/06/07](doc/Specification-support.md).
Supports [draft-04/06/07/2019-09](doc/Specification-support.md).

@@ -8,0 +8,0 @@ [![Node CI Status](https://github.com/ExodusMovement/schemasafe/workflows/Node%20CI/badge.svg)](https://github.com/ExodusMovement/schemasafe/actions)

@@ -11,3 +11,3 @@ 'use strict'

const { knownKeywords, schemaVersions, knownVocabularies } = require('./known-keywords')
const { initTracing, andDelta, orDelta, applyDelta, isDynamic } = require('./tracing')
const { initTracing, andDelta, orDelta, applyDelta, isDynamic, inProperties } = require('./tracing')

@@ -35,6 +35,7 @@ const noopRegExps = new Set(['^[\\s\\S]*$', '^[\\S\\s]*$', '^[^]*$', '', '.*', '^', '$'])

const evaluatedStatic = Symbol('evaluated')
const evaluatedStatic = Symbol('evaluatedStatic')
const optDynamic = Symbol('optDynamic')
const rootMeta = new WeakMap()
const compile = (schema, root, opts, scope, basePathRoot) => {
const compileSchema = (schema, root, opts, scope, basePathRoot) => {
const {

@@ -46,3 +47,2 @@ mode = 'default',

allErrors = false,
reflectErrorsValue = false,
dryRun = false,

@@ -76,4 +76,3 @@ allowUnusedKeywords = opts.mode === 'lax',

throw new Error('Strong mode forbids weakFormats and allowUnusedKeywords')
if (!includeErrors && (allErrors || reflectErrorsValue))
throw new Error('allErrors and reflectErrorsValue are not available if includeErrors = false')
if (!includeErrors && allErrors) throw new Error('allErrors requires includeErrors to be enabled')

@@ -127,2 +126,3 @@ const { gensym, getref, genref, genformat } = scopeMethods(scope)

if (allErrors) fun.write('let errorCount = 0')
if (opts[optDynamic]) fun.write('validate.evaluatedDynamic = null')

@@ -135,4 +135,5 @@ const helpers = jsHelpers(fun, scope, propvar, { unmodifiedPrototypes, isJSON }, noopRegExps)

const basePathStack = basePathRoot ? [basePathRoot] : []
const visit = (errors, history, current, node, schemaPath) => {
const visit = (errors, history, current, node, schemaPath, trace = {}) => {
// e.g. top-level data and property names, OR already checked by present() in history, OR in keys and not undefined
const isSub = history.length > 0 && history[history.length - 1].prop === current
const queryCurrent = () => history.filter((h) => h.prop === current)

@@ -145,3 +146,3 @@ const definitelyPresent =

const error = ({ path = [], prop = current, source }) => {
const error = ({ path = [], prop = current, source, suberr }) => {
const schemaP = functions.toPointer([...schemaPath, ...path])

@@ -160,10 +161,3 @@ const dataP = includeErrors ? buildPath(prop) : null

} else if (includeErrors === true && errors) {
const errorJS = reflectErrorsValue
? format(
'{ keywordLocation: %j, instanceLocation: %s, value: %s }',
schemaP,
dataP,
buildName(prop)
)
: format('{ keywordLocation: %j, instanceLocation: %s }', schemaP, dataP)
const errorJS = format('{ keywordLocation: %j, instanceLocation: %s }', schemaP, dataP)
if (allErrors) {

@@ -177,2 +171,3 @@ fun.write('if (%s === null) %s = []', errors, errors)

}
if (suberr) mergeerror(suberr) // can only happen in allErrors
if (allErrors) fun.write('errorCount++')

@@ -193,3 +188,4 @@ else fun.write('return false')

const enforceMinMax = (a, b) => laxMode(!(node[b] < node[a]), `Invalid ${a} / ${b} combination`)
const enforceValidation = (msg) => enforce(!requireValidation, `[requireValidation] ${msg}`)
const enforceValidation = (msg, suffix = 'must be specified') =>
enforce(!requireValidation, `[requireValidation] ${msg} ${suffix}`)
const subPath = (...args) => [...schemaPath, ...args]

@@ -204,4 +200,4 @@

// any is valid
enforceValidation('schema = true is not allowed')
return stat // nothing is evaluated for true
enforceValidation('schema = true', 'is not allowed')
return { stat } // nothing is evaluated for true
} else if (definitelyPresent) {

@@ -214,4 +210,4 @@ // node === false always fails in this case

}
evaluateDelta({ properties: [true], items: Infinity }) // everything is evaluated for false
return stat
evaluateDelta({ properties: [true], items: Infinity, type: [] }) // everything is evaluated for false
return { stat }
}

@@ -224,4 +220,4 @@

if (Object.keys(node).length === 0) {
enforceValidation('empty rules node encountered')
return stat // nothing to validate here, basically the same as node === true
enforceValidation('empty rules node', 'encountered')
return { stat } // nothing to validate here, basically the same as node === true
}

@@ -234,17 +230,28 @@

enforce(ruleTypes.every((t) => schemaTypes.has(t)), 'Invalid type used in consume')
enforce(ruleTypes.some((t) => schemaTypes.get(t)(node[prop])), 'Type not expected:', prop)
enforce(ruleTypes.some((t) => schemaTypes.get(t)(node[prop])), 'Unexpected type for', prop)
unused.delete(prop)
}
const get = (prop, ...ruleTypes) => {
if (node[prop] !== undefined) consume(prop, ...ruleTypes)
return node[prop]
}
const handle = (prop, ruleTypes, handler, errorArgs = {}) => {
if (node[prop] === undefined) return false
// opt-out on null is explicit in both places here, don't set default
consume(prop, ...ruleTypes)
if (handler !== null) {
const condition = handler(node[prop])
if (condition !== null) errorIf(condition, { path: [prop], ...errorArgs })
}
return true
}
const finish = () => {
const finish = (local) => {
if (!definitelyPresent) fun.write('}') // undefined check
enforce(unused.size === 0 || allowUnusedKeywords, 'Unprocessed keywords:', [...unused])
return { stat, local } // return statically evaluated
}
if (node === root) {
const $schema = node.$schema || $schemaDefault
if (node.$schema) {
if (typeof node.$schema !== 'string') throw new Error('Unexpected $schema')
consume('$schema', 'string')
}
const $schema = get('$schema', 'string') || $schemaDefault
if ($schema) {

@@ -258,122 +265,121 @@ const version = $schema.replace(/^http:\/\//, 'https://').replace(/#$/, '')

exclusiveRefs: schemaIsOlderThan('draft/2019-09'),
booleanRequired: schemaIsOlderThan('draft-04'),
})
}
if (node.$vocabulary) {
for (const [vocab, flag] of Object.entries(node.$vocabulary)) {
handle('$vocabulary', ['object'], ($vocabulary) => {
for (const [vocab, flag] of Object.entries($vocabulary)) {
if (flag === false) continue
enforce(flag === true && knownVocabularies.includes(vocab), 'Unknown vocabulary:', vocab)
}
consume('$vocabulary', 'object')
}
return null
})
}
if (node === schema && recursiveAnchor) consume('$recursiveAnchor', 'boolean')
if (node === schema && recursiveAnchor) handle('$recursiveAnchor', ['boolean'], null) // already applied
if (typeof node.description === 'string') consume('description', 'string') // unused, meta-only
if (typeof node.title === 'string') consume('title', 'string') // unused, meta-only
if (typeof node.$comment === 'string') consume('$comment', 'string') // unused, meta-only
if (Array.isArray(node.examples)) consume('examples', 'array') // unused, meta-only
handle('deprecated', ['boolean'], null) // unused, meta-only
handle('description', ['string'], null) // unused, meta-only
handle('title', ['string'], null) // unused, meta-only
handle('$comment', ['string'], null) // unused, meta-only
handle('examples', ['array'], null) // unused, meta-only
// defining defs are allowed, those are validated on usage
if (typeof node.$defs === 'object') {
consume('$defs', 'object')
} else if (typeof node.definitions === 'object') {
consume('definitions', 'object')
}
handle('$defs', ['object'], null) || handle('definitions', ['object'], null) // defs are allowed, those are validated on usage
const basePath = () => (basePathStack.length > 0 ? basePathStack[basePathStack.length - 1] : '')
if (typeof node.$id === 'string') {
basePathStack.push(joinPath(basePath(), node.$id))
consume('$id', 'string')
} else if (typeof node.id === 'string') {
basePathStack.push(joinPath(basePath(), node.id))
consume('id', 'string')
const setId = ($id) => {
basePathStack.push(joinPath(basePath(), $id))
return null
}
// $anchor is used only for ref resolution, on usage
if (typeof node.$anchor === 'string') consume('$anchor', 'string')
handle('$id', ['string'], setId) || handle('id', ['string'], setId)
handle('$anchor', ['string'], null) // $anchor is used only for ref resolution, on usage
const booleanRequired = getMeta().booleanRequired && typeof node.required === 'boolean'
if (node.default !== undefined && !useDefaults) consume('default', 'jsonval') // unused in this case
const defaultIsPresent = node.default !== undefined && useDefaults // will consume on use
if (definitelyPresent) {
if (defaultIsPresent) fail('Can not apply default value here (e.g. at root)')
if (node.required === true || node.required === false)
fail('Can not apply boolean required here (e.g. at root)')
} else if (defaultIsPresent || booleanRequired) {
if (node.default !== undefined && useDefaults) {
if (definitelyPresent) fail('Can not apply default value here (e.g. at root)')
fun.write('if (%s) {', safenot(present(current)))
if (defaultIsPresent) {
fun.write('%s = %j', name, node.default)
consume('default', 'jsonval')
}
if (booleanRequired) {
if (node.required === true) {
if (!defaultIsPresent) error({ path: ['required'] })
consume('required', 'boolean')
} else if (node.required === false) {
consume('required', 'boolean')
}
}
fun.write('%s = %j', name, get('default', 'jsonval'))
fun.write('} else {')
} else {
fun.write('if (%s) {', present(current))
handle('default', ['jsonval'], null) // unused
if (!definitelyPresent) fun.write('if (%s) {', present(current))
}
// evaluated: declare dynamic
const needUnevaluated = (rule) =>
opts[optDynamic] && (node[rule] || node[rule] === false || node === schema)
const local = Object.freeze({
items: needUnevaluated('unevaluatedItems') ? gensym('evaluatedItems') : null,
props: needUnevaluated('unevaluatedProperties') ? gensym('evaluatedProps') : null,
})
if (local.items) fun.write('const %s = [0]', local.items)
if (local.props) fun.write('const %s = [[], []]', local.props)
const dyn = { items: local.items || trace.items, props: local.props || trace.props }
const canSkipDynamic = () =>
(!dyn.items || stat.items === Infinity) && (!dyn.props || stat.properties.includes(true))
const evaluateDeltaDynamic = (delta) => {
// Skips applying those that have already been proved statically
if (dyn.items && delta.items > stat.items) fun.write('%s.push(%d)', dyn.items, delta.items)
if (dyn.props) {
const inStat = (properties, patterns) => inProperties(stat, { properties, patterns })
const properties = delta.properties.filter((x) => !inStat([x], []))
const patterns = delta.patterns.filter((x) => !inStat([], [x]))
if (properties.includes(true)) {
fun.write('%s[0].push(true)', dyn.props)
} else {
if (properties.length > 0) fun.write('%s[0].push(...%j)', dyn.props, properties)
if (patterns.length > 0) fun.write('%s[1].push(...%s)', dyn.props, patterns)
}
}
}
const applyDynamicToDynamic = (target, items, props) => {
if (isDynamic(stat).items && target.items && items)
fun.write('%s.push(...%s)', target.items, items)
if (isDynamic(stat).properties && target.props && props) {
fun.write('%s[0].push(...%s[0])', target.props, props)
fun.write('%s[1].push(...%s[1])', target.props, props)
}
}
const applyRef = (n, errorArgs) => {
// evaluated: propagate static from ref to current, skips cyclic.
// Can do this before the call as the call is just a write
const delta = (scope[n] && scope[n][evaluatedStatic]) || { unknown: true } // assume unknown if ref is cyclic
evaluateDelta(delta)
// Allow recursion to here only if $recursiveAnchor is true, else skip from deep recursion
const recursive = recursiveAnchor ? format('recursive || validate') : format('recursive')
if (includeErrors) {
// Save and restore errors in case of recursion
const res = gensym('res')
const err = gensym('err')
const suberr = gensym('suberr')
fun.write('const %s = validate.errors', err)
fun.write('const %s = %s(%s, %s)', res, n, name, recursive)
fun.write('const %s = %s.errors', suberr, n)
fun.write('validate.errors = %s', err)
errorIf(safenot(res), { ...errorArgs, source: suberr })
} else {
errorIf(format('!%s(%s, %s)', n, name, recursive), errorArgs)
}
// evaluated: propagate static from ref to current, skips cyclic
if (scope[n] && scope[n][evaluatedStatic]) evaluateDelta(scope[n][evaluatedStatic])
else evaluateDelta({ unknown: true }) // assume unknown if ref is cyclic
if (!includeErrors && canSkipDynamic()) return format('!%s(%s, %s)', n, name, recursive) // simple case
const res = gensym('res')
const err = gensym('err') // Save and restore errors in case of recursion (if needed)
const suberr = gensym('suberr')
if (includeErrors) fun.write('const %s = validate.errors', err)
fun.write('const %s = %s(%s, %s)', res, n, name, recursive)
if (includeErrors) fun.write('const %s = %s.errors', suberr, n)
if (includeErrors) fun.write('validate.errors = %s', err)
errorIf(safenot(res), { ...errorArgs, source: suberr })
// evaluated: propagate dynamic from ref to current
fun.if(res, () => {
const items = isDynamic(delta).items ? format('%s.evaluatedDynamic[0]', n) : null
const props = isDynamic(delta).properties ? format('%s.evaluatedDynamic[1]', n) : null
applyDynamicToDynamic(dyn, items, props)
})
return null
}
if (node.$ref) {
handle('$ref', ['string'], ($ref) => {
const resolved = resolveReference(root, schemas, node.$ref, basePath())
const [sub, subRoot, path] = resolved[0] || []
if (sub || sub === false) {
let n = getref(sub)
if (!n) n = compile(sub, subRoot, opts, scope, path)
applyRef(n, { path: ['$ref'] })
} else fail('failed to resolve $ref:', node.$ref)
consume('$ref', 'string')
if (getMeta().exclusiveRefs) {
// ref overrides any sibling keywords for older schemas
finish()
return stat
}
if (!sub && sub !== false) fail('failed to resolve $ref:', node.$ref)
const n = getref(sub) || compileSchema(sub, subRoot, opts, scope, path)
return applyRef(n, { path: ['$ref'] })
})
if (node.$ref && getMeta().exclusiveRefs) {
enforce(!opts[optDynamic], 'unevaluated* is supported only on draft2019-09 schemas and above')
return finish() // ref overrides any sibling keywords for older schemas
}
if (node.$recursiveRef) {
enforce(node.$recursiveRef === '#', 'Behavior of $recursiveRef is defined only for "#"')
handle('$recursiveRef', ['string'], ($recursiveRef) => {
enforce($recursiveRef === '#', 'Behavior of $recursiveRef is defined only for "#"')
// Apply deep recursion from here only if $recursiveAnchor is true, else just run self
const n = recursiveAnchor ? format('(recursive || validate)') : format('validate')
applyRef(n, { path: ['$recursiveRef'] })
consume('$recursiveRef', 'string')
}
return applyRef(n, { path: ['$recursiveRef'] })
})
/* Preparation and methods, post-$ref validation will begin at the end of the function */
const hasSubValidation =
node.$ref || ['allOf', 'anyOf', 'oneOf'].some((key) => Array.isArray(node[key]))
const typeArray =
node.type === undefined ? null : Array.isArray(node.type) ? node.type : [node.type]
for (const t of typeArray || [])
enforce(typeof t === 'string' && types.has(t), 'Unknown type:', t)
// typeArray === null and stat.type === null means no type validation, which is required if we don't have const or enum
if (!typeArray && !stat.type && node.const === undefined && !node.enum && !hasSubValidation)
enforceValidation('type is required')
// This is used for typechecks, null means * here

@@ -407,9 +413,7 @@ const allIn = (arr, valid) => {

const prev = allErrors && haveComplex ? gensym('prev') : null
const prevWrap = (shouldWrap, writeBody) => {
if (prev === null || !shouldWrap) writeBody()
else fun.if(format('errorCount === %s', prev), writeBody)
}
const prevWrap = (shouldWrap, writeBody) =>
fun.if(shouldWrap && prev !== null ? format('errorCount === %s', prev) : true, writeBody)
// Can not be used before undefined check above! The one performed by present()
const rule = (...args) => visit(errors, [...history, { stat, prop: current }], ...args)
const rule = (...args) => visit(errors, [...history, { stat, prop: current }], ...args).stat
const subrule = (suberr, ...args) => {

@@ -419,3 +423,3 @@ const sub = gensym('sub')

if (allErrors) fun.write('let errorCount = 0') // scoped error counter
const delta = visit(suberr, [...history, { stat, prop: current }], ...args)
const { stat: delta } = visit(suberr, [...history, { stat, prop: current }], ...args)
if (allErrors) {

@@ -434,31 +438,30 @@ fun.write('return errorCount === 0')

const mergeerror = (suberr) => {
if (!suberr) return
// suberror can be null e.g. on failed empty contains
const args = [errors, suberr, errors, suberr, suberr, errors, suberr]
fun.write('if (%s && %s) { %s.push(...%s) } else if (%s) %s = %s', ...args)
if (suberr !== null) fun.write('if (%s) %s.push(...%s)', suberr, errors, suberr)
}
// Extracted single additional(Items/Properties) rules, for reuse with unevaluated(Items/Properties)
const additionalItems = (limit, ruleValue, rulePath) => {
if (ruleValue === false) {
if (removeAdditional) {
const additionalItems = (rulePath, limit) => {
const handled = handle(rulePath, ['object', 'boolean'], (ruleValue) => {
if (ruleValue === false) {
if (!removeAdditional) return format('%s.length > %s', name, limit)
fun.write('if (%s.length > %s) %s.length = %s', name, limit, name, limit)
} else {
errorIf(format('%s.length > %s', name, limit), { path: [rulePath] })
return null
}
} else if (ruleValue) {
forArray(current, limit, (prop) => rule(prop, ruleValue, subPath(rulePath)))
}
consume(rulePath, 'object', 'boolean')
evaluateDelta({ items: Infinity })
return null
})
if (handled) evaluateDelta({ items: Infinity })
}
const additionalProperties = (condition, ruleValue, rulePath) => {
forObjectKeys(current, (sub, key) => {
fun.if(condition(key), () => {
if (ruleValue === false && removeAdditional) fun.write('delete %s[%s]', name, key)
else rule(sub, ruleValue, subPath(rulePath))
const additionalProperties = (rulePath, condition) => {
const handled = handle(rulePath, ['object', 'boolean'], (ruleValue) => {
forObjectKeys(current, (sub, key) => {
fun.if(condition(key), () => {
if (ruleValue === false && removeAdditional) fun.write('delete %s[%s]', name, key)
else rule(sub, ruleValue, subPath(rulePath))
})
})
return null
})
consume(rulePath, 'object', 'boolean')
evaluateDelta({ properties: [true] })
if (handled) evaluateDelta({ properties: [true] })
}

@@ -474,66 +477,47 @@ const additionalCondition = (key, properties, patternProperties) =>

const checkNumbers = () => {
const applyMinMax = (value, operator, errorArgs) => {
enforce(Number.isFinite(value), 'Invalid minimum or maximum:', value)
errorIf(format('!(%d %c %s)', value, operator, name), errorArgs)
}
const minMax = (value, operator) => format('!(%d %c %s)', value, operator, name) // don't remove negation, accounts for NaN
if (Number.isFinite(node.exclusiveMinimum)) {
applyMinMax(node.exclusiveMinimum, '<', { path: ['exclusiveMinimum'] })
consume('exclusiveMinimum', 'finite')
} else if (node.minimum !== undefined) {
applyMinMax(node.minimum, node.exclusiveMinimum ? '<' : '<=', { path: ['minimum'] })
consume('minimum', 'finite')
if (typeof node.exclusiveMinimum === 'boolean') consume('exclusiveMinimum', 'boolean')
handle('exclusiveMinimum', ['finite'], (min) => minMax(min, '<'))
} else {
handle('minimum', ['finite'], (min) => minMax(min, node.exclusiveMinimum ? '<' : '<='))
handle('exclusiveMinimum', ['boolean'], null) // handled above
}
if (Number.isFinite(node.exclusiveMaximum)) {
applyMinMax(node.exclusiveMaximum, '>', { path: ['exclusiveMaximum'] })
handle('exclusiveMaximum', ['finite'], (max) => minMax(max, '>'))
enforceMinMax('minimum', 'exclusiveMaximum')
enforceMinMax('exclusiveMinimum', 'exclusiveMaximum')
consume('exclusiveMaximum', 'finite')
} else if (node.maximum !== undefined) {
applyMinMax(node.maximum, node.exclusiveMaximum ? '>' : '>=', { path: ['maximum'] })
handle('maximum', ['finite'], (max) => minMax(max, node.exclusiveMaximum ? '>' : '>='))
handle('exclusiveMaximum', ['boolean'], null) // handled above
enforceMinMax('minimum', 'maximum')
enforceMinMax('exclusiveMinimum', 'maximum')
consume('maximum', 'finite')
if (typeof node.exclusiveMaximum === 'boolean') consume('exclusiveMaximum', 'boolean')
}
const multipleOf = node.multipleOf === undefined ? 'divisibleBy' : 'multipleOf' // draft3 support
if (node[multipleOf] !== undefined) {
const value = node[multipleOf]
enforce(Number.isFinite(value) && value > 0, `Invalid ${multipleOf}:`, value)
if (Number.isInteger(value)) {
errorIf(format('%s %% %d !== 0', name, value), { path: ['isMultipleOf'] })
} else {
scope.isMultipleOf = functions.isMultipleOf
const [last, exp] = `${value}`.replace(/.*\./, '').split('e-')
const e = last.length + (exp ? Number(exp) : 0)
const args = [name, value, e, Math.round(value * Math.pow(10, e))] // precompute for performance
errorIf(format('!isMultipleOf(%s, %d, 1e%d, %d)', ...args), { path: ['isMultipleOf'] })
}
consume(multipleOf, 'finite')
}
handle(multipleOf, ['finite'], (value) => {
enforce(value > 0, `Invalid ${multipleOf}:`, value)
if (Number.isInteger(value)) return format('%s %% %d !== 0', name, value)
scope.isMultipleOf = functions.isMultipleOf
const [last, exp] = `${value}`.replace(/.*\./, '').split('e-')
const e = last.length + (exp ? Number(exp) : 0)
const args = [name, value, e, Math.round(value * Math.pow(10, e))] // precompute for performance
return format('!isMultipleOf(%s, %d, 1e%d, %d)', ...args)
})
}
const checkStrings = () => {
if (node.maxLength !== undefined) {
enforce(Number.isFinite(node.maxLength), 'Invalid maxLength:', node.maxLength)
handle('maxLength', ['natural'], (max) => {
scope.stringLength = functions.stringLength
const args = [name, node.maxLength, name, node.maxLength]
errorIf(format('%s.length > %d && stringLength(%s) > %d', ...args), { path: ['maxLength'] })
consume('maxLength', 'natural')
}
if (node.minLength !== undefined) {
enforce(Number.isFinite(node.minLength), 'Invalid minLength:', node.minLength)
enforceMinMax('minLength', 'maxLength')
return format('%s.length > %d && stringLength(%s) > %d', name, max, name, max)
})
handle('minLength', ['natural'], (min) => {
scope.stringLength = functions.stringLength
const args = [name, node.minLength, name, node.minLength]
errorIf(format('%s.length < %d || stringLength(%s) < %d', ...args), { path: ['minLength'] })
consume('minLength', 'natural')
}
return format('%s.length < %d || stringLength(%s) < %d', name, min, name, min)
})
enforceMinMax('minLength', 'maxLength')
prevWrap(true, () => {
const checkFormat = (fmtname, target, path, formatsObj = fmts) => {
const checkFormat = (fmtname, target, formatsObj = fmts) => {
const known = typeof fmtname === 'string' && functions.hasOwn(formatsObj, fmtname)

@@ -548,19 +532,19 @@ enforce(known, 'Unrecognized format used:', fmtname)

if (functions.hasOwn(optFormats, fmtname)) enforceRegex(formatImpl.source)
errorIf(format('!%s.test(%s)', n, target), { path: [path] })
} else {
errorIf(format('!%s(%s)', n, target), { path: [path] })
return format('!%s.test(%s)', n, target)
}
return format('!%s(%s)', n, target)
}
if (node.format) {
checkFormat(node.format, name, 'format')
consume('format', 'string')
}
if (node.pattern) {
enforceRegex(node.pattern)
if (!noopRegExps.has(node.pattern))
errorIf(safenot(patternTest(node.pattern, name)), { path: ['pattern'] })
consume('pattern', 'string')
}
handle('format', ['string'], (value) => {
evaluateDelta({ fullstring: true })
return checkFormat(value, name)
})
handle('pattern', ['string'], (pattern) => {
enforceRegex(pattern)
evaluateDelta({ fullstring: true })
if (noopRegExps.has(pattern)) return null
return safenot(patternTest(pattern, name))
})
enforce(node.contentSchema !== false, 'contentSchema cannot be set to false')

@@ -572,3 +556,3 @@ if (node.contentEncoding || node.contentMediaType || node.contentSchema) {

if (node.contentEncoding === 'base64') {
checkFormat('base64', name, 'contentEncoding', formats.extra)
errorIf(checkFormat('base64', name, formats.extra), { path: ['contentEncoding'] })
if (node.contentMediaType) {

@@ -595,2 +579,3 @@ scope.deBase64 = functions.deBase64

consume('contentSchema', 'object', 'array')
evaluateDelta({ fullstring: true })
}

@@ -609,53 +594,31 @@ if (node.contentMediaType) {

})
const stringValidated = node.format || node.pattern || node.contentSchema || hasSubValidation
const stringWarning = 'pattern, format or contentSchema must be specified for strings'
if (typeApplicable('string') && requireStringValidation && !stringValidated)
fail(`[requireStringValidation] ${stringWarning}, use pattern: ^[\\s\\S]*$ to opt-out`)
}
const checkArrays = () => {
if (node.maxItems !== undefined) {
enforce(Number.isFinite(node.maxItems), 'Invalid maxItems:', node.maxItems)
if (Array.isArray(node.items) && node.items.length > node.maxItems)
fail(`Invalid maxItems: ${node.maxItems} is less than items array length`)
errorIf(format('%s.length > %d', name, node.maxItems), { path: ['maxItems'] })
consume('maxItems', 'natural')
}
handle('maxItems', ['natural'], (max) => {
if (Array.isArray(node.items) && node.items.length > max)
fail(`Invalid maxItems: ${max} is less than items array length`)
return format('%s.length > %d', name, max)
})
handle('minItems', ['natural'], (min) => format('%s.length < %d', name, min)) // can be higher that .items length with additionalItems
enforceMinMax('minItems', 'maxItems')
if (node.minItems !== undefined) {
enforce(Number.isFinite(node.minItems), 'Invalid minItems:', node.minItems)
enforceMinMax('minItems', 'maxItems')
// can be higher that .items length with additionalItems
errorIf(format('%s.length < %d', name, node.minItems), { path: ['minItems'] })
consume('minItems', 'natural')
}
if (node.items || node.items === false) {
if (Array.isArray(node.items)) {
for (let p = 0; p < node.items.length; p++)
rule(currPropImm(p), node.items[p], subPath(`${p}`))
evaluateDelta({ items: node.items.length })
handle('items', ['object', 'array', 'boolean'], (items) => {
if (Array.isArray(items)) {
for (let p = 0; p < items.length; p++) rule(currPropImm(p), items[p], subPath(`${p}`))
evaluateDelta({ items: items.length })
} else {
forArray(current, format('0'), (prop) => rule(prop, node.items, subPath('items')))
stat.items = Infinity
forArray(current, format('0'), (prop) => rule(prop, items, subPath('items')))
evaluateDelta({ items: Infinity })
}
consume('items', 'object', 'array', 'boolean')
} else if (typeApplicable('array') && !hasSubValidation) {
enforceValidation('items rule must be specified')
}
return null
})
if (!Array.isArray(node.items)) {
// additionalItems is allowed, but ignored per some spec tests in this case!
// We do nothing and let it throw except for in allowUnusedKeywords mode
// As a result, this is not allowed by default, only in allowUnusedKeywords mode
} else if (node.additionalItems || node.additionalItems === false) {
additionalItems(format('%d', node.items.length), node.additionalItems, 'additionalItems')
} else if (node.items.length === node.maxItems) {
// No additional items are possible
} else {
enforceValidation('additionalItems rule must be specified for fixed arrays')
}
if (Array.isArray(node.items))
additionalItems('additionalItems', format('%d', node.items.length))
// Else additionalItems is allowed, but ignored per some spec tests!
// We do nothing and let it throw except for in allowUnusedKeywords mode
// As a result, omitting .items is not allowed by default, only in allowUnusedKeywords mode
if (node.contains || node.contains === false) {
handle('contains', ['object', 'boolean'], () => {
const passes = gensym('passes')

@@ -671,21 +634,10 @@ fun.write('let %s = 0', passes)

if (Number.isFinite(node.minContains)) {
const condition = format('%s < %d', passes, node.minContains) // fast, reusable
errorIf(condition, { path: ['minContains'] })
consume('minContains', 'natural')
fun.if(condition, () => mergeerror(suberr))
} else {
const condition = format('%s < 1', passes) // fast, reusable
errorIf(condition, { path: ['contains'] })
fun.if(condition, () => mergeerror(suberr))
}
if (!handle('minContains', ['natural'], (mn) => format('%s < %d', passes, mn), { suberr }))
errorIf(format('%s < 1', passes), { path: ['contains'], suberr })
if (Number.isFinite(node.maxContains)) {
errorIf(format('%s > %d', passes, node.maxContains), { path: ['maxContains'] })
enforceMinMax('minContains', 'maxContains')
consume('maxContains', 'natural')
}
handle('maxContains', ['natural'], (max) => format('%s > %d', passes, max))
enforceMinMax('minContains', 'maxContains')
consume('contains', 'object', 'boolean')
}
return null
})

@@ -705,3 +657,4 @@ const uniqueIsSimple = () => {

prevWrap(true, () => {
if (node.uniqueItems === true) {
handle('uniqueItems', ['boolean'], (uniqueItems) => {
if (uniqueItems === false) return null
if (complexityChecks)

@@ -711,7 +664,4 @@ enforce(uniqueIsSimple(), 'maxItems should be specified for non-primitive uniqueItems')

scope.deepEqual = functions.deepEqual
errorIf(format('!unique(%s)', name), { path: ['uniqueItems'] })
consume('uniqueItems', 'boolean')
} else if (node.uniqueItems === false) {
consume('uniqueItems', 'boolean')
}
return format('!unique(%s)', name)
})
})

@@ -722,17 +672,8 @@ }

const propertiesCount = format('Object.keys(%s).length', name)
if (node.maxProperties !== undefined) {
enforce(Number.isFinite(node.maxProperties), 'Invalid maxProperties:', node.maxProperties)
errorIf(format('%s > %d', propertiesCount, node.maxProperties), { path: ['maxProperties'] })
consume('maxProperties', 'natural')
}
if (node.minProperties !== undefined) {
enforce(Number.isFinite(node.minProperties), 'Invalid minProperties:', node.minProperties)
enforceMinMax('minProperties', 'maxProperties')
errorIf(format('%s < %d', propertiesCount, node.minProperties), { path: ['minProperties'] })
consume('minProperties', 'natural')
}
handle('maxProperties', ['natural'], (max) => format('%s > %d', propertiesCount, max))
handle('minProperties', ['natural'], (min) => format('%s < %d', propertiesCount, min))
enforceMinMax('minProperties', 'maxProperties')
if (typeof node.propertyNames === 'object' || typeof node.propertyNames === 'boolean') {
handle('propertyNames', ['object', 'boolean'], (names) => {
forObjectKeys(current, (sub, key) => {
const names = node.propertyNames
const nameSchema = typeof names === 'object' ? { type: 'string', ...names } : names

@@ -742,6 +683,4 @@ const nameprop = Object.freeze({ name: key, errorParent: sub, type: 'string' })

})
consume('propertyNames', 'object', 'boolean')
}
if (typeof node.additionalProperties === 'object' && typeof node.propertyNames !== 'object')
enforceValidation('wild-card additionalProperties requires propertyNames')
return null
})

@@ -753,4 +692,4 @@ // if allErrors is false, we can skip present check for required properties validated before

if (Array.isArray(node.required)) {
for (const req of node.required) {
handle('required', ['array'], (required) => {
for (const req of required) {
if (checked(req)) continue

@@ -760,11 +699,10 @@ const prop = currPropImm(req)

}
evaluateDelta({ required: node.required })
consume('required', 'array')
}
evaluateDelta({ required })
return null
})
for (const dependencies of ['dependencies', 'dependentRequired', 'dependentSchemas']) {
if (node[dependencies]) {
for (const key of Object.keys(node[dependencies])) {
let deps = node[dependencies][key]
if (typeof deps === 'string') deps = [deps]
handle(dependencies, ['object'], (value) => {
for (const key of Object.keys(value)) {
const deps = typeof value[key] === 'string' ? [value[key]] : value[key]
const item = currPropImm(key, checked(key))

@@ -788,33 +726,33 @@ if (Array.isArray(deps) && dependencies !== 'dependentSchemas') {

const body = () => {
const delta = rule(current, deps, subPath(dependencies, key))
const delta = rule(current, deps, subPath(dependencies, key), dyn)
evaluateDelta(orDelta({}, delta))
evaluateDeltaDynamic(delta)
}
if (item.checked) body()
else fun.if(present(item), body)
fun.if(item.checked ? true : present(item), body)
} else fail(`Unexpected ${dependencies} entry`)
}
consume(dependencies, 'object')
}
return null
})
}
if (typeof node.properties === 'object') {
for (const p of Object.keys(node.properties))
rule(currPropImm(p, checked(p)), node.properties[p], subPath('properties', p))
evaluateDelta({ properties: Object.keys(node.properties || {}) })
consume('properties', 'object')
}
handle('properties', ['object'], (properties) => {
for (const p of Object.keys(properties))
rule(currPropImm(p, checked(p)), properties[p], subPath('properties', p))
evaluateDelta({ properties: Object.keys(properties || {}) })
return null
})
prevWrap(node.patternProperties, () => {
if (node.patternProperties) {
handle('patternProperties', ['object'], (patternProperties) => {
forObjectKeys(current, (sub, key) => {
for (const p of Object.keys(node.patternProperties)) {
for (const p of Object.keys(patternProperties)) {
enforceRegex(p, node.propertyNames || {})
fun.if(patternTest(p, key), () => {
rule(sub, node.patternProperties[p], subPath('patternProperties', p))
rule(sub, patternProperties[p], subPath('patternProperties', p))
})
}
})
evaluateDelta({ patterns: Object.keys(node.patternProperties || {}) })
consume('patternProperties', 'object')
}
evaluateDelta({ patterns: Object.keys(patternProperties || {}) })
return null
})
if (node.additionalProperties || node.additionalProperties === false) {

@@ -824,5 +762,3 @@ const properties = Object.keys(node.properties || {})

const condition = (key) => additionalCondition(key, properties, patternProperties)
additionalProperties(condition, node.additionalProperties, 'additionalProperties')
} else if (typeApplicable('object') && !hasSubValidation) {
enforceValidation('additionalProperties rule must be specified')
additionalProperties('additionalProperties', condition)
}

@@ -833,32 +769,21 @@ })

const checkConst = () => {
if (node.const !== undefined) {
errorIf(safenot(compare(name, node.const)), { path: ['const'] })
consume('const', 'jsonval')
return true
} else if (node.enum) {
enforce(Array.isArray(node.enum), 'Invalid enum')
const objects = node.enum.filter((value) => value && typeof value === 'object')
const primitive = node.enum.filter((value) => !(value && typeof value === 'object'))
const condition = safeor(...[...primitive, ...objects].map((value) => compare(name, value)))
errorIf(safenot(condition), { path: ['enum'] })
consume('enum', 'array')
return true
}
return false
if (handle('const', ['jsonval'], (val) => safenot(compare(name, val)))) return true
return handle('enum', ['array'], (vals) => {
const objects = vals.filter((value) => value && typeof value === 'object')
const primitive = vals.filter((value) => !(value && typeof value === 'object'))
return safenot(safeor(...[...primitive, ...objects].map((value) => compare(name, value))))
})
}
const checkGeneric = () => {
if (node.not || node.not === false) {
const { sub } = subrule(null, current, node.not, subPath('not'))
errorIf(sub, { path: ['not'] })
consume('not', 'object', 'boolean')
}
handle('not', ['object', 'boolean'], (not) => subrule(null, current, not, subPath('not')).sub)
const thenOrElse = node.then || node.then === false || node.else || node.else === false
if ((node.if || node.if === false) && thenOrElse) {
const { sub, delta: deltaIf } = subrule(null, current, node.if, subPath('if'))
const { sub, delta: deltaIf } = subrule(null, current, node.if, subPath('if'), dyn)
let deltaElse, deltaThen
fun.write('if (%s) {', safenot(sub))
if (node.else || node.else === false) {
deltaElse = rule(current, node.else, subPath('else'))
deltaElse = rule(current, node.else, subPath('else'), dyn)
evaluateDeltaDynamic(deltaElse)
consume('else', 'object', 'boolean')

@@ -868,3 +793,4 @@ } else deltaElse = {}

fun.write('} else {')
deltaThen = rule(current, node.then, subPath('then'))
deltaThen = rule(current, node.then, subPath('then'), dyn)
evaluateDeltaDynamic(andDelta(deltaIf, deltaThen))
consume('then', 'object', 'boolean')

@@ -877,14 +803,23 @@ } else deltaThen = {}

if (node.allOf !== undefined) {
enforce(Array.isArray(node.allOf), 'Invalid allOf')
for (const [key, sch] of Object.entries(node.allOf))
evaluateDelta(rule(current, sch, subPath('allOf', key)))
consume('allOf', 'array')
}
handle('allOf', ['array'], (allOf) => {
for (const [key, sch] of Object.entries(allOf))
evaluateDelta(rule(current, sch, subPath('allOf', key), dyn))
return null
})
if (node.anyOf !== undefined) {
enforce(Array.isArray(node.anyOf), 'Invalid anyOf')
handle('anyOf', ['array'], (anyOf) => {
const suberr = suberror()
if (anyOf.length > 0 && !canSkipDynamic()) {
// In this case, all have to be checked to gather evaluated properties
const entries = Object.entries(anyOf).map(([key, sch]) =>
subrule(suberr, current, sch, subPath('anyOf', key), dyn)
)
evaluateDelta(entries.reduce((acc, cur) => orDelta(acc, cur.delta), {}))
const condition = safenot(safeor(...entries.map(({ sub }) => sub)))
errorIf(condition, { path: ['anyOf'], suberr })
for (const { delta, sub } of entries) fun.if(sub, () => evaluateDeltaDynamic(delta))
return null
}
let delta
for (const [key, sch] of Object.entries(node.anyOf)) {
for (const [key, sch] of Object.entries(anyOf)) {
const { sub, delta: deltaVariant } = subrule(suberr, current, sch, subPath('anyOf', key))

@@ -894,11 +829,9 @@ fun.write('if (%s) {', safenot(sub))

}
if (node.anyOf.length > 0) evaluateDelta(delta)
error({ path: ['anyOf'] })
mergeerror(suberr)
node.anyOf.forEach(() => fun.write('}'))
consume('anyOf', 'array')
}
if (anyOf.length > 0) evaluateDelta(delta)
error({ path: ['anyOf'], suberr })
anyOf.forEach(() => fun.write('}'))
return null
})
if (node.oneOf !== undefined) {
enforce(Array.isArray(node.oneOf), 'Invalid oneOf')
handle('oneOf', ['array'], (oneOf) => {
const passes = gensym('passes')

@@ -909,13 +842,15 @@ fun.write('let %s = 0', passes)

let i = 0
for (const [key, sch] of Object.entries(node.oneOf)) {
const { sub, delta: deltaVariant } = subrule(suberr, current, sch, subPath('oneOf', key))
fun.write('if (%s) %s++', sub, passes)
if (!includeErrors && i++ > 0) errorIf(format('%s > 1', passes), { path: ['oneOf'] })
delta = delta ? orDelta(delta, deltaVariant) : deltaVariant
}
if (node.oneOf.length > 0) evaluateDelta(delta)
const entries = Object.entries(oneOf).map(([key, sch]) => {
if (!includeErrors && i++ > 1) errorIf(format('%s > 1', passes), { path: ['oneOf'] })
const entry = subrule(suberr, current, sch, subPath('oneOf', key), dyn)
fun.write('if (%s) %s++', entry.sub, passes)
delta = delta ? orDelta(delta, entry.delta) : entry.delta
return entry
})
if (oneOf.length > 0) evaluateDelta(delta)
errorIf(format('%s !== 1', passes), { path: ['oneOf'] })
fun.if(format('%s === 0', passes), () => mergeerror(suberr)) // if none matched, dump all errors
consume('oneOf', 'array')
}
for (const entry of entries) fun.if(entry.sub, () => evaluateDeltaDynamic(entry.delta))
return null
})
}

@@ -925,4 +860,3 @@

const [funSize, unusedSize] = [fun.size(), unused.size]
if (definitelyType(...validTypes)) checkBlock()
else fun.if(queryType, checkBlock)
fun.if(definitelyType(...validTypes) ? true : queryType, checkBlock)
// enforce check that non-applicable blocks are empty and no rules were applied

@@ -939,9 +873,12 @@ if (funSize !== fun.size() || unusedSize !== unused.size)

} else if (node.unevaluatedItems || node.unevaluatedItems === false) {
if (isDynamic(stat).items) throw new Error('Dynamic unevaluated is not implemented')
const limit = format('%d', stat.items)
additionalItems(limit, node.unevaluatedItems, 'unevaluatedItems')
if (isDynamic(stat).items) {
if (!opts[optDynamic]) throw new Error('Dynamic unevaluated tracing is not enabled')
additionalItems('unevaluatedItems', format('Math.max(%d, ...%s)', stat.items, dyn.items))
} else {
additionalItems('unevaluatedItems', format('%d', stat.items))
}
}
}
const checkObjectsFinal = () => {
prevWrap(node.patternProperties, () => {
prevWrap(stat.patterns.length > 0 || stat.dyn.patterns.length > 0 || stat.unknown, () => {
if (stat.properties.includes(true)) {

@@ -951,5 +888,12 @@ // Everything is statically evaluated, so this check is unreachable. Allow only 'false' rule here.

} else if (node.unevaluatedProperties || node.unevaluatedProperties === false) {
if (isDynamic(stat).properties) throw new Error('Dynamic unevaluated is not implemented')
const sawStatic = (key) => additionalCondition(key, stat.properties, stat.patterns)
additionalProperties(sawStatic, node.unevaluatedProperties, 'unevaluatedProperties')
const notStatic = (key) => additionalCondition(key, stat.properties, stat.patterns)
if (isDynamic(stat).properties) {
if (!opts[optDynamic]) throw new Error('Dynamic unevaluated tracing is not enabled')
scope.propertyIn = functions.propertyIn
const notDynamic = (key) => format('!propertyIn(%s, %s)', key, dyn.props)
const condition = (key) => safeand(notStatic(key), notDynamic(key))
additionalProperties('unevaluatedProperties', condition)
} else {
additionalProperties('unevaluatedProperties', notStatic)
}
}

@@ -962,7 +906,8 @@ })

const performValidation = () => {
if (prev !== null) fun.write('let %s = errorCount', prev)
if (prev !== null) fun.write('const %s = errorCount', prev)
if (checkConst()) {
// const/enum shouldn't have any other validation rules except for already checked type/$ref
enforce(unused.size === 0, 'Unexpected keywords mixed with const or enum:', [...unused])
evaluateDelta({ properties: [true], items: Infinity }) // everything is evaluated for const
const typeKeys = [...types.keys()] // we don't extract type from const/enum, it's enough that we know that it's present
evaluateDelta({ properties: [true], items: Infinity, type: typeKeys, fullstring: true }) // everything is evaluated for const
return

@@ -977,29 +922,65 @@ }

// evaluated: apply static + dynamic
typeWrap(checkArraysFinal, ['array'], types.get('array')(name))
typeWrap(checkObjectsFinal, ['object'], types.get('object')(name))
// evaluated: propagate dynamic to parent dynamic (aka trace)
// static to parent is merged via return value
applyDynamicToDynamic(trace, local.items, local.props)
}
const typeExact = (type) => typeArray && typeArray.length === 1 && typeArray[0] === type
if (current.type)
enforce(typeExact(current.type), 'Only one type is allowed here:', current.type)
const needTypeValidate = !current.type && typeArray !== null && !parentCheckedType(...typeArray)
if (needTypeValidate) {
const filteredTypes = typeArray.filter((t) => typeApplicable(t))
let typeIfAdded = false
handle('type', ['string', 'array'], (type) => {
const typearr = Array.isArray(type) ? type : [type]
for (const t of typearr) enforce(typeof t === 'string' && types.has(t), 'Unknown type:', t)
if (current.type) {
enforce(functions.deepEqual(typearr, [current.type]), 'One type is allowed:', current.type)
evaluateDelta({ type: [current.type] })
return null
}
if (parentCheckedType(...typearr)) return null
const filteredTypes = typearr.filter((t) => typeApplicable(t))
if (filteredTypes.length === 0) fail('No valid types possible')
const typeInvalid = safenot(safeor(...filteredTypes.map((t) => types.get(t)(name))))
errorIf(typeInvalid, { path: ['type'] })
}
evaluateDelta({ type: typeArray })
if (node.type !== undefined) consume('type', 'string', 'array')
evaluateDelta({ type: typearr }) // can be safely done here, filteredTypes already prepared
typeIfAdded = true
return safenot(safeor(...filteredTypes.map((t) => types.get(t)(name))))
})
// If type validation was needed and did not return early, wrap this inside an else clause.
if (needTypeValidate && allErrors) fun.block('else {', [], '}', performValidation)
if (typeIfAdded && allErrors) fun.block('else {', [], '}', performValidation)
else performValidation()
finish()
return stat // return statically evaluated
if (!isSub) {
if (!stat.type) enforceValidation('type')
if (typeApplicable('array') && stat.items !== Infinity)
enforceValidation(node.items ? 'additionalItems or unevaluatedItems' : 'items rule')
if (typeApplicable('object') && !stat.properties.includes(true))
enforceValidation('additionalProperties or unevaluatedProperties')
if (typeof node.propertyNames !== 'object')
for (const sub of ['additionalProperties', 'unevaluatedProperties'])
if (node[sub]) enforceValidation(`wild-card ${sub}`, 'requires propertyNames')
if (!stat.fullstring && requireStringValidation) {
const stringWarning = 'pattern, format or contentSchema must be specified for strings'
fail(`[requireStringValidation] ${stringWarning}, use pattern: ^[\\s\\S]*$ to opt-out`)
}
} else {
const n0 = schemaPath[schemaPath.length - 1]
const n1 = schemaPath[schemaPath.length - 2]
const allowed0 = ['not', 'if', 'then', 'else']
const allowed1 = ['oneOf', 'anyOf', 'allOf', 'dependencies', 'dependentSchemas']
// Sanity check, unreachable, double-check that we came from expected path
enforce(allowed0.includes(n0) || allowed1.includes(n1), 'Unexpected')
}
return finish(local)
}
const stat = visit(format('validate.errors'), [], { name: safe('data') }, schema, [])
const { stat, local } = visit(format('validate.errors'), [], { name: safe('data') }, schema, [])
// evaluated: return dynamic for refs
if (opts[optDynamic] && (isDynamic(stat).items || isDynamic(stat).properties)) {
if (!local) throw new Error('Failed to trace dynamic properties') // Unreachable
fun.write('validate.evaluatedDynamic = [%s, %s]', local.items, local.props)
}
if (allErrors) {

@@ -1019,2 +1000,17 @@ fun.write('return errorCount === 0')

const compile = (schema, opts) => {
try {
const scope = Object.create(null)
return { scope, ref: compileSchema(schema, schema, opts, scope) }
} catch (e) {
// For performance, we try to build the schema without dynamic tracing first, then re-run with
// it enabled if needed. Enabling it without need can give up to about 40% performance drop.
if (e.message === 'Dynamic unevaluated tracing is not enabled') {
const scope = Object.create(null)
return { scope, ref: compileSchema(schema, schema, { ...opts, [optDynamic]: true }, scope) }
}
throw e
}
}
module.exports = { compile }

@@ -11,4 +11,3 @@ 'use strict'

const options = { ...opts, schemas: buildSchemas(schemas || []), isJSON: isJSON || jsonCheck }
const scope = Object.create(null)
const ref = compile(schema, schema, options, scope)
const { scope, ref } = compile(schema, options)
if (opts.dryRun) return

@@ -15,0 +14,0 @@ const fun = genfun()

@@ -19,3 +19,3 @@ 'use strict'

// https://json-schema.org/understanding-json-schema/reference/generic.html
...['description', 'title', 'examples', '$comment'], // unused
...['deprecated', 'description', 'title', 'examples', '$comment'], // unused
]

@@ -22,0 +22,0 @@

@@ -78,4 +78,8 @@ 'use strict'

const safeor = safewrap((...args) => args.join(' || ') || 'false')
const safeand = safewrap((...args) => args.join(' && ') || 'true')
const safeor = safewrap(
(...args) => (args.some((arg) => `${arg}` === 'true') ? 'true' : args.join(' || ') || 'false')
)
const safeand = safewrap(
(...args) => (args.some((arg) => `${arg}` === 'false') ? 'false' : args.join(' && ') || 'true')
)
const safenot = (arg) => {

@@ -82,0 +86,0 @@ if (`${arg}` === 'true') return safe('false')

@@ -62,20 +62,6 @@ 'use strict'

// Fast in Node.js, awful in browsers, no reason to optimize now. Work-around: polyfill Buffer
const deBase64 = (string) => {
if (typeof Buffer !== 'undefined') return Buffer.from(string, 'base64').toString('utf-8')
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
const map = Array(128)
chars.split('').forEach((c, i) => (map[c.charCodeAt(0)] = i.toString(4).padStart(3, 0)))
let tmp = ''
const bytes = new Uint8Array(Math.floor((string.length * 3) / 4))
let filled = 0
for (let i = 0; i < string.length; i++) {
tmp += map[string.charCodeAt(i)] || ''
if (tmp.length >= 4) {
bytes[filled++] = parseInt(tmp.slice(0, 4), 4)
tmp = tmp.slice(4)
}
}
const view = new Uint8Array(bytes.buffer, bytes.byteOffset, Math.min(filled, bytes.length))
return new TextDecoder('utf-8').decode(view)
const b = atob(string)
return new TextDecoder('utf-8').decode(new Uint8Array(b.length).map((_, i) => b.charCodeAt(i)))
}

@@ -91,9 +77,13 @@

const errorMerge = ({ keywordLocation, instanceLocation, ...more }, schemaBase, dataBase) => ({
const errorMerge = ({ keywordLocation, instanceLocation }, schemaBase, dataBase) => ({
keywordLocation: `${schemaBase}${keywordLocation.slice(1)}`,
instanceLocation: `${dataBase}${instanceLocation.slice(1)}`,
...more,
})
const errorUtils = { toPointer, pointerPart, errorMerge }
module.exports = { stringLength, isMultipleOf, deepEqual, unique, deBase64, hasOwn, ...errorUtils }
const propertyIn = (key, [properties, patterns]) =>
properties.includes(true) ||
properties.some((prop) => prop === key) ||
patterns.some((pattern) => new RegExp(pattern, 'u').test(key))
const extraUtils = { toPointer, pointerPart, errorMerge, propertyIn }
module.exports = { stringLength, isMultipleOf, deepEqual, unique, deBase64, hasOwn, ...extraUtils }

@@ -15,3 +15,3 @@ 'use strict'

const initTracing = () => ({
...{ properties: [], patterns: [], required: [], items: 0, type: null },
...{ properties: [], patterns: [], required: [], items: 0, type: null, fullstring: false },
dyn: { properties: [], patterns: [], items: 0 },

@@ -23,2 +23,3 @@ unknown: false,

const wrapFun = (f) => (...args) => f(...args.map(wrap))
const stringValidated = (A) => A.fullstring || (A.type && !A.type.includes('string'))

@@ -32,2 +33,3 @@ // Result means that both sets A and B are correct

type: A.type && B.type ? [...new Set([...A.type, ...B.type])] : null,
fullstring: stringValidated(A) || stringValidated(B),
dyn: {

@@ -63,2 +65,3 @@ items: Math.max(A.dyn.items, B.dyn.items),

type: A.type && B.type ? A.type.filter((x) => B.type.includes(x)) : null,
fullstring: stringValidated(A) && stringValidated(B),
dyn: {

@@ -79,2 +82,3 @@ items: Math.max(A.items, B.items, A.dyn.items, B.dyn.items),

stat.type = stat.type ? stat.type.filter((x) => delta.type.includes(x)) : delta.type
if (delta.fullstring || (stat.type && !stat.type.includes('string'))) stat.fullstring = true
if (delta.dyn) stat.dyn.items = Math.max(stat.dyn.items, delta.dyn.items)

@@ -87,6 +91,6 @@ if (delta.dyn) stat.dyn.properties.push(...delta.dyn.properties)

const isDynamic = wrapFun(({ unknown, items, dyn, ...stat }) => ({
items: unknown || dyn.items > items,
properties: unknown || !inProperties(stat, dyn),
items: items !== Infinity && (unknown || dyn.items > items),
properties: !stat.properties.includes(true) && (unknown || !inProperties(stat, dyn)),
}))
module.exports = { initTracing, andDelta, orDelta, applyDelta, isDynamic }
module.exports = { initTracing, andDelta, orDelta, applyDelta, isDynamic, inProperties }
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc