@cfworker/json-schema
Advanced tools
Comparing version 1.1.4 to 1.2.0
@@ -89,2 +89,8 @@ import { encodePointer } from './pointer'; | ||
} | ||
if (schema.__absolute_uri__ === undefined) { | ||
Object.defineProperty(schema, '__absolute_uri__', { | ||
enumerable: false, | ||
value: schemaURI | ||
}); | ||
} | ||
if (schema.$ref && schema.__absolute_ref__ === undefined) { | ||
@@ -91,0 +97,0 @@ const url = new URL(schema.$ref, baseURI); |
@@ -44,2 +44,4 @@ export declare type SchemaDraft = '4' | '7' | '2019-09'; | ||
contains?: Schema | boolean; | ||
minContains?: number; | ||
maxContains?: number; | ||
minItems?: number; | ||
@@ -57,3 +59,14 @@ maxItems?: number; | ||
__absolute_ref__?: string; | ||
__absolute_uri__?: string; | ||
[key: string]: any; | ||
} | ||
export interface OutputUnit { | ||
keyword: string; | ||
keywordLocation: string; | ||
instanceLocation: string; | ||
error: string; | ||
} | ||
export interface ValidationResult { | ||
valid: boolean; | ||
errors: OutputUnit[]; | ||
} |
@@ -1,7 +0,5 @@ | ||
import { Schema, SchemaDraft } from './types'; | ||
export declare function validate(instance: any, schema: Schema | boolean, draft?: SchemaDraft, lookup?: Record<string, boolean | Schema>, recursiveAnchor?: Schema | null, instancePointer?: string, evaluated?: { | ||
import { Schema, SchemaDraft, ValidationResult } from './types'; | ||
export declare function validate(instance: any, schema: Schema | boolean, draft?: SchemaDraft, lookup?: Record<string, boolean | Schema>, recursiveAnchor?: Schema | null, instanceLocation?: string, schemaLocation?: string, evaluated?: { | ||
properties?: Record<string, boolean>; | ||
items?: number; | ||
}): { | ||
valid: boolean; | ||
}; | ||
}): ValidationResult; |
@@ -6,10 +6,18 @@ import { deepCompareStrict } from './deep-compare-strict'; | ||
import { ucs2length } from './ucs2-length'; | ||
const validResult = Object.freeze({ valid: true }); | ||
const invalidResult = Object.freeze({ valid: false }); | ||
export function validate(instance, schema, draft = '2019-09', lookup = dereference(schema), recursiveAnchor = null, instancePointer = '/#', evaluated) { | ||
export function validate(instance, schema, draft = '2019-09', lookup = dereference(schema), recursiveAnchor = null, instanceLocation = '#', schemaLocation = '#', evaluated) { | ||
if (schema === true) { | ||
return validResult; | ||
return { valid: true, errors: [] }; | ||
} | ||
if (schema === false) { | ||
return invalidResult; | ||
return { | ||
valid: false, | ||
errors: [ | ||
{ | ||
instanceLocation, | ||
keyword: 'false', | ||
keywordLocation: instanceLocation, | ||
error: 'False boolean schema.' | ||
} | ||
] | ||
}; | ||
} | ||
@@ -40,3 +48,4 @@ const rawInstanceType = typeof 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, items: $items, additionalItems: $additionalItems, unevaluatedItems: $unevaluatedItems, contains: $contains, minItems: $minItems, maxItems: $maxItems, uniqueItems: $uniqueItems, minimum: $minimum, maximum: $maximum, exclusiveMinimum: $exclusiveMinimum, exclusiveMaximum: $exclusiveMaximum, multipleOf: $multipleOf, minLength: $minLength, maxLength: $maxLength, pattern: $pattern, __absolute_ref__ } = schema; | ||
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, 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, __absolute_ref__ } = schema; | ||
const errors = []; | ||
if ($ref !== undefined) { | ||
@@ -53,7 +62,14 @@ const uri = __absolute_ref__ || $ref; | ||
} | ||
if (!validate(instance, refSchema, draft, lookup, recursiveAnchor, instancePointer).valid) { | ||
return invalidResult; | ||
const keywordLocation = `${schemaLocation}/$ref`; | ||
const result = validate(instance, refSchema, draft, lookup, recursiveAnchor, instanceLocation, keywordLocation, evaluated); | ||
if (!result.valid) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: '$ref', | ||
keywordLocation, | ||
error: 'A subschema had errors.' | ||
}, ...result.errors); | ||
} | ||
if (draft === '4' || draft === '7') { | ||
return validResult; | ||
return { valid: errors.length === 0, errors }; | ||
} | ||
@@ -64,5 +80,13 @@ } | ||
} | ||
if ($recursiveRef === '#' && | ||
!validate(instance, recursiveAnchor === null ? schema : recursiveAnchor, draft, lookup, recursiveAnchor, instancePointer).valid) { | ||
return invalidResult; | ||
if ($recursiveRef === '#') { | ||
const keywordLocation = `${schemaLocation}/$recursiveRef`; | ||
const result = validate(instance, recursiveAnchor === null ? schema : recursiveAnchor, draft, lookup, recursiveAnchor, instanceLocation, keywordLocation, evaluated); | ||
if (!result.valid) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: '$recursiveRef', | ||
keywordLocation, | ||
error: 'A subschema had errors.' | ||
}, ...result.errors); | ||
} | ||
} | ||
@@ -83,3 +107,8 @@ if (Array.isArray($type)) { | ||
if (!valid) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'type', | ||
keywordLocation: `${schemaLocation}/type`, | ||
error: `Instance type "${instanceType}" is invalid. Expected "${$type.join('", "')}".` | ||
}); | ||
} | ||
@@ -89,7 +118,17 @@ } | ||
if (instanceType !== 'number' || instance % 1 || instance !== instance) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'type', | ||
keywordLocation: `${schemaLocation}/type`, | ||
error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` | ||
}); | ||
} | ||
} | ||
else if ($type !== undefined && instanceType !== $type) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'type', | ||
keywordLocation: `${schemaLocation}/type`, | ||
error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` | ||
}); | ||
} | ||
@@ -99,7 +138,17 @@ if ($const !== undefined) { | ||
if (!deepCompareStrict(instance, $const)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'const', | ||
keywordLocation: `${schemaLocation}/const`, | ||
error: `Instance does not match ${JSON.stringify($const)}.` | ||
}); | ||
} | ||
} | ||
else if (instance !== $const) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'const', | ||
keywordLocation: `${schemaLocation}/const`, | ||
error: `Instance does not match ${JSON.stringify($const)}.` | ||
}); | ||
} | ||
@@ -110,35 +159,121 @@ } | ||
if (!$enum.some(value => deepCompareStrict(instance, value))) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'enum', | ||
keywordLocation: `${schemaLocation}/enum`, | ||
error: `Instance does not match any of ${JSON.stringify($enum)}.` | ||
}); | ||
} | ||
} | ||
else if (!$enum.some(value => instance === value)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'enum', | ||
keywordLocation: `${schemaLocation}/enum`, | ||
error: `Instance does not match any of ${JSON.stringify($enum)}.` | ||
}); | ||
} | ||
} | ||
if ($not !== undefined && | ||
validate(instance, $not, draft, lookup, recursiveAnchor, instancePointer, evaluated).valid) { | ||
return invalidResult; | ||
if ($not !== undefined) { | ||
const keywordLocation = `${schemaLocation}/not`; | ||
const result = validate(instance, $not, draft, lookup, recursiveAnchor, instanceLocation, keywordLocation); | ||
if (result.valid) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'not', | ||
keywordLocation, | ||
error: 'Instance matched "not" schema.' | ||
}); | ||
} | ||
} | ||
if ($anyOf !== undefined && | ||
!$anyOf.some(subSchema => validate(instance, subSchema, draft, lookup, recursiveAnchor, instancePointer, evaluated).valid)) { | ||
return invalidResult; | ||
if ($anyOf !== undefined) { | ||
const keywordLocation = `${schemaLocation}/anyOf`; | ||
const errorsLength = errors.length; | ||
let anyValid = false; | ||
for (let i = 0; i < $anyOf.length; i++) { | ||
const subSchema = $anyOf[i]; | ||
const result = validate(instance, subSchema, draft, lookup, recursiveAnchor, instanceLocation, `${keywordLocation}/${i}`, evaluated); | ||
errors.push(...result.errors); | ||
anyValid = anyValid || result.valid; | ||
} | ||
if (anyValid) { | ||
errors.length = errorsLength; | ||
} | ||
else { | ||
errors.splice(errorsLength, 0, { | ||
instanceLocation, | ||
keyword: 'anyOf', | ||
keywordLocation, | ||
error: 'Instance does not match any subschemas.' | ||
}); | ||
} | ||
} | ||
if ($allOf !== undefined && | ||
!$allOf.every(subSchema => validate(instance, subSchema, draft, lookup, recursiveAnchor, instancePointer, evaluated).valid)) { | ||
return invalidResult; | ||
if ($allOf !== undefined) { | ||
const keywordLocation = `${schemaLocation}/allOf`; | ||
const errorsLength = errors.length; | ||
let allValid = true; | ||
for (let i = 0; i < $allOf.length; i++) { | ||
const subSchema = $allOf[i]; | ||
const result = validate(instance, subSchema, draft, lookup, recursiveAnchor, instanceLocation, `${keywordLocation}/${i}`, evaluated); | ||
errors.push(...result.errors); | ||
allValid = allValid && result.valid; | ||
} | ||
if (allValid) { | ||
errors.length = errorsLength; | ||
} | ||
else { | ||
errors.splice(errorsLength, 0, { | ||
instanceLocation, | ||
keyword: 'allOf', | ||
keywordLocation, | ||
error: `Instance does not match every subschema.` | ||
}); | ||
} | ||
} | ||
if ($oneOf !== undefined && | ||
$oneOf.filter(subSchema => validate(instance, subSchema, draft, lookup, recursiveAnchor, instancePointer, evaluated).valid).length !== 1) { | ||
return invalidResult; | ||
if ($oneOf !== undefined) { | ||
const keywordLocation = `${schemaLocation}/oneOf`; | ||
const errorsLength = errors.length; | ||
const matches = $oneOf.filter((subSchema, i) => { | ||
const result = validate(instance, subSchema, draft, lookup, recursiveAnchor, instanceLocation, `${keywordLocation}/${i}`, evaluated); | ||
errors.push(...result.errors); | ||
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 ($if !== undefined) { | ||
if (validate(instance, $if, draft, lookup, recursiveAnchor, instancePointer, evaluated).valid) { | ||
if ($then !== undefined && | ||
!validate(instance, $then, draft, lookup, recursiveAnchor, instancePointer, evaluated).valid) { | ||
return invalidResult; | ||
const keywordLocation = `${schemaLocation}/if`; | ||
const conditionResult = validate(instance, $if, draft, lookup, recursiveAnchor, instanceLocation, keywordLocation, evaluated).valid; | ||
if (conditionResult) { | ||
if ($then !== undefined) { | ||
const thenResult = validate(instance, $then, draft, lookup, recursiveAnchor, instanceLocation, `${schemaLocation}/then`, evaluated); | ||
if (!thenResult.valid) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'if', | ||
keywordLocation, | ||
error: `Instance does not match "then" schema.` | ||
}, ...thenResult.errors); | ||
} | ||
} | ||
} | ||
else if ($else !== undefined && | ||
!validate(instance, $else, draft, lookup, recursiveAnchor, instancePointer, evaluated).valid) { | ||
return invalidResult; | ||
else if ($else !== undefined) { | ||
const elseResult = validate(instance, $else, draft, lookup, recursiveAnchor, instanceLocation, `${schemaLocation}/else`, evaluated); | ||
if (!elseResult.valid) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'if', | ||
keywordLocation, | ||
error: `Instance does not match "else" schema.` | ||
}, ...elseResult.errors); | ||
} | ||
} | ||
@@ -150,3 +285,8 @@ } | ||
if (!(key in instance)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'required', | ||
keywordLocation: `${schemaLocation}/required`, | ||
error: `Instance does not have required property "${key}".` | ||
}); | ||
} | ||
@@ -157,11 +297,29 @@ } | ||
if ($minProperties !== undefined && keys.length < $minProperties) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'minProperties', | ||
keywordLocation: `${schemaLocation}/minProperties`, | ||
error: `Instance does not have at least ${$minProperties} properties.` | ||
}); | ||
} | ||
if ($maxProperties !== undefined && keys.length > $maxProperties) { | ||
return invalidResult; | ||
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) { | ||
if (!validate(key, $propertyNames, draft, lookup, recursiveAnchor, instancePointer).valid) { | ||
return invalidResult; | ||
const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; | ||
const result = validate(key, $propertyNames, draft, lookup, recursiveAnchor, subInstancePointer, keywordLocation); | ||
if (!result.valid) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'propertyNames', | ||
keywordLocation, | ||
error: `Property name "${key}" does not match schema.` | ||
}, ...result.errors); | ||
} | ||
@@ -171,8 +329,14 @@ } | ||
if ($dependentRequired !== undefined) { | ||
const keywordLocation = `${schemaLocation}/dependantRequired`; | ||
for (const key in $dependentRequired) { | ||
if (key in instance) { | ||
const required = $dependentRequired[key]; | ||
for (const key of required) { | ||
if (!(key in instance)) { | ||
return invalidResult; | ||
for (const dependantKey of required) { | ||
if (!(dependantKey in instance)) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'dependentRequired', | ||
keywordLocation, | ||
error: `Instance has "${key}" but does not have "${dependantKey}".` | ||
}); | ||
} | ||
@@ -185,5 +349,12 @@ } | ||
for (const key in $dependentSchemas) { | ||
const keywordLocation = `${schemaLocation}/dependentSchemas`; | ||
if (key in instance) { | ||
if (!validate(instance, $dependentSchemas[key], draft, lookup, recursiveAnchor, instancePointer).valid) { | ||
return invalidResult; | ||
const result = validate(instance, $dependentSchemas[key], draft, lookup, 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); | ||
} | ||
@@ -194,2 +365,3 @@ } | ||
if ($dependencies !== undefined) { | ||
const keywordLocation = `${schemaLocation}/dependencies`; | ||
for (const key in $dependencies) { | ||
@@ -199,5 +371,10 @@ if (key in instance) { | ||
if (Array.isArray(propsOrSchema)) { | ||
for (const key of propsOrSchema) { | ||
if (!(key in instance)) { | ||
return invalidResult; | ||
for (const dependantKey of propsOrSchema) { | ||
if (!(dependantKey in instance)) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'dependencies', | ||
keywordLocation, | ||
error: `Instance has "${key}" but does not have "${dependantKey}".` | ||
}); | ||
} | ||
@@ -207,4 +384,10 @@ } | ||
else { | ||
if (!validate(instance, propsOrSchema, draft, lookup, recursiveAnchor, instancePointer).valid) { | ||
return invalidResult; | ||
const result = validate(instance, propsOrSchema, draft, lookup, 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); | ||
} | ||
@@ -219,3 +402,5 @@ } | ||
} | ||
let stop = false; | ||
if ($properties !== undefined) { | ||
const keywordLocation = `${schemaLocation}/properties`; | ||
for (const key in $properties) { | ||
@@ -225,12 +410,21 @@ if (!(key in instance)) { | ||
} | ||
const subInstancePointer = `${instancePointer}/${encodePointer(key)}`; | ||
if (validate(instance[key], $properties[key], draft, lookup, recursiveAnchor, subInstancePointer).valid) { | ||
const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; | ||
const result = validate(instance[key], $properties[key], draft, lookup, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(key)}`); | ||
if (result.valid) { | ||
evaluated.properties[key] = thisEvaluated[key] = true; | ||
} | ||
else { | ||
return invalidResult; | ||
stop = true; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'properties', | ||
keywordLocation, | ||
error: `Property "${key}" does not match schema.` | ||
}, ...result.errors); | ||
break; | ||
} | ||
} | ||
} | ||
if ($patternProperties !== undefined) { | ||
if (!stop && $patternProperties !== undefined) { | ||
const keywordLocation = `${schemaLocation}/patternProperties`; | ||
for (const pattern in $patternProperties) { | ||
@@ -243,8 +437,15 @@ const regex = new RegExp(pattern); | ||
} | ||
const subInstancePointer = `${instancePointer}/${encodePointer(key)}`; | ||
if (validate(instance[key], subSchema, draft, lookup, recursiveAnchor, subInstancePointer).valid) { | ||
const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; | ||
const result = validate(instance[key], subSchema, draft, lookup, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(pattern)}`); | ||
if (result.valid) { | ||
evaluated.properties[key] = thisEvaluated[key] = true; | ||
} | ||
else { | ||
return invalidResult; | ||
stop = true; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'patternProperties', | ||
keywordLocation, | ||
error: `Property "${key}" matches pattern "${pattern}" but does not match associated schema.` | ||
}, ...result.errors); | ||
} | ||
@@ -254,3 +455,4 @@ } | ||
} | ||
if ($additionalProperties !== undefined) { | ||
if (!stop && $additionalProperties !== undefined) { | ||
const keywordLocation = `${schemaLocation}/additionalProperties`; | ||
for (const key in instance) { | ||
@@ -260,18 +462,35 @@ if (thisEvaluated[key]) { | ||
} | ||
const subInstancePointer = `${instancePointer}/${encodePointer(key)}`; | ||
if (validate(instance[key], $additionalProperties, draft, lookup, recursiveAnchor, subInstancePointer).valid) { | ||
const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; | ||
const result = validate(instance[key], $additionalProperties, draft, lookup, recursiveAnchor, subInstancePointer, keywordLocation); | ||
if (result.valid) { | ||
evaluated.properties[key] = true; | ||
} | ||
else { | ||
return invalidResult; | ||
stop = true; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'additionalProperties', | ||
keywordLocation, | ||
error: `Property "${key}" does not match additional properties schema.` | ||
}, ...result.errors); | ||
} | ||
} | ||
} | ||
else if ($unevaluatedProperties !== undefined) { | ||
else if (!stop && $unevaluatedProperties !== undefined) { | ||
const keywordLocation = `${schemaLocation}/unevaluatedProperties`; | ||
for (const key in instance) { | ||
if (!evaluated.properties[key]) { | ||
const subInstancePointer = `${instancePointer}/${encodePointer(key)}`; | ||
if (!validate(instance[key], $unevaluatedProperties, draft, lookup, recursiveAnchor, subInstancePointer).valid) { | ||
return invalidResult; | ||
const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; | ||
const result = validate(instance[key], $unevaluatedProperties, draft, lookup, recursiveAnchor, subInstancePointer, keywordLocation); | ||
if (result.valid) { | ||
evaluated.properties[key] = true; | ||
} | ||
else { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'unevaluatedProperties', | ||
keywordLocation, | ||
error: `Property "${key}" does not match unevaluated properties schema.` | ||
}, ...result.errors); | ||
} | ||
} | ||
@@ -283,6 +502,16 @@ } | ||
if ($maxItems !== undefined && instance.length > $maxItems) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'maxItems', | ||
keywordLocation: `${schemaLocation}/maxItems`, | ||
error: `Array has too many items (${instance.length} > ${$maxItems}).` | ||
}); | ||
} | ||
if ($minItems !== undefined && instance.length < $minItems) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'minItems', | ||
keywordLocation: `${schemaLocation}/minItems`, | ||
error: `Array has too few items (${instance.length} < ${$minItems}).` | ||
}); | ||
} | ||
@@ -294,8 +523,18 @@ if (!evaluated || evaluated.items === undefined) { | ||
let i = 0; | ||
let stop = false; | ||
if ($items !== undefined) { | ||
const keywordLocation = `${schemaLocation}/items`; | ||
if (Array.isArray($items)) { | ||
const length2 = Math.min($items.length, length); | ||
for (; i < length2; i++) { | ||
if (!validate(instance[i], $items[i], draft, lookup, recursiveAnchor, `${instancePointer}/${i}`).valid) { | ||
return invalidResult; | ||
const result = validate(instance[i], $items[i], draft, lookup, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`); | ||
if (!result.valid) { | ||
stop = true; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'items', | ||
keywordLocation, | ||
error: `Items did not match schema.` | ||
}, ...result.errors); | ||
break; | ||
} | ||
@@ -306,4 +545,12 @@ } | ||
for (; i < length; i++) { | ||
if (!validate(instance[i], $items, draft, lookup, recursiveAnchor, `${instancePointer}/${i}`).valid) { | ||
return invalidResult; | ||
const result = validate(instance[i], $items, draft, lookup, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); | ||
if (!result.valid) { | ||
stop = true; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'items', | ||
keywordLocation, | ||
error: `Items did not match schema.` | ||
}, ...result.errors); | ||
break; | ||
} | ||
@@ -313,6 +560,14 @@ } | ||
evaluated.items = Math.max(i, evaluated.items); | ||
if ($additionalItems !== undefined) { | ||
if (!stop && $additionalItems !== undefined) { | ||
const keywordLocation = `${schemaLocation}/additionalItems`; | ||
for (; i < length; i++) { | ||
if (!validate(instance[i], $additionalItems, draft, lookup, recursiveAnchor, `${instancePointer}/${i}`).valid) { | ||
return invalidResult; | ||
const result = validate(instance[i], $additionalItems, draft, lookup, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); | ||
if (!result.valid) { | ||
stop = true; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'additionalItems', | ||
keywordLocation, | ||
error: `Items did not match additional items schema.` | ||
}, ...result.errors); | ||
} | ||
@@ -323,23 +578,80 @@ } | ||
} | ||
if ($unevaluatedItems !== undefined) { | ||
if (!stop && $unevaluatedItems !== undefined) { | ||
const keywordLocation = `${schemaLocation}/unevaluatedItems`; | ||
for (i = Math.max(evaluated.items, 0); i < length; i++) { | ||
if (!validate(instance[i], $unevaluatedItems, draft, lookup, recursiveAnchor, `${instancePointer}/${i}`).valid) { | ||
return invalidResult; | ||
const result = validate(instance[i], $unevaluatedItems, draft, lookup, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); | ||
if (!result.valid) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'unevaluatedItems', | ||
keywordLocation, | ||
error: `Items did not match unevaluated items schema.` | ||
}, ...result.errors); | ||
} | ||
} | ||
evaluated.items = Math.max(i, evaluated.items); | ||
} | ||
if ($contains !== undefined) { | ||
if (length === 0) { | ||
return invalidResult; | ||
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.` | ||
}); | ||
} | ||
let contained = false; | ||
for (let i = 0; i < length; i++) { | ||
if (validate(instance[i], $contains, draft, lookup, recursiveAnchor, `${instancePointer}/${i}`).valid) { | ||
contained = true; | ||
break; | ||
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 i = 0; i < length; i++) { | ||
const result = validate(instance[i], $contains, draft, lookup, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation); | ||
if (result.valid) { | ||
contained++; | ||
if ($minContains === undefined && $maxContains === undefined) { | ||
break; | ||
} | ||
} | ||
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 (!contained) { | ||
return invalidResult; | ||
} | ||
} | ||
@@ -357,3 +669,10 @@ if ($uniqueItems) { | ||
if (a === b || (ao && bo && deepCompareStrict(a, b))) { | ||
return invalidResult; | ||
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; | ||
} | ||
@@ -369,3 +688,8 @@ } | ||
instance < $minimum)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'minimum', | ||
keywordLocation: `${schemaLocation}/minimum`, | ||
error: `${instance} is less than ${$exclusiveMinimum ? 'or equal to ' : ''} ${$minimum}.` | ||
}); | ||
} | ||
@@ -375,3 +699,8 @@ if ($maximum !== undefined && | ||
instance > $maximum)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'maximum', | ||
keywordLocation: `${schemaLocation}/maximum`, | ||
error: `${instance} is greater than ${$exclusiveMaximum ? 'or equal to ' : ''} ${$maximum}.` | ||
}); | ||
} | ||
@@ -381,12 +710,32 @@ } | ||
if ($minimum !== undefined && instance < $minimum) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'minimum', | ||
keywordLocation: `${schemaLocation}/minimum`, | ||
error: `${instance} is less than ${$minimum}.` | ||
}); | ||
} | ||
if ($maximum !== undefined && instance > $maximum) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'maximum', | ||
keywordLocation: `${schemaLocation}/maximum`, | ||
error: `${instance} is greater than ${$maximum}.` | ||
}); | ||
} | ||
if ($exclusiveMinimum !== undefined && instance <= $exclusiveMinimum) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'exclusiveMinimum', | ||
keywordLocation: `${schemaLocation}/exclusiveMinimum`, | ||
error: `${instance} is less than ${$exclusiveMinimum}.` | ||
}); | ||
} | ||
if ($exclusiveMaximum !== undefined && instance >= $exclusiveMaximum) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'exclusiveMaximum', | ||
keywordLocation: `${schemaLocation}/exclusiveMaximum`, | ||
error: `${instance} is greater than or equal to ${$exclusiveMaximum}.` | ||
}); | ||
} | ||
@@ -397,3 +746,8 @@ } | ||
if (division !== Math.floor(division)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'multipleOf', | ||
keywordLocation: `${schemaLocation}/multipleOf`, | ||
error: `${instance} is not a multiple of ${$multipleOf}.` | ||
}); | ||
} | ||
@@ -403,10 +757,28 @@ } | ||
else if (instanceType === 'string') { | ||
if ($minLength !== undefined && ucs2length(instance) < $minLength) { | ||
return invalidResult; | ||
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 && ucs2length(instance) > $maxLength) { | ||
return invalidResult; | ||
if ($maxLength !== undefined && length > $maxLength) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'maxLength', | ||
keywordLocation: `${schemaLocation}/maxLength`, | ||
error: `String is too long (${length} > ${$minLength}).` | ||
}); | ||
} | ||
if ($pattern !== undefined && !new RegExp($pattern).test(instance)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'pattern', | ||
keywordLocation: `${schemaLocation}/pattern`, | ||
error: `String does not match pattern.` | ||
}); | ||
} | ||
@@ -416,6 +788,11 @@ if ($format !== undefined && | ||
!fastFormat[$format](instance)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'format', | ||
keywordLocation: `${schemaLocation}/format`, | ||
error: `String does not match format "${$format}".` | ||
}); | ||
} | ||
} | ||
return validResult; | ||
return { valid: errors.length === 0, errors }; | ||
} |
@@ -7,6 +7,4 @@ import { Schema, SchemaDraft } from './types'; | ||
constructor(schema: Schema | boolean, draft?: SchemaDraft); | ||
validate(instance: any): { | ||
valid: boolean; | ||
}; | ||
validate(instance: any): import("./types").ValidationResult; | ||
addSchema(schema: Schema, id?: string): void; | ||
} |
{ | ||
"name": "@cfworker/json-schema", | ||
"type": "module", | ||
"version": "1.1.4", | ||
"version": "1.2.0", | ||
"description": "A JSON schema validator that will run on Cloudflare workers. Supports drafts 4, 7, and 2019-09.", | ||
@@ -41,11 +41,11 @@ "keywords": [ | ||
"devDependencies": { | ||
"@cfworker/dev": "^1.1.4", | ||
"@cfworker/dev": "^1.2.0", | ||
"@types/chai": "^4.2.11", | ||
"@types/mocha": "^7.0.2", | ||
"chai": "^4.2.0", | ||
"json-schema-test-suite": "https://github.com/json-schema-org/JSON-Schema-Test-Suite", | ||
"mocha": "^7.1.1", | ||
"typescript": "^3.8.3" | ||
"json-schema-test-suite": "git+https://github.com/json-schema-org/JSON-Schema-Test-Suite#ac63eb7", | ||
"mocha": "^7.2.0", | ||
"typescript": "^3.9.3" | ||
}, | ||
"gitHead": "fd3e8bad07c4fc8987ab612568cea573af27183b" | ||
"gitHead": "8728596ef4f1b597931a613b4e750f8686e66b62" | ||
} |
@@ -109,2 +109,10 @@ import { encodePointer } from './pointer'; | ||
// set the schema's absolute URI. | ||
if (schema.__absolute_uri__ === undefined) { | ||
Object.defineProperty(schema, '__absolute_uri__', { | ||
enumerable: false, | ||
value: schemaURI | ||
}); | ||
} | ||
// if a $ref is found, resolve it's absolute URI. | ||
@@ -111,0 +119,0 @@ if (schema.$ref && schema.__absolute_ref__ === undefined) { |
@@ -58,2 +58,4 @@ export type SchemaDraft = '4' | '7' | '2019-09'; | ||
contains?: Schema | boolean; | ||
minContains?: number; | ||
maxContains?: number; | ||
minItems?: number; | ||
@@ -74,4 +76,17 @@ maxItems?: number; | ||
__absolute_ref__?: string; | ||
__absolute_uri__?: string; | ||
[key: string]: any; | ||
} | ||
export interface OutputUnit { | ||
keyword: string; | ||
keywordLocation: string; | ||
instanceLocation: string; | ||
error: string; | ||
} | ||
export interface ValidationResult { | ||
valid: boolean; | ||
errors: OutputUnit[]; | ||
} |
@@ -5,8 +5,11 @@ import { deepCompareStrict } from './deep-compare-strict'; | ||
import { encodePointer } from './pointer'; | ||
import { InstanceType, Schema, SchemaDraft } from './types'; | ||
import { | ||
InstanceType, | ||
OutputUnit, | ||
Schema, | ||
SchemaDraft, | ||
ValidationResult | ||
} from './types'; | ||
import { ucs2length } from './ucs2-length'; | ||
const validResult = Object.freeze({ valid: true }); | ||
const invalidResult = Object.freeze({ valid: false }); | ||
export function validate( | ||
@@ -18,11 +21,22 @@ instance: any, | ||
recursiveAnchor: Schema | null = null, | ||
instancePointer = '/#', | ||
instanceLocation = '#', | ||
schemaLocation = '#', | ||
evaluated?: { properties?: Record<string, boolean>; items?: number } | ||
): { valid: boolean } { | ||
): ValidationResult { | ||
if (schema === true) { | ||
return validResult; | ||
return { valid: true, errors: [] }; | ||
} | ||
if (schema === false) { | ||
return invalidResult; | ||
return { | ||
valid: false, | ||
errors: [ | ||
{ | ||
instanceLocation, | ||
keyword: 'false', | ||
keywordLocation: instanceLocation, | ||
error: 'False boolean schema.' | ||
} | ||
] | ||
}; | ||
} | ||
@@ -89,2 +103,4 @@ | ||
contains: $contains, | ||
minContains: $minContains, | ||
maxContains: $maxContains, | ||
minItems: $minItems, | ||
@@ -107,2 +123,4 @@ maxItems: $maxItems, | ||
const errors: OutputUnit[] = []; | ||
if ($ref !== undefined) { | ||
@@ -119,16 +137,26 @@ const uri = __absolute_ref__ || $ref; | ||
} | ||
if ( | ||
!validate( | ||
instance, | ||
refSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instancePointer | ||
).valid | ||
) { | ||
return invalidResult; | ||
const keywordLocation = `${schemaLocation}/$ref`; | ||
const result = validate( | ||
instance, | ||
refSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instanceLocation, | ||
keywordLocation, | ||
evaluated | ||
); | ||
if (!result.valid) { | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: '$ref', | ||
keywordLocation, | ||
error: 'A subschema had errors.' | ||
}, | ||
...result.errors | ||
); | ||
} | ||
if (draft === '4' || draft === '7') { | ||
return validResult; | ||
return { valid: errors.length === 0, errors }; | ||
} | ||
@@ -141,5 +169,5 @@ } | ||
if ( | ||
$recursiveRef === '#' && | ||
!validate( | ||
if ($recursiveRef === '#') { | ||
const keywordLocation = `${schemaLocation}/$recursiveRef`; | ||
const result = validate( | ||
instance, | ||
@@ -150,6 +178,17 @@ recursiveAnchor === null ? schema : recursiveAnchor, | ||
recursiveAnchor, | ||
instancePointer | ||
).valid | ||
) { | ||
return invalidResult; | ||
instanceLocation, | ||
keywordLocation, | ||
evaluated | ||
); | ||
if (!result.valid) { | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: '$recursiveRef', | ||
keywordLocation, | ||
error: 'A subschema had errors.' | ||
}, | ||
...result.errors | ||
); | ||
} | ||
} | ||
@@ -173,10 +212,27 @@ | ||
if (!valid) { | ||
return invalidResult; | ||
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 || instance !== instance) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'type', | ||
keywordLocation: `${schemaLocation}/type`, | ||
error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` | ||
}); | ||
} | ||
} else if ($type !== undefined && instanceType !== $type) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'type', | ||
keywordLocation: `${schemaLocation}/type`, | ||
error: `Instance type "${instanceType}" is invalid. Expected "${$type}".` | ||
}); | ||
} | ||
@@ -187,6 +243,16 @@ | ||
if (!deepCompareStrict(instance, $const)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'const', | ||
keywordLocation: `${schemaLocation}/const`, | ||
error: `Instance does not match ${JSON.stringify($const)}.` | ||
}); | ||
} | ||
} else if (instance !== $const) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'const', | ||
keywordLocation: `${schemaLocation}/const`, | ||
error: `Instance does not match ${JSON.stringify($const)}.` | ||
}); | ||
} | ||
@@ -198,12 +264,22 @@ } | ||
if (!$enum.some(value => deepCompareStrict(instance, value))) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'enum', | ||
keywordLocation: `${schemaLocation}/enum`, | ||
error: `Instance does not match any of ${JSON.stringify($enum)}.` | ||
}); | ||
} | ||
} else if (!$enum.some(value => instance === value)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'enum', | ||
keywordLocation: `${schemaLocation}/enum`, | ||
error: `Instance does not match any of ${JSON.stringify($enum)}.` | ||
}); | ||
} | ||
} | ||
if ( | ||
$not !== undefined && | ||
validate( | ||
if ($not !== undefined) { | ||
const keywordLocation = `${schemaLocation}/not`; | ||
const result = validate( | ||
instance, | ||
@@ -214,78 +290,122 @@ $not, | ||
recursiveAnchor, | ||
instancePointer, | ||
evaluated | ||
).valid | ||
) { | ||
return invalidResult; | ||
instanceLocation, | ||
keywordLocation /*, | ||
evaluated*/ | ||
); | ||
if (result.valid) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'not', | ||
keywordLocation, | ||
error: 'Instance matched "not" schema.' | ||
}); | ||
} | ||
} | ||
if ( | ||
$anyOf !== undefined && | ||
!$anyOf.some( | ||
subSchema => | ||
validate( | ||
instance, | ||
subSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instancePointer, | ||
evaluated | ||
).valid | ||
) | ||
) { | ||
return invalidResult; | ||
if ($anyOf !== undefined) { | ||
const keywordLocation = `${schemaLocation}/anyOf`; | ||
const errorsLength = errors.length; | ||
let anyValid = false; | ||
for (let i = 0; i < $anyOf.length; i++) { | ||
const subSchema = $anyOf[i]; | ||
const result = validate( | ||
instance, | ||
subSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instanceLocation, | ||
`${keywordLocation}/${i}`, | ||
evaluated | ||
); | ||
errors.push(...result.errors); | ||
anyValid = anyValid || result.valid; | ||
} | ||
if (anyValid) { | ||
errors.length = errorsLength; | ||
} else { | ||
errors.splice(errorsLength, 0, { | ||
instanceLocation, | ||
keyword: 'anyOf', | ||
keywordLocation, | ||
error: 'Instance does not match any subschemas.' | ||
}); | ||
} | ||
} | ||
if ( | ||
$allOf !== undefined && | ||
!$allOf.every( | ||
subSchema => | ||
validate( | ||
instance, | ||
subSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instancePointer, | ||
evaluated | ||
).valid | ||
) | ||
) { | ||
return invalidResult; | ||
if ($allOf !== undefined) { | ||
const keywordLocation = `${schemaLocation}/allOf`; | ||
const errorsLength = errors.length; | ||
let allValid = true; | ||
for (let i = 0; i < $allOf.length; i++) { | ||
const subSchema = $allOf[i]; | ||
const result = validate( | ||
instance, | ||
subSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instanceLocation, | ||
`${keywordLocation}/${i}`, | ||
evaluated | ||
); | ||
errors.push(...result.errors); | ||
allValid = allValid && result.valid; | ||
} | ||
if (allValid) { | ||
errors.length = errorsLength; | ||
} else { | ||
errors.splice(errorsLength, 0, { | ||
instanceLocation, | ||
keyword: 'allOf', | ||
keywordLocation, | ||
error: `Instance does not match every subschema.` | ||
}); | ||
} | ||
} | ||
if ( | ||
$oneOf !== undefined && | ||
$oneOf.filter( | ||
subSchema => | ||
validate( | ||
instance, | ||
subSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instancePointer, | ||
evaluated | ||
).valid | ||
).length !== 1 | ||
) { | ||
return invalidResult; | ||
} | ||
if ($if !== undefined) { | ||
if ( | ||
validate( | ||
if ($oneOf !== undefined) { | ||
const keywordLocation = `${schemaLocation}/oneOf`; | ||
const errorsLength = errors.length; | ||
const matches = $oneOf.filter((subSchema, i) => { | ||
const result = validate( | ||
instance, | ||
$if, | ||
subSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instancePointer, | ||
instanceLocation, | ||
`${keywordLocation}/${i}`, | ||
evaluated | ||
).valid | ||
) { | ||
if ( | ||
$then !== undefined && | ||
!validate( | ||
); | ||
errors.push(...result.errors); | ||
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 ($if !== undefined) { | ||
const keywordLocation = `${schemaLocation}/if`; | ||
const conditionResult = validate( | ||
instance, | ||
$if, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instanceLocation, | ||
keywordLocation, | ||
evaluated | ||
).valid; | ||
if (conditionResult) { | ||
if ($then !== undefined) { | ||
const thenResult = validate( | ||
instance, | ||
@@ -296,11 +416,20 @@ $then, | ||
recursiveAnchor, | ||
instancePointer, | ||
instanceLocation, | ||
`${schemaLocation}/then`, | ||
evaluated | ||
).valid | ||
) { | ||
return invalidResult; | ||
); | ||
if (!thenResult.valid) { | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'if', | ||
keywordLocation, | ||
error: `Instance does not match "then" schema.` | ||
}, | ||
...thenResult.errors | ||
); | ||
} | ||
} | ||
} else if ( | ||
$else !== undefined && | ||
!validate( | ||
} else if ($else !== undefined) { | ||
const elseResult = validate( | ||
instance, | ||
@@ -311,7 +440,17 @@ $else, | ||
recursiveAnchor, | ||
instancePointer, | ||
instanceLocation, | ||
`${schemaLocation}/else`, | ||
evaluated | ||
).valid | ||
) { | ||
return invalidResult; | ||
); | ||
if (!elseResult.valid) { | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'if', | ||
keywordLocation, | ||
error: `Instance does not match "else" schema.` | ||
}, | ||
...elseResult.errors | ||
); | ||
} | ||
} | ||
@@ -324,3 +463,8 @@ } | ||
if (!(key in instance)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'required', | ||
keywordLocation: `${schemaLocation}/required`, | ||
error: `Instance does not have required property "${key}".` | ||
}); | ||
} | ||
@@ -333,22 +477,42 @@ } | ||
if ($minProperties !== undefined && keys.length < $minProperties) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'minProperties', | ||
keywordLocation: `${schemaLocation}/minProperties`, | ||
error: `Instance does not have at least ${$minProperties} properties.` | ||
}); | ||
} | ||
if ($maxProperties !== undefined && keys.length > $maxProperties) { | ||
return invalidResult; | ||
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) { | ||
if ( | ||
!validate( | ||
key, | ||
$propertyNames, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instancePointer | ||
).valid | ||
) { | ||
return invalidResult; | ||
const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; | ||
const result = validate( | ||
key, | ||
$propertyNames, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer, | ||
keywordLocation | ||
); | ||
if (!result.valid) { | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'propertyNames', | ||
keywordLocation, | ||
error: `Property name "${key}" does not match schema.` | ||
}, | ||
...result.errors | ||
); | ||
} | ||
@@ -359,8 +523,14 @@ } | ||
if ($dependentRequired !== undefined) { | ||
const keywordLocation = `${schemaLocation}/dependantRequired`; | ||
for (const key in $dependentRequired) { | ||
if (key in instance) { | ||
const required = $dependentRequired[key]; | ||
for (const key of required) { | ||
if (!(key in instance)) { | ||
return invalidResult; | ||
for (const dependantKey of required) { | ||
if (!(dependantKey in instance)) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'dependentRequired', | ||
keywordLocation, | ||
error: `Instance has "${key}" but does not have "${dependantKey}".` | ||
}); | ||
} | ||
@@ -374,14 +544,24 @@ } | ||
for (const key in $dependentSchemas) { | ||
const keywordLocation = `${schemaLocation}/dependentSchemas`; | ||
if (key in instance) { | ||
if ( | ||
!validate( | ||
instance, | ||
$dependentSchemas[key], | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instancePointer | ||
).valid | ||
) { | ||
return invalidResult; | ||
const result = validate( | ||
instance, | ||
$dependentSchemas[key], | ||
draft, | ||
lookup, | ||
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 | ||
); | ||
} | ||
@@ -393,2 +573,3 @@ } | ||
if ($dependencies !== undefined) { | ||
const keywordLocation = `${schemaLocation}/dependencies`; | ||
for (const key in $dependencies) { | ||
@@ -398,19 +579,32 @@ if (key in instance) { | ||
if (Array.isArray(propsOrSchema)) { | ||
for (const key of propsOrSchema) { | ||
if (!(key in instance)) { | ||
return invalidResult; | ||
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 { | ||
if ( | ||
!validate( | ||
instance, | ||
propsOrSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
instancePointer | ||
).valid | ||
) { | ||
return invalidResult; | ||
const result = validate( | ||
instance, | ||
propsOrSchema, | ||
draft, | ||
lookup, | ||
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 | ||
); | ||
} | ||
@@ -427,3 +621,6 @@ } | ||
let stop = false; | ||
if ($properties !== undefined) { | ||
const keywordLocation = `${schemaLocation}/properties`; | ||
for (const key in $properties) { | ||
@@ -433,20 +630,32 @@ if (!(key in instance)) { | ||
} | ||
const subInstancePointer = `${instancePointer}/${encodePointer(key)}`; | ||
if ( | ||
validate( | ||
instance[key], | ||
$properties[key], | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer | ||
).valid | ||
) { | ||
const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; | ||
const result = validate( | ||
instance[key], | ||
$properties[key], | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer, | ||
`${keywordLocation}/${encodePointer(key)}` | ||
); | ||
if (result.valid) { | ||
evaluated.properties[key] = thisEvaluated[key] = true; | ||
} else { | ||
return invalidResult; | ||
stop = true; | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'properties', | ||
keywordLocation, | ||
error: `Property "${key}" does not match schema.` | ||
}, | ||
...result.errors | ||
); | ||
break; | ||
} | ||
} | ||
} | ||
if ($patternProperties !== undefined) { | ||
if (!stop && $patternProperties !== undefined) { | ||
const keywordLocation = `${schemaLocation}/patternProperties`; | ||
for (const pattern in $patternProperties) { | ||
@@ -459,16 +668,27 @@ const regex = new RegExp(pattern); | ||
} | ||
const subInstancePointer = `${instancePointer}/${encodePointer(key)}`; | ||
if ( | ||
validate( | ||
instance[key], | ||
subSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer | ||
).valid | ||
) { | ||
const subInstancePointer = `${instanceLocation}/${encodePointer( | ||
key | ||
)}`; | ||
const result = validate( | ||
instance[key], | ||
subSchema, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer, | ||
`${keywordLocation}/${encodePointer(pattern)}` | ||
); | ||
if (result.valid) { | ||
evaluated.properties[key] = thisEvaluated[key] = true; | ||
} else { | ||
return invalidResult; | ||
stop = true; | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'patternProperties', | ||
keywordLocation, | ||
error: `Property "${key}" matches pattern "${pattern}" but does not match associated schema.` | ||
}, | ||
...result.errors | ||
); | ||
} | ||
@@ -478,3 +698,5 @@ } | ||
} | ||
if ($additionalProperties !== undefined) { | ||
if (!stop && $additionalProperties !== undefined) { | ||
const keywordLocation = `${schemaLocation}/additionalProperties`; | ||
for (const key in instance) { | ||
@@ -484,33 +706,55 @@ if (thisEvaluated[key]) { | ||
} | ||
const subInstancePointer = `${instancePointer}/${encodePointer(key)}`; | ||
if ( | ||
validate( | ||
instance[key], | ||
$additionalProperties, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer | ||
).valid | ||
) { | ||
const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`; | ||
const result = validate( | ||
instance[key], | ||
$additionalProperties, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer, | ||
keywordLocation | ||
); | ||
if (result.valid) { | ||
evaluated.properties[key] = true; | ||
} else { | ||
return invalidResult; | ||
stop = true; | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'additionalProperties', | ||
keywordLocation, | ||
error: `Property "${key}" does not match additional properties schema.` | ||
}, | ||
...result.errors | ||
); | ||
} | ||
} | ||
} else if ($unevaluatedProperties !== undefined) { | ||
} else if (!stop && $unevaluatedProperties !== undefined) { | ||
const keywordLocation = `${schemaLocation}/unevaluatedProperties`; | ||
for (const key in instance) { | ||
if (!evaluated.properties[key]) { | ||
const subInstancePointer = `${instancePointer}/${encodePointer(key)}`; | ||
if ( | ||
!validate( | ||
instance[key], | ||
$unevaluatedProperties, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer | ||
).valid | ||
) { | ||
return invalidResult; | ||
const subInstancePointer = `${instanceLocation}/${encodePointer( | ||
key | ||
)}`; | ||
const result = validate( | ||
instance[key], | ||
$unevaluatedProperties, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
subInstancePointer, | ||
keywordLocation | ||
); | ||
if (result.valid) { | ||
evaluated.properties[key] = true; | ||
} else { | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'unevaluatedProperties', | ||
keywordLocation, | ||
error: `Property "${key}" does not match unevaluated properties schema.` | ||
}, | ||
...result.errors | ||
); | ||
} | ||
@@ -522,7 +766,17 @@ } | ||
if ($maxItems !== undefined && instance.length > $maxItems) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'maxItems', | ||
keywordLocation: `${schemaLocation}/maxItems`, | ||
error: `Array has too many items (${instance.length} > ${$maxItems}).` | ||
}); | ||
} | ||
if ($minItems !== undefined && instance.length < $minItems) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'minItems', | ||
keywordLocation: `${schemaLocation}/minItems`, | ||
error: `Array has too few items (${instance.length} < ${$minItems}).` | ||
}); | ||
} | ||
@@ -536,17 +790,29 @@ | ||
let i = 0; | ||
let stop = false; | ||
if ($items !== undefined) { | ||
const keywordLocation = `${schemaLocation}/items`; | ||
if (Array.isArray($items)) { | ||
const length2 = Math.min($items.length, length); | ||
for (; i < length2; i++) { | ||
if ( | ||
!validate( | ||
instance[i], | ||
$items[i], | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
`${instancePointer}/${i}` | ||
).valid | ||
) { | ||
return invalidResult; | ||
const result = validate( | ||
instance[i], | ||
$items[i], | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
`${instanceLocation}/${i}`, | ||
`${keywordLocation}/${i}` | ||
); | ||
if (!result.valid) { | ||
stop = true; | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'items', | ||
keywordLocation, | ||
error: `Items did not match schema.` | ||
}, | ||
...result.errors | ||
); | ||
break; | ||
} | ||
@@ -556,13 +822,23 @@ } | ||
for (; i < length; i++) { | ||
if ( | ||
!validate( | ||
instance[i], | ||
$items, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
`${instancePointer}/${i}` | ||
).valid | ||
) { | ||
return invalidResult; | ||
const result = validate( | ||
instance[i], | ||
$items, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
`${instanceLocation}/${i}`, | ||
keywordLocation | ||
); | ||
if (!result.valid) { | ||
stop = true; | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'items', | ||
keywordLocation, | ||
error: `Items did not match schema.` | ||
}, | ||
...result.errors | ||
); | ||
break; | ||
} | ||
@@ -574,15 +850,25 @@ } | ||
if ($additionalItems !== undefined) { | ||
if (!stop && $additionalItems !== undefined) { | ||
const keywordLocation = `${schemaLocation}/additionalItems`; | ||
for (; i < length; i++) { | ||
if ( | ||
!validate( | ||
instance[i], | ||
$additionalItems, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
`${instancePointer}/${i}` | ||
).valid | ||
) { | ||
return invalidResult; | ||
const result = validate( | ||
instance[i], | ||
$additionalItems, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
`${instanceLocation}/${i}`, | ||
keywordLocation | ||
); | ||
if (!result.valid) { | ||
stop = true; | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'additionalItems', | ||
keywordLocation, | ||
error: `Items did not match additional items schema.` | ||
}, | ||
...result.errors | ||
); | ||
} | ||
@@ -594,27 +880,50 @@ } | ||
if ($unevaluatedItems !== undefined) { | ||
if (!stop && $unevaluatedItems !== undefined) { | ||
const keywordLocation = `${schemaLocation}/unevaluatedItems`; | ||
for (i = Math.max(evaluated.items, 0); i < length; i++) { | ||
if ( | ||
!validate( | ||
instance[i], | ||
$unevaluatedItems, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
`${instancePointer}/${i}` | ||
).valid | ||
) { | ||
return invalidResult; | ||
const result = validate( | ||
instance[i], | ||
$unevaluatedItems, | ||
draft, | ||
lookup, | ||
recursiveAnchor, | ||
`${instanceLocation}/${i}`, | ||
keywordLocation | ||
); | ||
if (!result.valid) { | ||
errors.push( | ||
{ | ||
instanceLocation, | ||
keyword: 'unevaluatedItems', | ||
keywordLocation, | ||
error: `Items did not match unevaluated items schema.` | ||
}, | ||
...result.errors | ||
); | ||
} | ||
} | ||
evaluated.items = Math.max(i, evaluated.items); | ||
} | ||
if ($contains !== undefined) { | ||
if (length === 0) { | ||
return invalidResult; | ||
} | ||
let contained = false; | ||
for (let i = 0; i < length; i++) { | ||
if ( | ||
validate( | ||
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 i = 0; i < length; i++) { | ||
const result = validate( | ||
instance[i], | ||
@@ -625,12 +934,46 @@ $contains, | ||
recursiveAnchor, | ||
`${instancePointer}/${i}` | ||
).valid | ||
`${instanceLocation}/${i}`, | ||
keywordLocation | ||
); | ||
if (result.valid) { | ||
contained++; | ||
if ($minContains === undefined && $maxContains === undefined) { | ||
break; | ||
} | ||
} else { | ||
errors.push(...result.errors); | ||
} | ||
} | ||
if (contained >= ($minContains || 0)) { | ||
errors.length = errorsLength; | ||
} | ||
if ( | ||
$minContains === undefined && | ||
$maxContains === undefined && | ||
contained === 0 | ||
) { | ||
contained = true; | ||
break; | ||
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 (!contained) { | ||
return invalidResult; | ||
} | ||
} | ||
@@ -649,3 +992,10 @@ | ||
if (a === b || (ao && bo && deepCompareStrict(a, b))) { | ||
return invalidResult; | ||
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; | ||
} | ||
@@ -662,3 +1012,10 @@ } | ||
) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'minimum', | ||
keywordLocation: `${schemaLocation}/minimum`, | ||
error: `${instance} is less than ${ | ||
$exclusiveMinimum ? 'or equal to ' : '' | ||
} ${$minimum}.` | ||
}); | ||
} | ||
@@ -670,16 +1027,43 @@ if ( | ||
) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'maximum', | ||
keywordLocation: `${schemaLocation}/maximum`, | ||
error: `${instance} is greater than ${ | ||
$exclusiveMaximum ? 'or equal to ' : '' | ||
} ${$maximum}.` | ||
}); | ||
} | ||
} else { | ||
if ($minimum !== undefined && instance < $minimum) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'minimum', | ||
keywordLocation: `${schemaLocation}/minimum`, | ||
error: `${instance} is less than ${$minimum}.` | ||
}); | ||
} | ||
if ($maximum !== undefined && instance > $maximum) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'maximum', | ||
keywordLocation: `${schemaLocation}/maximum`, | ||
error: `${instance} is greater than ${$maximum}.` | ||
}); | ||
} | ||
if ($exclusiveMinimum !== undefined && instance <= $exclusiveMinimum) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'exclusiveMinimum', | ||
keywordLocation: `${schemaLocation}/exclusiveMinimum`, | ||
error: `${instance} is less than ${$exclusiveMinimum}.` | ||
}); | ||
} | ||
if ($exclusiveMaximum !== undefined && instance >= $exclusiveMaximum) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'exclusiveMaximum', | ||
keywordLocation: `${schemaLocation}/exclusiveMaximum`, | ||
error: `${instance} is greater than or equal to ${$exclusiveMaximum}.` | ||
}); | ||
} | ||
@@ -690,14 +1074,38 @@ } | ||
if (division !== Math.floor(division)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'multipleOf', | ||
keywordLocation: `${schemaLocation}/multipleOf`, | ||
error: `${instance} is not a multiple of ${$multipleOf}.` | ||
}); | ||
} | ||
} | ||
} else if (instanceType === 'string') { | ||
if ($minLength !== undefined && ucs2length(instance) < $minLength) { | ||
return invalidResult; | ||
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 && ucs2length(instance) > $maxLength) { | ||
return invalidResult; | ||
if ($maxLength !== undefined && length > $maxLength) { | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'maxLength', | ||
keywordLocation: `${schemaLocation}/maxLength`, | ||
error: `String is too long (${length} > ${$minLength}).` | ||
}); | ||
} | ||
if ($pattern !== undefined && !new RegExp($pattern).test(instance)) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'pattern', | ||
keywordLocation: `${schemaLocation}/pattern`, | ||
error: `String does not match pattern.` | ||
}); | ||
} | ||
@@ -709,7 +1117,12 @@ if ( | ||
) { | ||
return invalidResult; | ||
errors.push({ | ||
instanceLocation, | ||
keyword: 'format', | ||
keywordLocation: `${schemaLocation}/format`, | ||
error: `String does not match format "${$format}".` | ||
}); | ||
} | ||
} | ||
return validResult; | ||
return { valid: errors.length === 0, errors }; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
105657
2732