UNPKG

json-schema-describes-subset

Version:

Tools for static JSON schema analysis, including functions to determine if one schema describes a subset of another or if a schema describes the empty set or to convert a schema to its disjunctive normal form (DNF).

136 lines 5.95 kB
import isNumber from 'lodash/isNumber.js'; import { AllOfSchema, AtomicSchemaObject, NotSchema, } from '../../atomic-schema/index.js'; import { MultipleOfAtomicSchema, TypeAtomicSchema } from '../type.js'; import { ConstAtomicSchema } from '../const.js'; import { findMultipleOfCardinality, findMultipleOfUnionCardinality, isInteger, isValidMultiple, multiRealLCM, realLCM, } from './multiple-of-utils/index.js'; import { getNegatedConstValuesFromNegatedAtomicSchemasByConstructor } from '../negated-const-helpers.js'; export class MinimumAtomicSchema extends AtomicSchemaObject { minimum; constructor(minimum) { super(); this.minimum = minimum; } negate() { return new AllOfSchema([ new MaximumAtomicSchema(this.minimum), new NotSchema(new ConstAtomicSchema(this.minimum)), new TypeAtomicSchema('number'), ]); } toJSONSchema() { return { minimum: this.minimum }; } } export class MaximumAtomicSchema extends AtomicSchemaObject { maximum; constructor(maximum) { super(); this.maximum = maximum; } negate() { return new AllOfSchema([ new MinimumAtomicSchema(this.maximum), new NotSchema(new ConstAtomicSchema(this.maximum)), new TypeAtomicSchema('number'), ]); } toJSONSchema() { return { maximum: this.maximum }; } } export const numberExtraction = { extract: ({ schema }) => { const schemas = []; if (typeof schema.minimum === 'number') { schemas.push(new MinimumAtomicSchema(schema.minimum)); } if (typeof schema.maximum === 'number') { schemas.push(new MaximumAtomicSchema(schema.maximum)); } if (typeof schema.multipleOf === 'number') { schemas.push(schema.multipleOf === 0 ? // 0 would actually not be valid json schema, but just in case new ConstAtomicSchema(0) : new MultipleOfAtomicSchema(schema.multipleOf)); } if (typeof schema.exclusiveMinimum === 'number') { schemas.push(new AllOfSchema([ new MinimumAtomicSchema(schema.exclusiveMinimum), new NotSchema(new ConstAtomicSchema(schema.exclusiveMinimum)), ])); } if (typeof schema.exclusiveMaximum === 'number') { schemas.push(new AllOfSchema([ new MaximumAtomicSchema(schema.exclusiveMaximum), new NotSchema(new ConstAtomicSchema(schema.exclusiveMaximum)), ])); } if (schemas.length === 0) { return true; } return new AllOfSchema(schemas); }, }; export const numberSimplification = { appliesToJSONSchemaType: 'number', mergeableKeywords: ['minimum', 'maximum', 'multipleOf'], simplify({ atomicSchemasByConstructor, negatedAtomicSchemasByConstructor, validateConst, }) { const minimum = Math.max(...atomicSchemasByConstructor .get(MinimumAtomicSchema) .map(({ minimum }) => minimum)); const maximum = Math.min(...atomicSchemasByConstructor .get(MaximumAtomicSchema) .map(({ maximum }) => maximum)); if (minimum > maximum) { return false; } if (minimum === maximum) { return validateConst(minimum); } const negatedConsts = getNegatedConstValuesFromNegatedAtomicSchemasByConstructor(negatedAtomicSchemasByConstructor, isNumber); const negatedMultipleOfs = negatedAtomicSchemasByConstructor .get(MultipleOfAtomicSchema) .map(({ multipleOf }) => multipleOf); const multipleOf = multiRealLCM(...atomicSchemasByConstructor .get(MultipleOfAtomicSchema) .map(({ multipleOf }) => multipleOf)); if (multipleOf !== undefined) { if (negatedMultipleOfs.some((negatedMultipleOf) => isInteger(multipleOf / negatedMultipleOf))) { return false; } const { cardinality: possibleValuesCount, singleElement } = findMultipleOfCardinality(multipleOf, minimum, maximum); if (singleElement !== undefined) { return validateConst(singleElement); } if (possibleValuesCount === 0) { return false; } if (Number.isFinite(possibleValuesCount)) { /** * Applying de Morgan's theorem to {@link negatedMultipleOfs} and * {@link negatedConsts} makes a negated union of multipleOfs or consts. * If these (negated) multipeOfs or consts are intersected with * {@link mulitpleOf} first, that union is a subset of the elements * described by {@link mulitpleOf}. If {@link impossibleValuesCount} is * the same as {@link possibleValuesCount}, there are no elements in this * set => return true * */ const impossibleValuesCount = findMultipleOfUnionCardinality(minimum, maximum, negatedMultipleOfs.map((negatedMultipleOf) => realLCM(negatedMultipleOf, multipleOf)), negatedConsts.filter(isValidMultiple(multipleOf, minimum, maximum))); if (impossibleValuesCount >= possibleValuesCount) { return false; } } } return { ...(minimum > -Infinity ? { minimum } : {}), ...(maximum < Infinity ? { maximum } : {}), ...(multipleOf !== undefined ? { multipleOf } : {}), allOf: [ ...negatedConsts.map((constValue) => ({ not: { const: constValue } })), ...negatedMultipleOfs.map((multipleOf) => ({ not: { multipleOf } })), ], }; }, }; //# sourceMappingURL=number.js.map