UNPKG

json-schema-library

Version:

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

112 lines (104 loc) 4.57 kB
import settings from "../settings"; import { isObject } from "../utils/isObject"; import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationResult } from "../Keyword"; import { SchemaNode } from "../types"; import { getValue } from "../utils/getValue"; import { validateNode } from "../validateNode"; export const additionalPropertiesKeyword: Keyword = { id: "additionalProperties", keyword: "additionalProperties", order: -10, parse: parseAdditionalProperties, addResolve: ({ schema }) => schema.additionalProperties != null, resolve: additionalPropertyResolver, addValidate: ({ schema }) => schema.additionalProperties !== true && schema.additionalProperties != null && // this is an arrangement with patternProperties. patternProperties validate before additionalProperties: // https://spacetelescope.github.io/understanding-json-schema/reference/object.html#index-5 !(schema.additionalProperties === false && isObject(schema.patternProperties)), validate: validateAdditionalProperty }; // must come as last resolver export function parseAdditionalProperties(node: SchemaNode) { const { schema, evaluationPath, schemaLocation } = node; if (isObject(schema.additionalProperties)) { node.additionalProperties = node.compileSchema( schema.additionalProperties, `${evaluationPath}/additionalProperties`, `${schemaLocation}/additionalProperties` ); } } function additionalPropertyResolver({ node, data, key }: JsonSchemaResolverParams) { const value = getValue(data, key); if (node.additionalProperties) { const { node: reduced, error } = node.additionalProperties.reduceNode(value); return reduced ?? error; } if (node.schema.additionalProperties === false) { return node.createError("no-additional-properties-error", { pointer: `${key}`, schema: node.schema, value: getValue(data, key), property: `${key}` // @todo add pointer to resolver // properties: expectedProperties }); } } /** * @additionalProperties only checks properties and additionalProperties * * The additionalProperties keyword is used to control the handling of extra stuff, that is, * properties whose names are not listed in the properties keyword or match any of the regular * expressions in the patternProperties keyword. By default any additional properties are allowed. * https://json-schema.org/understanding-json-schema/reference/object */ function validateAdditionalProperty({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) { if (!isObject(data)) { return; } const { schema } = node; const errors: ValidationResult[] = []; let receivedProperties = Object.keys(data).filter((prop) => settings.propertyBlacklist.includes(prop) === false); if (Array.isArray(node.patternProperties)) { // filter received properties by matching patternProperties receivedProperties = receivedProperties.filter((prop) => { for (let i = 0; i < node.patternProperties.length; i += 1) { if (node.patternProperties[i].pattern.test(prop)) { return false; // remove } } return true; }); } // adds an error for each an unexpected property const expectedProperties = node.properties ? Object.keys(node.properties) : []; receivedProperties .filter((property) => expectedProperties.indexOf(property) === -1) .forEach((property) => { const propertyValue = getValue(data, property); if (isObject(node.additionalProperties)) { const validationErrors = validateNode( node.additionalProperties, propertyValue, `${pointer}/${property}`, path ); // @note: we pass through specific errors here validationErrors && errors.push(...validationErrors); } else { errors.push( node.createError("no-additional-properties-error", { pointer: `${pointer}/${property}`, schema, value: data, property, properties: expectedProperties }) ); } }); return errors; }