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).

156 lines 4.86 kB
import _addFormats, {} from 'ajv-formats'; import intersection from 'lodash/intersection.js'; import { AllOfSchema, AtomicSchemaObject, NotSchema, } from '../../atomic-schema/index.js'; import { MultipleOfAtomicSchema, TypeAtomicSchema, } from '../../built-in-plugins/type.js'; import { MaximumAtomicSchema, MinimumAtomicSchema, } from '../../built-in-plugins/index.js'; // workaround (https://github.com/microsoft/TypeScript/issues/50058#issuecomment-1297806160) const addFormats = _addFormats; export const stringFormatNames = [ 'date', 'time', 'date-time', 'iso-time', 'iso-date-time', 'duration', 'uri-reference', 'uri-template', 'uri', 'email', 'hostname', 'ipv4', 'ipv6', 'regex', 'uuid', 'json-pointer', 'relative-json-pointer', 'byte', /* there are no checks for 'password' or 'binary' => no need to add them */ // 'password', // 'binary', ]; export class StringFormatAtomicSchema extends AtomicSchemaObject { format; constructor(format) { super(); this.format = format; } negate() { return new AllOfSchema([ new NotSchema(this), /* actually redundant, but makes checking for contradictions easier */ new TypeAtomicSchema('string'), ]); } toJSONSchema() { return { format: this.format }; } } // TODO: derive further atomic schemas from format, e. g. `minLength` export const stringFormatLogicalCombinations = Object.fromEntries([ ...stringFormatNames.map((formatName) => [ formatName, new StringFormatAtomicSchema(formatName), ]), ]); export const numberFormatLogicalCombinations = { // number formats according to the checks in ajv-formats => no need to add // `'float'` or `'double'` as format int32: new AllOfSchema([ new MultipleOfAtomicSchema(1), new MinimumAtomicSchema(-(2 ** 31)), new MaximumAtomicSchema(2 ** 31 - 1), ]), int64: new MultipleOfAtomicSchema(1), }; const formatLogicalCombinations = { ...stringFormatLogicalCombinations, ...numberFormatLogicalCombinations, }; export const formatExtractionPlugin = { extract: ({ schema }) => { return (typeof schema.format !== 'string' || (formatLogicalCombinations[schema.format] ?? true)); }, }; export const formatValidationPlugin = { modifyAjv(ajv) { addFormats(ajv, { formats: Object.keys(formatLogicalCombinations), mode: 'full', keywords: false, // currently no support for ranges }); }, }; export const formatSimplificationPlugin = { appliesToJSONSchemaType: 'string', mergeableKeywords: [], simplify({ atomicSchemasByConstructor, negatedAtomicSchemasByConstructor, }) { const formats = atomicSchemasByConstructor .get(StringFormatAtomicSchema) .map(({ format }) => format); const negatedFormats = negatedAtomicSchemasByConstructor .get(StringFormatAtomicSchema) .map(({ format }) => format); if (intersection(formats, negatedFormats).length > 0) { return false; } return { allOf: [ ...formats.map((format) => ({ format })), ...negatedFormats.map((format) => ({ not: { format } })), ], }; }, }; /** * Adds support for the `format` values provided by * [`ajv-formats`](https://ajv.js.org/packages/ajv-formats.html). * * `format`s that apply to strings are only compared for equality, so that only * schemas like * * ```json * { * "allOf": [ * { "format": "email" }, * { "not": { "format": "email" } } * ] * } * ``` * * include a [contradiction](https://github.com/jobohner/json-schema-describes-subset/blob/v0.4.0/docs/README.md#contradictions). * * `format`s that apply to numbers are transformed to equivalent * {@link AtomicSchemaObject}s like {@link MultipleOfAtomicSchema}, * {@link MinimumAtomicSchema} or {@link MaximumAtomicSchema}. * * @example ```ts * import { schemaDescribesSubset } from 'json-schema-describes-subset' * import { formatPlugin } from 'json-schema-describes-subset/custom-plugins/format-plugin' * * console.log( * schemaDescribesSubset( * { format: 'email' }, * { format: 'date-time' }, * { plugins: [formatPlugin] }, * ), * ) // logs: `null` * * console.log( * schemaDescribesSubset( * { type: 'integer', minimum: 0, maximum: 10 }, * { format: 'int32' }, * { plugins: [formatPlugin] }, * ), * ) // logs: `true` * ``` * * @privateRemarks TODO: return type of {@link toDNF} * */ export const formatPlugin = [ formatValidationPlugin, formatExtractionPlugin, formatSimplificationPlugin, ]; //# sourceMappingURL=format-plugin.js.map