/* eslint-disable security/detect-non-literal-regexp */ import type { OutputUnit, Schema, SchemaDraft, ValidationResult } from './types' import { coerceValue, getInstanceType } from './coercion' import { deepCompareStrict } from './deep-compare-strict' import { dereference } from './dereference' import { format } from './format' import { encodePointer } from './pointer' import { ucs2length } from './ucs2-length' export type Evaluated = Record export function validate( instance: any, schema: Schema | boolean, draft: SchemaDraft = '2019-09', lookup: Record = dereference(schema), coerce = false, shortCircuit = true, recursiveAnchor: Schema | null = null, instanceLocation = '#', schemaLocation = '#', evaluated: Evaluated = Object.create(null) ): ValidationResult { if (schema === true) { return { valid: true, errors: [], instance } } if (schema === false) { return { valid: false, instance, errors: [ { instanceLocation, keyword: 'false', keywordLocation: instanceLocation, error: 'False boolean schema.' } ] } } let instanceType = getInstanceType(instance) const { $ref, $recursiveRef, $recursiveAnchor, type: $type, const: $const, enum: $enum, required: $required, not: $not, anyOf: $anyOf, allOf: $allOf, oneOf: $oneOf, if: $if, then: $then, else: $else, format: $format, properties: $properties, patternProperties: $patternProperties, additionalProperties: $additionalProperties, unevaluatedProperties: $unevaluatedProperties, minProperties: $minProperties, maxProperties: $maxProperties, propertyNames: $propertyNames, dependentRequired: $dependentRequired, dependentSchemas: $dependentSchemas, dependencies: $dependencies, prefixItems: $prefixItems, items: $items, additionalItems: $additionalItems, unevaluatedItems: $unevaluatedItems, contains: $contains, minContains: $minContains, maxContains: $maxContains, minItems: $minItems, maxItems: $maxItems, uniqueItems: $uniqueItems, minimum: $minimum, maximum: $maximum, exclusiveMinimum: $exclusiveMinimum, exclusiveMaximum: $exclusiveMaximum, multipleOf: $multipleOf, minLength: $minLength, maxLength: $maxLength, pattern: $pattern, // eslint-disable-next-line @typescript-eslint/naming-convention __absolute_ref__, // eslint-disable-next-line @typescript-eslint/naming-convention __absolute_recursive_ref__ } = schema const errors: OutputUnit[] = [] if ($recursiveAnchor === true && recursiveAnchor === null) { recursiveAnchor = schema } if ($recursiveRef === '#') { const refSchema = recursiveAnchor === null ? (lookup[__absolute_recursive_ref__!] as Schema) : recursiveAnchor const keywordLocation = `${schemaLocation}/$recursiveRef` const result = validate( instance, recursiveAnchor === null ? schema : recursiveAnchor, draft, lookup, coerce, shortCircuit, refSchema, instanceLocation, keywordLocation, evaluated ) if (result.valid) { instance = result.instance } else { errors.push( { instanceLocation, keyword: '$recursiveRef', keywordLocation, error: 'A subschema had errors.' }, ...result.errors ) } } if ($ref !== undefined) { const uri = __absolute_ref__ || $ref const refSchema = lookup[uri] if (refSchema === undefined) { let message = `Unresolved $ref "${$ref}".` if (__absolute_ref__ && __absolute_ref__ !== $ref) { message += ` Absolute URI "${__absolute_ref__}".` } message += `\nKnown schemas:\n- ${Object.keys(lookup).join('\n- ')}` throw new Error(message) } const keywordLocation = `${schemaLocation}/$ref` const result = validate( instance, refSchema, draft, lookup, coerce, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated ) if (result.valid) { instance = result.instance } else { errors.push( { instanceLocation, keyword: '$ref', keywordLocation, error: 'A subschema had errors.' }, ...result.errors ) } if (draft === '4' || draft === '7') { return { valid: errors.length === 0, instance, errors } } } if (Array.isArray($type)) { const length = $type.length let valid = false for (let i = 0; i < length; i++) { if ( instanceType === $type[i] || ($type[i] === 'integer' && instanceType === 'number' && instance % 1 === 0 && Number.isNaN(instance)) ) { valid = true break } } if (!valid) { errors.push({ instanceLocation, keyword: 'type', keywordLocation: `${schemaLocation}/type`, error: `Instance type "${instanceType}" is invalid. Expected "${$type.join( '", "' )}".` }) } } else if ($type === 'integer') { if (instanceType !== 'number' || instance % 1 || Number.isNaN(instance)) { const coercedInstance = coerce ? coerceValue({ instance, instanceType, $type }) : undefined if (coercedInstance !== undefined) { instance = coercedInstance instanceType = getInstanceType(instance) } else { errors.push({ instanceLocation, keyword: 'type', keywordLocation: `${schemaLocation}/type`, error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` }) } } } else if ($type !== undefined && instanceType !== $type) { const coercedInstance = coerce ? coerceValue({ instance, instanceType, $type }) : undefined if (coercedInstance !== undefined) { instance = coercedInstance instanceType = getInstanceType(instance) } else { errors.push({ instanceLocation, keyword: 'type', keywordLocation: `${schemaLocation}/type`, error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` }) } } if ($const !== undefined) { if (instanceType === 'object' || instanceType === 'array') { if (!deepCompareStrict(instance, $const)) { errors.push({ instanceLocation, keyword: 'const', keywordLocation: `${schemaLocation}/const`, error: `Instance does not match ${JSON.stringify($const)}.` }) } } else if (instance !== $const) { errors.push({ instanceLocation, keyword: 'const', keywordLocation: `${schemaLocation}/const`, error: `Instance does not match ${JSON.stringify($const)}.` }) } } if ($enum !== undefined) { if (instanceType === 'object' || instanceType === 'array') { if (!$enum.some((value) => deepCompareStrict(instance, value))) { errors.push({ instanceLocation, keyword: 'enum', keywordLocation: `${schemaLocation}/enum`, error: `Instance does not match any of ${JSON.stringify($enum)}.` }) } } else if (!$enum.includes(instance)) { errors.push({ instanceLocation, keyword: 'enum', keywordLocation: `${schemaLocation}/enum`, error: `Instance does not match any of ${JSON.stringify($enum)}.` }) } } // TODO: type coercion if ($not !== undefined) { const keywordLocation = `${schemaLocation}/not` const result = validate( instance, $not, draft, lookup, coerce, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation /*, evaluated*/ ) if (result.valid) { errors.push({ instanceLocation, keyword: 'not', keywordLocation, error: 'Instance matched "not" schema.' }) } } const subEvaluateds: Array = [] // TODO: type coercion if ($anyOf !== undefined) { const keywordLocation = `${schemaLocation}/anyOf` const errorsLength = errors.length let anyValid = false for (const [i, subSchema] of $anyOf.entries()) { const subEvaluated: Evaluated = Object.create(evaluated) const result = validate( instance, subSchema, draft, lookup, coerce, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated ) errors.push(...result.errors) anyValid = anyValid || result.valid if (result.valid) { subEvaluateds.push(subEvaluated) } } if (anyValid) { errors.length = errorsLength } else { errors.splice(errorsLength, 0, { instanceLocation, keyword: 'anyOf', keywordLocation, error: 'Instance does not match any subschemas.' }) } } // TODO: type coercion if ($allOf !== undefined) { const keywordLocation = `${schemaLocation}/allOf` const errorsLength = errors.length let allValid = true for (const [i, subSchema] of $allOf.entries()) { const subEvaluated: Evaluated = Object.create(evaluated) const result = validate( instance, subSchema, draft, lookup, coerce, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated ) errors.push(...result.errors) allValid = allValid && result.valid if (result.valid) { subEvaluateds.push(subEvaluated) } } if (allValid) { errors.length = errorsLength } else { errors.splice(errorsLength, 0, { instanceLocation, keyword: 'allOf', keywordLocation, error: `Instance does not match every subschema.` }) } } // TODO: type coercion if ($oneOf !== undefined) { const keywordLocation = `${schemaLocation}/oneOf` const errorsLength = errors.length const matches = $oneOf.filter((subSchema, i) => { const subEvaluated: Evaluated = Object.create(evaluated) const result = validate( instance, subSchema, draft, lookup, coerce, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated ) errors.push(...result.errors) if (result.valid) { subEvaluateds.push(subEvaluated) } return result.valid }).length if (matches === 1) { errors.length = errorsLength } else { errors.splice(errorsLength, 0, { instanceLocation, keyword: 'oneOf', keywordLocation, error: `Instance does not match exactly one subschema (${matches} matches).` }) } } if (instanceType === 'object' || instanceType === 'array') { Object.assign(evaluated, ...subEvaluateds) } if ($if !== undefined) { const keywordLocation = `${schemaLocation}/if` const conditionResult = validate( instance, $if, draft, lookup, coerce, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated ).valid if (conditionResult) { if ($then !== undefined) { const thenResult = validate( instance, $then, draft, lookup, coerce, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/then`, evaluated ) if (thenResult.valid) { instance = thenResult.instance } else { errors.push( { instanceLocation, keyword: 'if', keywordLocation, error: `Instance does not match "then" schema.` }, ...thenResult.errors ) } } } else if ($else !== undefined) { const elseResult = validate( instance, $else, draft, lookup, coerce, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/else`, evaluated ) if (elseResult.valid) { instance = elseResult.instance } else { errors.push( { instanceLocation, keyword: 'if', keywordLocation, error: `Instance does not match "else" schema.` }, ...elseResult.errors ) } } } if (instanceType === 'object') { if ($required !== undefined) { for (const key of $required) { if (!(key in instance)) { errors.push({ instanceLocation, keyword: 'required', keywordLocation: `${schemaLocation}/required`, error: `Instance does not have required property "${key}".` }) } } } const keys = Object.keys(instance) if ($minProperties !== undefined && keys.length < $minProperties) { errors.push({ instanceLocation, keyword: 'minProperties', keywordLocation: `${schemaLocation}/minProperties`, error: `Instance does not have at least ${$minProperties} properties.` }) } if ($maxProperties !== undefined && keys.length > $maxProperties) { errors.push({ instanceLocation, keyword: 'maxProperties', keywordLocation: `${schemaLocation}/maxProperties`, error: `Instance does not have at least ${$maxProperties} properties.` }) } if ($propertyNames !== undefined) { const keywordLocation = `${schemaLocation}/propertyNames` for (const key in instance) { const subInstancePointer = `${instanceLocation}/${encodePointer(key)}` const result = validate( key, $propertyNames, draft, lookup, coerce, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation ) if (!result.valid) { errors.push( { instanceLocation, keyword: 'propertyNames', keywordLocation, error: `Property name "${key}" does not match schema.` }, ...result.errors ) } } } if ($dependentRequired !== undefined) { const keywordLocation = `${schemaLocation}/dependantRequired` for (const key in $dependentRequired) { if (key in instance) { const required = $dependentRequired[key] as string[] for (const dependantKey of required) { if (!(dependantKey in instance)) { errors.push({ instanceLocation, keyword: 'dependentRequired', keywordLocation, error: `Instance has "${key}" but does not have "${dependantKey}".` }) } } } } } if ($dependentSchemas !== undefined) { for (const key in $dependentSchemas) { const keywordLocation = `${schemaLocation}/dependentSchemas` if (key in instance) { const result = validate( instance, $dependentSchemas[key]!, draft, lookup, coerce, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`, evaluated ) if (!result.valid) { errors.push( { instanceLocation, keyword: 'dependentSchemas', keywordLocation, error: `Instance has "${key}" but does not match dependant schema.` }, ...result.errors ) } } } } if ($dependencies !== undefined) { const keywordLocation = `${schemaLocation}/dependencies` for (const key in $dependencies) { if (key in instance) { const propsOrSchema = $dependencies[key] as Schema | string[] if (Array.isArray(propsOrSchema)) { for (const dependantKey of propsOrSchema) { if (!(dependantKey in instance)) { errors.push({ instanceLocation, keyword: 'dependencies', keywordLocation, error: `Instance has "${key}" but does not have "${dependantKey}".` }) } } } else { const result = validate( instance, propsOrSchema, draft, lookup, coerce, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}` ) if (!result.valid) { errors.push( { instanceLocation, keyword: 'dependencies', keywordLocation, error: `Instance has "${key}" but does not match dependant schema.` }, ...result.errors ) } } } } } const thisEvaluated = Object.create(null) let stop = false if ($properties !== undefined) { const keywordLocation = `${schemaLocation}/properties` for (const key in $properties) { if (!(key in instance)) { continue } const subInstancePointer = `${instanceLocation}/${encodePointer(key)}` const result = validate( instance[key], $properties[key]!, draft, lookup, coerce, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(key)}` ) if (result.valid) { evaluated[key] = thisEvaluated[key] = true instance[key] = result.instance } else { stop = shortCircuit errors.push( { instanceLocation, keyword: 'properties', keywordLocation, error: `Property "${key}" does not match schema.` }, ...result.errors ) if (stop) break } } } if (!stop && $patternProperties !== undefined) { const keywordLocation = `${schemaLocation}/patternProperties` for (const pattern in $patternProperties) { const regex = new RegExp(pattern, 'u') const subSchema = $patternProperties[pattern] for (const key in instance) { if (!regex.test(key)) { continue } const subInstancePointer = `${instanceLocation}/${encodePointer(key)}` const result = validate( instance[key], subSchema!, draft, lookup, coerce, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(pattern)}` ) if (result.valid) { evaluated[key] = thisEvaluated[key] = true instance[key] = result.instance } else { stop = shortCircuit errors.push( { instanceLocation, keyword: 'patternProperties', keywordLocation, error: `Property "${key}" matches pattern "${pattern}" but does not match associated schema.` }, ...result.errors ) } } } } if (!stop && $additionalProperties !== undefined) { const keywordLocation = `${schemaLocation}/additionalProperties` for (const key in instance) { if (thisEvaluated[key]) { continue } const subInstancePointer = `${instanceLocation}/${encodePointer(key)}` const result = validate( instance[key], $additionalProperties, draft, lookup, coerce, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation ) if (result.valid) { evaluated[key] = true instance[key] = result.instance } else { stop = shortCircuit errors.push( { instanceLocation, keyword: 'additionalProperties', keywordLocation, error: `Property "${key}" does not match additional properties schema.` }, ...result.errors ) } } } else if (!stop && $unevaluatedProperties !== undefined) { const keywordLocation = `${schemaLocation}/unevaluatedProperties` for (const key in instance) { if (!evaluated[key]) { const subInstancePointer = `${instanceLocation}/${encodePointer(key)}` const result = validate( instance[key], $unevaluatedProperties, draft, lookup, coerce, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation ) if (result.valid) { evaluated[key] = true instance[key] = result.instance } else { errors.push( { instanceLocation, keyword: 'unevaluatedProperties', keywordLocation, error: `Property "${key}" does not match unevaluated properties schema.` }, ...result.errors ) } } } } } else if (instanceType === 'array') { if ($maxItems !== undefined && instance.length > $maxItems) { errors.push({ instanceLocation, keyword: 'maxItems', keywordLocation: `${schemaLocation}/maxItems`, error: `Array has too many items (${instance.length} > ${$maxItems}).` }) } if ($minItems !== undefined && instance.length < $minItems) { errors.push({ instanceLocation, keyword: 'minItems', keywordLocation: `${schemaLocation}/minItems`, error: `Array has too few items (${instance.length} < ${$minItems}).` }) } const length: number = instance.length let i = 0 let stop = false if ($prefixItems !== undefined) { const keywordLocation = `${schemaLocation}/prefixItems` const length2 = Math.min($prefixItems.length, length) for (; i < length2; i++) { const result = validate( instance[i], $prefixItems[i]!, draft, lookup, coerce, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}` ) evaluated[i] = true if (!result.valid) { stop = shortCircuit errors.push( { instanceLocation, keyword: 'prefixItems', keywordLocation, error: `Items did not match schema.` }, ...result.errors ) if (stop) break } } } if ($items !== undefined) { const keywordLocation = `${schemaLocation}/items` if (Array.isArray($items)) { const length2 = Math.min($items.length, length) for (; i < length2; i++) { const result = validate( instance[i], $items[i]!, draft, lookup, coerce, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}` ) evaluated[i] = true if (result.valid) { instance[i] = result.instance } else { stop = shortCircuit errors.push( { instanceLocation, keyword: 'items', keywordLocation, error: `Items did not match schema.` }, ...result.errors ) if (stop) break } } } else { for (; i < length; i++) { const result = validate( instance[i], $items, draft, lookup, coerce, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation ) evaluated[i] = true if (result.valid) { instance[i] = result.instance } else { stop = shortCircuit errors.push( { instanceLocation, keyword: 'items', keywordLocation, error: `Items did not match schema.` }, ...result.errors ) if (stop) break } } } if (!stop && $additionalItems !== undefined) { const keywordLocation = `${schemaLocation}/additionalItems` for (; i < length; i++) { const result = validate( instance[i], $additionalItems, draft, lookup, coerce, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation ) evaluated[i] = true if (result.valid) { instance[i] = result.instance } else if (!result.valid) { stop = shortCircuit errors.push( { instanceLocation, keyword: 'additionalItems', keywordLocation, error: `Items did not match additional items schema.` }, ...result.errors ) } } } } if ($contains !== undefined) { if (length === 0 && $minContains === undefined) { errors.push({ instanceLocation, keyword: 'contains', keywordLocation: `${schemaLocation}/contains`, error: `Array is empty. It must contain at least one item matching the schema.` }) } else if ($minContains !== undefined && length < $minContains) { errors.push({ instanceLocation, keyword: 'minContains', keywordLocation: `${schemaLocation}/minContains`, error: `Array has less items (${length}) than minContains (${$minContains}).` }) } else { const keywordLocation = `${schemaLocation}/contains` const errorsLength = errors.length let contained = 0 for (let j = 0; j < length; j++) { const result = validate( instance[j], $contains, draft, lookup, coerce, shortCircuit, recursiveAnchor, `${instanceLocation}/${j}`, keywordLocation ) if (result.valid) { evaluated[j] = true contained++ } else { errors.push(...result.errors) } } if (contained >= ($minContains || 0)) { errors.length = errorsLength } if ( $minContains === undefined && $maxContains === undefined && contained === 0 ) { errors.splice(errorsLength, 0, { instanceLocation, keyword: 'contains', keywordLocation, error: `Array does not contain item matching schema.` }) } else if ($minContains !== undefined && contained < $minContains) { errors.push({ instanceLocation, keyword: 'minContains', keywordLocation: `${schemaLocation}/minContains`, error: `Array must contain at least ${$minContains} items matching schema. Only ${contained} items were found.` }) } else if ($maxContains !== undefined && contained > $maxContains) { errors.push({ instanceLocation, keyword: 'maxContains', keywordLocation: `${schemaLocation}/maxContains`, error: `Array may contain at most ${$maxContains} items matching schema. ${contained} items were found.` }) } } } if (!stop && $unevaluatedItems !== undefined) { const keywordLocation = `${schemaLocation}/unevaluatedItems` for (i; i < length; i++) { if (evaluated[i]) { continue } const result = validate( instance[i], $unevaluatedItems, draft, lookup, coerce, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation ) evaluated[i] = true if (result.valid) { instance[i] = result.instance } else { errors.push( { instanceLocation, keyword: 'unevaluatedItems', keywordLocation, error: `Items did not match unevaluated items schema.` }, ...result.errors ) } } } if ($uniqueItems) { for (let j = 0; j < length; j++) { const a = instance[j] const ao = typeof a === 'object' && a !== null for (let k = 0; k < length; k++) { if (j === k) { continue } const b = instance[k] const bo = typeof b === 'object' && b !== null if (a === b || (ao && bo && deepCompareStrict(a, b))) { errors.push({ instanceLocation, keyword: 'uniqueItems', keywordLocation: `${schemaLocation}/uniqueItems`, error: `Duplicate items at indexes ${j} and ${k}.` }) j = Number.MAX_SAFE_INTEGER k = Number.MAX_SAFE_INTEGER } } } } } else if (instanceType === 'number') { if (draft === '4') { if ( $minimum !== undefined && (($exclusiveMinimum === true && instance <= $minimum) || instance < $minimum) ) { errors.push({ instanceLocation, keyword: 'minimum', keywordLocation: `${schemaLocation}/minimum`, error: `${instance} is less than ${ $exclusiveMinimum ? 'or equal to ' : '' } ${$minimum}.` }) } if ( $maximum !== undefined && (($exclusiveMaximum === true && instance >= $maximum) || instance > $maximum) ) { errors.push({ instanceLocation, keyword: 'maximum', keywordLocation: `${schemaLocation}/maximum`, error: `${instance} is greater than ${ $exclusiveMaximum ? 'or equal to ' : '' } ${$maximum}.` }) } } else { if ($minimum !== undefined && instance < $minimum) { errors.push({ instanceLocation, keyword: 'minimum', keywordLocation: `${schemaLocation}/minimum`, error: `${instance} is less than ${$minimum}.` }) } if ($maximum !== undefined && instance > $maximum) { errors.push({ instanceLocation, keyword: 'maximum', keywordLocation: `${schemaLocation}/maximum`, error: `${instance} is greater than ${$maximum}.` }) } if ($exclusiveMinimum !== undefined && instance <= $exclusiveMinimum) { errors.push({ instanceLocation, keyword: 'exclusiveMinimum', keywordLocation: `${schemaLocation}/exclusiveMinimum`, error: `${instance} is less than ${$exclusiveMinimum}.` }) } if ($exclusiveMaximum !== undefined && instance >= $exclusiveMaximum) { errors.push({ instanceLocation, keyword: 'exclusiveMaximum', keywordLocation: `${schemaLocation}/exclusiveMaximum`, error: `${instance} is greater than or equal to ${$exclusiveMaximum}.` }) } } if ($multipleOf !== undefined) { const remainder = instance % $multipleOf if ( Math.abs(0 - remainder) >= 1.192_092_9e-7 && Math.abs($multipleOf - remainder) >= 1.192_092_9e-7 ) { errors.push({ instanceLocation, keyword: 'multipleOf', keywordLocation: `${schemaLocation}/multipleOf`, error: `${instance} is not a multiple of ${$multipleOf}.` }) } } } else if (instanceType === 'string') { const length = $minLength === undefined && $maxLength === undefined ? 0 : ucs2length(instance) if ($minLength !== undefined && length < $minLength) { errors.push({ instanceLocation, keyword: 'minLength', keywordLocation: `${schemaLocation}/minLength`, error: `String is too short (${length} < ${$minLength}).` }) } if ($maxLength !== undefined && length > $maxLength) { errors.push({ instanceLocation, keyword: 'maxLength', keywordLocation: `${schemaLocation}/maxLength`, error: `String is too long (${length} > ${$maxLength}).` }) } if ($pattern !== undefined && !new RegExp($pattern, 'u').test(instance)) { errors.push({ instanceLocation, keyword: 'pattern', keywordLocation: `${schemaLocation}/pattern`, error: `String does not match pattern.` }) } if ( $format !== undefined && format[$format] && !format[$format](instance) ) { errors.push({ instanceLocation, keyword: 'format', keywordLocation: `${schemaLocation}/format`, error: `String does not match format "${$format}".` }) } } return { valid: errors.length === 0, instance, errors } }