UNPKG

json-schema-library

Version:

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

131 lines (120 loc) 5.24 kB
import settings from "../settings"; import { isObject } from "../utils/isObject"; import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword"; import { isBooleanSchema, 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 (schema.additionalProperties == null || isBooleanSchema(schema.additionalProperties)) { return; } if (!isObject(schema.additionalProperties)) { return node.createError("schema-error", { pointer: node.schemaLocation, schema, value: schema.additionalProperties, message: `keyword 'additionalProperties' must be a valid JSON Schema - receoved: ${typeof schema.additionalProperties}` }); } node.additionalProperties = node.compileSchema( schema.additionalProperties, `${evaluationPath}/additionalProperties`, `${schemaLocation}/additionalProperties` ); return node.additionalProperties.schemaValidation; } 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: ValidationReturnType = []; let receivedProperties = Object.keys(data).filter((prop) => settings.propertyBlacklist.includes(prop) === false); const patternProperties = node.patternProperties; if (Array.isArray(patternProperties)) { // filter received properties by matching patternProperties receivedProperties = receivedProperties.filter((prop) => { for (const property of patternProperties) { if (property.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 (propertyValue === undefined) { return; // do not validate undefined properties } if (isObject(node.additionalProperties)) { const validationErrors = validateNode( node.additionalProperties, propertyValue, `${pointer}/${property}`, path ); if (validationErrors) { // @note: we pass through specific errors here errors.push(...validationErrors); } } else { errors.push( node.createError("no-additional-properties-error", { pointer: `${pointer}/${property}`, schema, value: data, property, properties: expectedProperties }) ); } }); return errors; }