UNPKG

json-schema-library

Version:

Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation

122 lines (110 loc) 4.5 kB
import { isObject } from "../../utils/isObject"; import { SchemaNode } from "../../types"; import { Keyword, JsonSchemaValidatorParams, ValidationReturnType } from "../../Keyword"; import { validateNode } from "../../validateNode"; const KEYWORD = "unevaluatedItems"; /** * @draft >= 2019-09 * Similar to additionalItems, but can "see" into subschemas and across references * https://json-schema.org/draft/2019-09/json-schema-core#rfc.section.9.3.1.3 */ export const unevaluatedItemsKeyword: Keyword = { id: KEYWORD, keyword: KEYWORD, parse: parseUnevaluatedItems, addValidate: ({ schema }) => schema[KEYWORD] != null, validate: validateUnevaluatedItems }; export function parseUnevaluatedItems(node: SchemaNode) { const { unevaluatedItems } = node.schema; if (unevaluatedItems == null || typeof unevaluatedItems === "boolean") { return; } if (!isObject(unevaluatedItems)) { return node.createError("schema-error", { pointer: node.evaluationPath, schema: node.schema, value: undefined, message: `Keyword '${KEYWORD}' must be an object - received '${typeof unevaluatedItems}'` }); } node.unevaluatedItems = node.compileSchema( node.schema.unevaluatedItems, `${node.evaluationPath}/${KEYWORD}`, `${node.schemaLocation}/${KEYWORD}` ); } function validateUnevaluatedItems({ node, data, pointer, path }: JsonSchemaValidatorParams) { const { schema } = node; // if not in items, and not matches additionalItems if ( !Array.isArray(data) || data.length === 0 || schema.unevaluatedItems == null || schema.unevaluatedItems === true ) { return undefined; } // const reducedNode = node; let { node: reducedNode } = node.reduceNode(data, { pointer, path }); reducedNode = reducedNode ?? node; if (reducedNode.schema.unevaluatedItems === true || reducedNode.schema.additionalItems === true) { return undefined; } // console.log("EVAL", reducedNode.schema); const validIf = node.if != null && validateNode(node.if, data, pointer, path).length === 0; const errors: ValidationReturnType = []; // "unevaluatedItems with nested items" for (let i = 0; i < data.length; i += 1) { const value = data[i]; const { node: child } = node.getNodeChild(i, data, { path }); // console.log(`CHILD '${i}':`, data[i], "=>", child?.schema); if (child) { // when a single node is invalid if (validateNode(child, value, `${pointer}/${i}`, path).length > 0) { // nothing should validate, so we validate unevaluated items only const unevaluatedItems = node.unevaluatedItems; if (unevaluatedItems) { data.forEach((value) => { const result = validateNode(unevaluatedItems, value, `${pointer}/${i}`, path); if (result == null) { return; } if (Array.isArray(result)) { return errors.push(...result); } errors.push(result); }); } if (node.schema.unevaluatedItems === false) { return node.createError("unevaluated-items-error", { pointer: `${pointer}/${i}`, value: JSON.stringify(value), schema }); } } } // "unevaluatedItems false" if (child === undefined) { // valid if ensures node.if is set if (validIf && node.if!.prefixItems && node.if!.prefixItems.length > i) { // evaluated by if -- skip } else if (node.unevaluatedItems) { const result = validateNode(node.unevaluatedItems, value, `${pointer}/${i}`, path); if (result.length > 0) { errors.push(...result); } } else { errors.push( node.createError("unevaluated-items-error", { pointer: `${pointer}/${i}`, value: JSON.stringify(value), schema }) ); } } } return errors; }