UNPKG

json-schema-library

Version:

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

241 lines (240 loc) 8.15 kB
import { isSchemaNode } from "../types"; import settings from "../settings"; import { getValue } from "../utils/getValue"; import sanitizeErrors from "../utils/sanitizeErrors"; import { isObject } from "../utils/isObject"; import { validateNode } from "../validateNode"; import { joinDynamicId } from "../SchemaNode"; const { DECLARATOR_ONEOF } = settings; export const oneOfKeyword = { id: "oneOf", keyword: "oneOf", parse: parseOneOf, addReduce: (node) => node.oneOf != null, reduce: reduceOneOf, addValidate: (node) => node.oneOf != null, validate: oneOfValidator }; export const oneOfFuzzyKeyword = { id: "oneOf-fuzzy", keyword: "oneOf", parse: parseOneOf, addReduce: (node) => node.oneOf != null, reduce: reduceOneOfFuzzy, addValidate: (node) => node.oneOf != null, validate: oneOfValidator }; export function parseOneOf(node) { const { schema, evaluationPath, schemaLocation } = node; if (Array.isArray(schema.oneOf) && schema.oneOf.length) { node.oneOf = schema.oneOf.map((s, index) => node.compileSchema(s, `${evaluationPath}/oneOf/${index}`, `${schemaLocation}/oneOf/${index}`)); } } function reduceOneOf({ node, data, pointer, path }) { var _a, _b; if (node.oneOf == null) { return; } // !keyword: oneOfProperty // an additional <DECLARATOR_ONEOF> (default `oneOfProperty`) on the schema will exactly determine the // oneOf value (if set in data) if (data != null && node.schema[DECLARATOR_ONEOF]) { return reduceOneOfDeclarator({ node, data, pointer, path }); } const matches = []; const errors = []; for (let i = 0; i < node.oneOf.length; i += 1) { const validationErrors = validateNode(node.oneOf[i], data, pointer, path); if (validationErrors.length === 0) { matches.push({ index: i, node: node.oneOf[i] }); } else { errors.push(...validationErrors); } } if (matches.length === 1) { const { node, index } = matches[0]; const { node: reducedNode, error } = node.reduceNode(data, { pointer, path }); if (reducedNode) { const nestedDynamicId = (_b = (_a = reducedNode.dynamicId) === null || _a === void 0 ? void 0 : _a.replace(node.dynamicId, "")) !== null && _b !== void 0 ? _b : ""; const dynamicId = nestedDynamicId === "" ? `oneOf/${index}` : nestedDynamicId; reducedNode.oneOfIndex = index; // @evaluation-info reducedNode.dynamicId = joinDynamicId(reducedNode.dynamicId, `+${node.schemaLocation}(${dynamicId})`); return reducedNode; } return error; } if (matches.length === 0) { return node.createError("one-of-error", { value: JSON.stringify(data), pointer, schema: node.schema, oneOf: node.schema.oneOf, errors }); } return node.createError("one-of-error", { value: JSON.stringify(data), pointer, schema: node.schema, oneOf: node.schema.oneOf, errors }); } export function reduceOneOfDeclarator({ node, data, pointer, path }) { const errors = []; const oneOfProperty = node.schema[DECLARATOR_ONEOF]; const oneOfValue = getValue(data, oneOfProperty); if (oneOfValue === undefined) { return node.createError("missing-one-of-property-error", { property: oneOfProperty, pointer, schema: node.schema, value: data }); } for (let i = 0; i < node.oneOf.length; i += 1) { const { node: resultNode } = node.oneOf[i].getNodeChild(oneOfProperty, data); if (!isSchemaNode(resultNode)) { return node.createError("missing-one-of-declarator-error", { declarator: DECLARATOR_ONEOF, oneOfProperty, schemaPointer: node.oneOf[i].schemaLocation, pointer: `${pointer}/oneOf/${i}`, schema: node.schema, value: data }); } const result = sanitizeErrors(validateNode(resultNode, oneOfValue, pointer, path)); // result = result.filter(errorOrPromise); if (result.length > 0) { errors.push(...result); } else { const { node: reducedNode } = node.oneOf[i].reduceNode(data, { pointer, path }); if (reducedNode) { reducedNode.oneOfIndex = i; // @evaluation-info return reducedNode; } } } return node.createError("one-of-property-error", { property: oneOfProperty, value: oneOfValue, pointer, schema: node.schema, errors }); } /** * Returns a ranking for the data and given schema * * @param draft * @param - json schema type: object * @param data * @param [pointer] * @return ranking value (higher is better) */ function fuzzyObjectValue(node, data, pointer, path) { var _a; if (data == null || node.properties == null) { return -1; } let value = 0; const keys = Object.keys((_a = node.properties) !== null && _a !== void 0 ? _a : {}); for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; if (data[key]) { if (validateNode(node.properties[key], data[key], pointer, path).length === 0) { value += 1; } } } return value; } /** * Selects and returns a oneOf schema for the given data * * @param draft * @param data * @param [schema] - current json schema containing property oneOf * @param [pointer] - json pointer to data * @return oneOf schema or an error */ export function reduceOneOfFuzzy({ node, data, pointer, path }) { // @todo: usingMergeNode may add reducers that are no longer available if (node.oneOf == null) { return node; } const oneOfResult = reduceOneOf({ node, data, pointer, path }); if (isSchemaNode(oneOfResult)) { return oneOfResult; } // fuzzy match oneOf if (isObject(data)) { let nodeOfItem; let schemaOfIndex = -1; let fuzzyGreatest = 0; for (let i = 0; i < node.oneOf.length; i += 1) { const oneNode = node.oneOf[i]; const fuzzyValue = fuzzyObjectValue(oneNode, data, pointer, path); if (fuzzyGreatest < fuzzyValue) { fuzzyGreatest = fuzzyValue; nodeOfItem = oneNode; schemaOfIndex = i; } } if (nodeOfItem === undefined) { return node.createError("one-of-error", { value: JSON.stringify(data), pointer, schema: node.schema, oneOf: node.schema.oneOf }); } const { node: reducedNode, error } = nodeOfItem.reduceNode(data, { pointer, path }); if (reducedNode) { reducedNode.oneOfIndex = schemaOfIndex; // @evaluation-info return reducedNode; } return error; } return oneOfResult; } function oneOfValidator({ node, data, pointer = "#", path }) { const { oneOf, schema } = node; if (!oneOf) { return; } const matches = []; const errors = []; for (let i = 0; i < oneOf.length; i += 1) { const validationResult = validateNode(oneOf[i], data, pointer, path); if (validationResult.length > 0) { errors.push(...validationResult); } else { matches.push({ index: i, node: oneOf[i] }); } } if (matches.length === 1) { const { node, index } = matches[0]; node.oneOfIndex = index; // @evaluation-info return undefined; } if (matches.length > 1) { return node.createError("multiple-one-of-error", { value: data, pointer, schema, matches }); } return node.createError("one-of-error", { value: JSON.stringify(data), pointer, schema, oneOf: schema.oneOf, errors }); }