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

@exodus/schemasafe

Package Overview
Dependencies
Maintainers
36
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-alpha.2 to 1.0.0-alpha.3

2

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

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

@@ -131,2 +131,3 @@ # `@exodus/schemasafe`

* (function() {
* 'use strict'
* const format0 = (value) => /^0x[0-9A-Fa-f]*$/.test(value);

@@ -136,9 +137,4 @@ * return (function validate(data) {

* let errors = 0
* if (!(typeof data === "string")) {
* return false
* } else {
* if (!format0(data)) {
* return false
* }
* }
* if (!(typeof data === "string")) return false
* if (!format0(data)) return false
* return errors === 0

@@ -145,0 +141,0 @@ * })})();

@@ -49,5 +49,7 @@ 'use strict'

const noopRegExps = new Set(['^[\\s\\S]*$', '^[\\S\\s]*$', '^[^]*$', '', '.*'])
// Helper methods for semi-structured paths
const propvar = (name, key) => ({ parent: name, keyname: key }) // property by variable
const propimm = (name, val) => ({ parent: name, keyval: val }) // property by immediate value
const propvar = (parent, keyname, inKeys = false) => ({ parent, keyname, inKeys }) // property by variable
const propimm = (parent, keyval) => ({ parent, keyval }) // property by immediate value
const buildName = ({ name, parent, keyval, keyname }) => {

@@ -75,2 +77,3 @@ if (name) {

useDefaults = false,
removeAdditional = false, // supports additionalProperties: false and additionalItems: false
includeErrors: optIncludeErrors = false,

@@ -84,2 +87,4 @@ allErrors: optAllErrors = false,

complexityChecks = opts.mode === 'strong',
isJSON: optIsJSON = false, // assume input to be JSON, which e.g. makes undefined impossible
jsonCheck = false, // disabled by default, it's assumed that data is from JSON.parse
$schemaDefault = null,

@@ -106,2 +111,5 @@ formats: optFormats = {},

throw new Error('Strong mode forbids weakFormats and allowUnusedKeywords')
if (optIsJSON && jsonCheck)
throw new Error('Can not specify both isJSON and jsonCheck options, please choose one')
const isJSON = optIsJSON || jsonCheck

@@ -136,12 +144,17 @@ if (!scope[scopeCache])

const name = buildName(location) // also checks for sanity, do not remove
const { parent, keyval, keyname } = location
if (parent) {
const { parent, keyval, keyname, inKeys } = location
if (inKeys) {
/* c8 ignore next */
if (isJSON) throw new Error('Unreachable: useless check, can not be undefined')
return format('%s !== undefined', name)
}
if (parent && keyname) {
scope.hasOwn = functions.hasOwn
if (keyval) {
return format('%s !== undefined && hasOwn(%s, %j)', name, parent, keyval)
} else if (keyname) {
return format('%s !== undefined && hasOwn(%s, %s)', name, parent, keyname)
}
return format('%s !== undefined && hasOwn(%s, %s)', name, parent, keyname)
} else if (parent && keyval !== undefined) {
scope.hasOwn = functions.hasOwn
return format('%s !== undefined && hasOwn(%s, %j)', name, parent, keyval)
}
return format('%s !== undefined', name)
/* c8 ignore next */
throw new Error('Unreachable: present() check without parent')
}

@@ -156,8 +169,11 @@

let jsonCheckPerformed = false
const getMeta = () => rootMeta.get(root) || {}
const basePathStack = basePathRoot ? [basePathRoot] : []
const visit = (allErrors, includeErrors, current, node, schemaPath) => {
const visit = (allErrors, includeErrors, history, current, node, schemaPath) => {
// e.g. top-level data and property names, OR already checked by present() in history, OR in keys and not undefined
const definitelyPresent =
!current.parent || history.includes(current) || (current.inKeys && isJSON)
const name = buildName(current)
const rule = (...args) => visit(allErrors, includeErrors, ...args)
const subrule = (...args) => visit(true, false, ...args)
const writeErrorObject = (error) => {

@@ -175,9 +191,7 @@ if (allErrors) {

if (includeErrors === true) {
const leanError = { field: prop || name, message: msg }
const errorObj = { field: prop || name, message: msg, schemaPath: toPointer(schemaPath) }
if (verboseErrors) {
const type = node.type || 'any'
const fullError = { ...leanError, type, schemaPath: toPointer(schemaPath) }
writeErrorObject(format('{ ...%j, value: %s }', fullError, value || name))
writeErrorObject(format('{ ...%j, value: %s }', errorObj, value || name))
} else {
writeErrorObject(format('%j', leanError))
writeErrorObject(format('%j', errorObj))
}

@@ -192,5 +206,15 @@ }

const errorIf = (fmt, args, ...errorArgs) => {
fun.write('if (%s) {', format(fmt, ...args))
error(...errorArgs)
fun.write('}')
const condition = format(fmt, ...args)
if (includeErrors === false) {
// in this case, we can fast-track and inline this to generate more readable code
if (allErrors) {
fun.write('if (%s) errors++', condition)
} else {
fun.write('if (%s) return false', condition)
}
} else {
fun.write('if (%s) {', condition)
error(...errorArgs)
fun.write('}')
}
}

@@ -206,2 +230,11 @@

// JSON check is once only for the top-level object, before everything else
if (jsonCheck && !jsonCheckPerformed) {
/* c8 ignore next */
if (`${name}` !== 'data') throw new Error('Unreachable: invalid json check')
scope.deepEqual = functions.deepEqual
errorIf('!deepEqual(%s, JSON.parse(JSON.stringify(%s)))', [name, name], 'not JSON compatible')
jsonCheckPerformed = true
}
if (typeof node === 'boolean') {

@@ -211,2 +244,5 @@ if (node === true) {

enforceValidation('schema = true is not allowed')
} else if (definitelyPresent) {
// node === false always fails in this case
error('is unexpected')
} else {

@@ -223,2 +259,7 @@ // node === false

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

@@ -233,5 +274,4 @@ const consume = (prop, ...ruleTypes) => {

const isTopLevel = !current.parent // e.g. top-level data and property names
const finish = () => {
if (!isTopLevel) fun.write('}') // undefined check
if (!definitelyPresent) fun.write('}') // undefined check
enforce(unused.size === 0 || allowUnusedKeywords, 'Unprocessed keywords:', [...unused])

@@ -283,7 +323,6 @@ }

const defaultIsPresent = node.default !== undefined && useDefaults // will consume on use
if (isTopLevel) {
// top-level data is coerced to null above, or is an object key, it can't be undefined
if (defaultIsPresent) fail('Can not apply default value at root')
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 at root')
fail('Can not apply boolean required here (e.g. at root)')
} else if (defaultIsPresent || booleanRequired) {

@@ -318,3 +357,4 @@ fun.write('if (!(%s)) {', present(current))

scope[n] = (...args) => fn(...args)
fn = compile(sub, subRoot, { ...opts, includeErrors: false }, scope, path)
const override = { includeErrors: false, jsonCheck: false, isJSON }
fn = compile(sub, subRoot, { ...opts, ...override }, scope, path)
scope[n] = fn

@@ -367,2 +407,6 @@ }

// Can not be used before undefined check above! The one performed by present()
const rule = (...args) => visit(allErrors, includeErrors, [...history, current], ...args)
const subrule = (...args) => visit(true, false, [...history, current], ...args)
/* Checks inside blocks are independent, they are happening on the same code depth */

@@ -444,3 +488,3 @@

enforceRegex(node.pattern)
if (node.pattern !== '^[\\s\\S]*$' && node.pattern !== '^[\\S\\s]*$') {
if (!noopRegExps.has(node.pattern)) {
const p = patterns(node.pattern)

@@ -491,3 +535,8 @@ errorIf('!%s.test(%s)', [p, name], 'pattern mismatch')

} else if (node.additionalItems === false) {
errorIf('%s.length > %d', [name, node.items.length], 'has additional items')
const limit = node.items.length
if (removeAdditional) {
fun.write('if (%s.length > %d) %s.length = %d', name, limit, name, limit)
} else {
errorIf('%s.length > %d', [name, limit], 'has additional items')
}
consume('additionalItems', 'boolean')

@@ -513,11 +562,7 @@ } else if (node.additionalItems) {

const i = genloop()
fun.write('for (let %s = 0; %s < %s.length; %s++) {', i, i, name, i)
fun.write('const %s = errors', prev)
subrule(propvar(name, i), node.contains, subPath('contains'))
fun.write('if (%s === errors) {', prev)
fun.write('%s++', passes)
fun.write('} else {')
fun.write('errors = %s', prev)
fun.write('}')
fun.write('}')
fun.block('for (let %s = 0; %s < %s.length; %s++) {', [i, i, name, i], '}', () => {
fun.write('const %s = errors', prev)
subrule(propvar(name, i), node.contains, subPath('contains'))
fun.write('if (%s === errors) { %s++ } else errors = %s', prev, passes, prev)
})

@@ -633,3 +678,4 @@ if (Number.isFinite(node.minContains)) {

fun.block('if (%s.test(%s)) {', [patterns(p), key], '}', () => {
rule(propvar(name, key), node.patternProperties[p], subPath('patternProperties', p))
const sub = propvar(name, key, true) // always own property, from Object.keys
rule(sub, node.patternProperties[p], subPath('patternProperties', p))
})

@@ -652,5 +698,10 @@ }

if (node.additionalProperties === false) {
error('has additional properties', null, format('%j + %s', `${name}.`, key))
if (removeAdditional) {
fun.write('delete %s[%s]', name, key)
} else {
error('has additional properties', null, format('%j + %s', `${name}.`, key))
}
} else {
rule(propvar(name, key), node.additionalProperties, subPath('additionalProperties'))
const sub = propvar(name, key, true) // always own property, from Object.keys
rule(sub, node.additionalProperties, subPath('additionalProperties'))
}

@@ -685,5 +736,3 @@ })

error('negative schema matches')
fun.write('} else {')
fun.write('errors = %s', prev)
fun.write('}')
fun.write('} else errors = %s', prev)
consume('not', 'object', 'boolean')

@@ -734,3 +783,3 @@ }

node.anyOf.forEach((sch, i) => {
if (i) fun.write('}')
if (i > 0) fun.write('}')
})

@@ -752,7 +801,3 @@ fun.write('if (%s !== errors) {', prev)

subrule(current, sch, schemaPath)
fun.write('if (%s === errors) {', prev)
fun.write('%s++', passes)
fun.write('} else {')
fun.write('errors = %s', prev)
fun.write('}')
fun.write('if (%s === errors) { %s++ } else errors = %s', prev, passes, prev)
}

@@ -781,11 +826,7 @@ errorIf('%s !== 1', [passes], 'no (or more than one) schemas match')

const needTypeValidation = `${typeValidate}` !== 'true'
if (needTypeValidation) {
fun.write('if (!(%s)) {', typeValidate)
error('is the wrong type')
}
if (needTypeValidation) errorIf('!(%s)', [typeValidate], 'is the wrong type')
if (type) consume('type', 'string', 'array')
// If type validation was needed, we should wrap this inside an else clause.
// No need to close, type validation would always close at the end if it's used.
maybeWrap(needTypeValidation, '} else {', [], '', () => {
// If type validation was needed and did not return early, wrap this inside an else clause.
maybeWrap(needTypeValidation && allErrors, 'else {', [], '}', () => {
typeWrap(checkNumbers, ['number', 'integer'], types.get('number')(name))

@@ -798,8 +839,6 @@ typeWrap(checkStrings, ['string'], types.get('string')(name))

if (needTypeValidation) fun.write('}') // type check
finish()
}
visit(optAllErrors, optIncludeErrors, { name: safe('data') }, schema, [])
visit(optAllErrors, optIncludeErrors, [], { name: safe('data') }, schema, [])

@@ -821,3 +860,3 @@ fun.write('return errors === 0')

// strong mode is default in parser
const validate = validator(schema, { mode: 'strong', ...opts })
const validate = validator(schema, { mode: 'strong', ...opts, jsonCheck: false, isJSON: true })
const parse = (src) => {

@@ -830,3 +869,5 @@ if (typeof src !== 'string') throw new Error('Invalid type!')

: ''
throw new Error(`JSON validation error${message ? `: ${message}` : ''}`)
const error = new Error(`JSON validation error${message ? `: ${message}` : ''}`)
error.errors = validate.errors
throw error
}

@@ -833,0 +874,0 @@ parse.toModule = () =>

'use strict'
const { format } = require('./safe-format')
const isArrowFnWithParensRegex = /^\([^)]*\) *=>/

@@ -29,3 +31,3 @@ const isArrowFnWithoutParensRegex = /^[^=]*=>/

const proto = Object.getPrototypeOf(item)
if (item instanceof RegExp && proto === RegExp.prototype) return String(item)
if (item instanceof RegExp && proto === RegExp.prototype) return format('%r', item)
throw new Error('Can not stringify an object with unexpected prototype')

@@ -32,0 +34,0 @@ }

'use strict'
function toPointer(path) {
if (path.length === 0) return ''
if (path.length === 0) return '#'
return `#/${path.map((part) => `${part}`.replace(/~/g, '~0').replace(/\//g, '~1')).join('/')}`

@@ -6,0 +6,0 @@ }

@@ -6,5 +6,13 @@ 'use strict'

const compares = new Set(['<', '>', '<=', '>='])
const escapeCode = (code) => `\\u${code.toString(16).padStart(4, '0')}`
// Supports simple js variables only, i.e. constants and JSON-stringifiable
const jsval = (val) => {
if ([Infinity, -Infinity, NaN, undefined].includes(val)) return `${val}`
// https://v8.dev/features/subsume-json#security, e.g. {'\u2028':0} on Node.js 8
return JSON.stringify(val).replace(/[\u2028\u2029]/g, (char) => escapeCode(char.charCodeAt(0)))
}
const format = (fmt, ...args) => {
const res = fmt.replace(/%[%dscj]/g, (match) => {
const res = fmt.replace(/%[%drscj]/g, (match) => {
if (match === '%%') return '%'

@@ -17,2 +25,6 @@ if (args.length === 0) throw new Error('Unexpected arguments count')

throw new Error('Expected a number')
case '%r':
// String(regex) is not ok on Node.js 10 and below: console.log(String(new RegExp('\n')))
if (val instanceof RegExp) return format('new RegExp(%j, %j)', val.source, val.flags)
throw new Error('Expected a RegExp instance')
case '%s':

@@ -25,4 +37,3 @@ if (val instanceof SafeString) return val

case '%j':
if ([Infinity, -Infinity, NaN, undefined].includes(val)) return `${val}`
return JSON.stringify(val)
return jsval(val)
}

@@ -29,0 +40,0 @@ /* c8 ignore next */

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