json-schema-diff
Advanced tools
Comparing version 0.12.0 to 0.12.1
@@ -0,1 +1,12 @@ | ||
<a name="0.12.1"></a> | ||
## [0.12.1](https://bitbucket.org/atlassian/json-schema-diff/compare/0.12.0...0.12.1) (2019-04-15) | ||
### Bug Fixes | ||
* add missing values to the array complement schema calculation ([d86b249](https://bitbucket.org/atlassian/json-schema-diff/commits/d86b249)) | ||
* add missing values to the array complement schema calculation ([8869982](https://bitbucket.org/atlassian/json-schema-diff/commits/8869982)) | ||
<a name="0.12.0"></a> | ||
@@ -2,0 +13,0 @@ # [0.12.0](https://bitbucket.org/atlassian/json-schema-diff/compare/0.11.0...0.12.0) (2019-04-09) |
@@ -11,11 +11,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const logger_1 = require("./common/logger"); | ||
const dereference_schema_1 = require("./diff-schemas/dereference-schema"); | ||
const parse_as_json_set_1 = require("./diff-schemas/parse-as-json-set"); | ||
const diff_sets_1 = require("./diff-schemas/set/diff-sets"); | ||
const validate_schemas_1 = require("./diff-schemas/validate-schemas"); | ||
const logDebug = (setName, set) => { | ||
if (process.env.JSON_SCHEMA_DIFF_ENABLE_DEBUG === 'true') { | ||
console.log(`\n${setName}`); | ||
console.log(JSON.stringify(set.toJsonSchema(), null, 2)); | ||
} | ||
}; | ||
exports.diffSchemas = (sourceSchema, destinationSchema) => __awaiter(this, void 0, void 0, function* () { | ||
@@ -27,13 +23,6 @@ const [dereferencedSourceSchema, dereferencedDestinationSchema] = yield Promise.all([ | ||
const sourceSet = parse_as_json_set_1.parseAsJsonSet(dereferencedSourceSchema); | ||
logDebug('sourceSet', sourceSet); | ||
logger_1.logSetDebug('sourceSet', sourceSet); | ||
const destinationSet = parse_as_json_set_1.parseAsJsonSet(dereferencedDestinationSchema); | ||
logDebug('destinationSet', destinationSet); | ||
const intersectionOfSets = sourceSet.intersect(destinationSet); | ||
logDebug('intersectionOfSets', intersectionOfSets); | ||
const intersectionOfSetsComplement = intersectionOfSets.complement(); | ||
logDebug('intersectionOfSetsComplement', intersectionOfSetsComplement); | ||
const addedToDestinationSet = intersectionOfSetsComplement.intersect(destinationSet); | ||
logDebug('addedToDestinationSet', addedToDestinationSet); | ||
const removedFromDestinationSet = intersectionOfSetsComplement.intersect(sourceSet); | ||
logDebug('removedFromDestinationSet', removedFromDestinationSet); | ||
logger_1.logSetDebug('destinationSet', destinationSet); | ||
const { addedToDestinationSet, removedFromDestinationSet } = diff_sets_1.diffSets(sourceSet, destinationSet, logger_1.logSetDebug); | ||
return { | ||
@@ -40,0 +29,0 @@ addedJsonSchema: addedToDestinationSet.toJsonSchema(), |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const diff_sets_1 = require("./diff-sets"); | ||
const omit_defaults_1 = require("./json-set/omit-defaults"); | ||
const set_1 = require("./set"); | ||
const noop = () => { | ||
return; | ||
}; | ||
class AllJsonSet { | ||
@@ -19,2 +23,5 @@ constructor() { | ||
} | ||
equal(other) { | ||
return other === this; | ||
} | ||
toJsonSchema() { | ||
@@ -40,2 +47,5 @@ return true; | ||
} | ||
equal(other) { | ||
return other === this; | ||
} | ||
toJsonSchema() { | ||
@@ -64,3 +74,7 @@ return false; | ||
const type = schemaTypes.concat(otherSchemaTypes); | ||
return Object.assign({}, schema, otherSchema, { type }); | ||
const mergedSchema = Object.assign({}, schema, otherSchema, { type }); | ||
if (schema.not && otherSchema.not) { | ||
mergedSchema.not = this.mergeCoreRepresentationJsonSchemas(schema.not, otherSchema.not); | ||
} | ||
return mergedSchema; | ||
} | ||
@@ -118,2 +132,6 @@ static toDiffJsonSchema(jsonSchema) { | ||
} | ||
equal(other) { | ||
const { addedToDestinationSet, removedFromDestinationSet } = diff_sets_1.diffSets(this, other, noop); | ||
return addedToDestinationSet.type === 'empty' && removedFromDestinationSet.type === 'empty'; | ||
} | ||
toJsonSchema() { | ||
@@ -120,0 +138,0 @@ const typeSetSchemas = Object |
@@ -48,2 +48,6 @@ "use strict"; | ||
}; | ||
exports.omitDefaults = (originalSchema) => omitDefaultsFromAnyOfSchema(omitDefaultAdditionalProperties(omitEmptyProperties(omitEmptyRequired(omitDefaultMaxProperties(omitDefaultMinProperties(omitDefaultMinItems(omitDefaultMaxItems(omitDefaultItems(originalSchema))))))))); | ||
const omitEmptyNotProperties = (schema) => { | ||
return schema.not | ||
? Object.assign({}, schema, { not: omitEmptyProperties(schema.not) }) : schema; | ||
}; | ||
exports.omitDefaults = (originalSchema) => omitEmptyNotProperties(omitDefaultsFromAnyOfSchema(omitDefaultAdditionalProperties(omitEmptyProperties(omitEmptyRequired(omitDefaultMaxProperties(omitDefaultMinProperties(omitDefaultMinItems(omitDefaultMaxItems(omitDefaultItems(originalSchema)))))))))); |
@@ -34,2 +34,5 @@ "use strict"; | ||
} | ||
equal() { | ||
throw new Error('Not implemented'); | ||
} | ||
toJsonSchema() { | ||
@@ -36,0 +39,0 @@ const schemas = this.subsets |
@@ -25,9 +25,15 @@ "use strict"; | ||
toJsonSchema() { | ||
return { | ||
items: this.config.items.toJsonSchema(), | ||
maxItems: this.config.maxItems, | ||
minItems: this.config.minItems, | ||
type: ['array'] | ||
}; | ||
return Object.assign({ items: this.config.items.toJsonSchema(), maxItems: this.config.maxItems, minItems: this.config.minItems, type: ['array'] }, this.notJsonSchema()); | ||
} | ||
notJsonSchema() { | ||
if (this.config.notItems) { | ||
return { | ||
not: { | ||
items: this.config.notItems.toJsonSchema(), | ||
type: ['array'] | ||
} | ||
}; | ||
} | ||
return {}; | ||
} | ||
} | ||
@@ -34,0 +40,0 @@ exports.allArraySubset = new subset_1.AllSubset('array'); |
@@ -8,12 +8,11 @@ "use strict"; | ||
}; | ||
const isMaxItemsAndMinItemsContradiction = (config) => { | ||
return config.minItems > config.maxItems; | ||
}; | ||
const isMinItemsContradiction = (config) => { | ||
return config.minItems === Infinity; | ||
}; | ||
exports.arraySubsetConfigHasContradictions = (config) => { | ||
return isItemsAndMinItemsContradiction(config) | ||
|| isMaxItemsAndMinItemsContradiction(config) | ||
|| isMinItemsContradiction(config); | ||
}; | ||
const isMaxItemsAndMinItemsContradiction = (config) => config.minItems > config.maxItems; | ||
const isMinItemsContradiction = (config) => config.minItems === Infinity; | ||
const isItemsAndNotItemsContradiction = (config) => config.notItems ? config.items.equal(config.notItems) : false; | ||
const contradictionTests = [ | ||
isItemsAndMinItemsContradiction, | ||
isMaxItemsAndMinItemsContradiction, | ||
isMinItemsContradiction, | ||
isItemsAndNotItemsContradiction | ||
]; | ||
exports.arraySubsetConfigHasContradictions = (config) => contradictionTests.some((contradictionTest) => contradictionTest(config)); |
@@ -6,5 +6,6 @@ "use strict"; | ||
const complementItems = (config) => ({ | ||
items: config.items.complement(), | ||
items: json_set_1.allJsonSet, | ||
maxItems: keyword_defaults_1.defaultMaxItems, | ||
minItems: 1 | ||
minItems: keyword_defaults_1.defaultMinItems, | ||
notItems: config.items | ||
}); | ||
@@ -11,0 +12,0 @@ const complementMinItems = (config) => ({ |
@@ -8,3 +8,6 @@ "use strict"; | ||
maxItems: intersectMaxItems(configA, configB), | ||
minItems: intersectMinItems(configA, configB) | ||
minItems: intersectMinItems(configA, configB), | ||
// TODO: This is wrong but we cannot expose it without the not keyword | ||
// {type: 'array', items: {type: 'number'}} -> {not: {type: 'array', items: {type: 'number'}}} | ||
notItems: configA.notItems | ||
}); |
@@ -5,7 +5,12 @@ "use strict"; | ||
const keyword_defaults_1 = require("../../keyword-defaults"); | ||
const maxItemsAllowsNoItems = (config) => config.maxItems === 0; | ||
const notItemsDisallowsEmptyArrays = (config) => config.notItems !== undefined && config.notItems.type === 'empty'; | ||
exports.simplifyArraySubsetConfig = (config) => { | ||
if (config.maxItems === 0) { | ||
if (maxItemsAllowsNoItems(config)) { | ||
return Object.assign({}, config, { items: json_set_1.emptyJsonSet, maxItems: keyword_defaults_1.defaultMaxItems }); | ||
} | ||
if (notItemsDisallowsEmptyArrays(config)) { | ||
return Object.assign({}, config, { notItems: undefined, minItems: 1 }); | ||
} | ||
return config; | ||
}; |
@@ -5,3 +5,2 @@ "use strict"; | ||
const intersect_object_subset_config_1 = require("./object-subset/intersect-object-subset-config"); | ||
const object_subset_config_1 = require("./object-subset/object-subset-config"); | ||
const object_subset_config_has_contradictions_1 = require("./object-subset/object-subset-config-has-contradictions"); | ||
@@ -16,2 +15,9 @@ const simplify_object_subset_config_1 = require("./object-subset/simplify-object-subset-config"); | ||
} | ||
static toSchemaProperties(properties) { | ||
const schemaProperties = {}; | ||
for (const propertyName of Object.keys(properties)) { | ||
schemaProperties[propertyName] = properties[propertyName].toJsonSchema(); | ||
} | ||
return schemaProperties; | ||
} | ||
get properties() { | ||
@@ -30,18 +36,15 @@ return this.config.properties; | ||
toJsonSchema() { | ||
const properties = this.toJsonSchemaMap(); | ||
const additionalProperties = this.config.additionalProperties.toJsonSchema(); | ||
return { | ||
additionalProperties, | ||
maxProperties: this.config.maxProperties, | ||
minProperties: this.config.minProperties, | ||
properties, | ||
required: this.config.required, | ||
type: ['object'] | ||
}; | ||
return Object.assign({ additionalProperties: this.config.additionalProperties.toJsonSchema(), maxProperties: this.config.maxProperties, minProperties: this.config.minProperties, properties: SomeObjectSubset.toSchemaProperties(this.config.properties), required: this.config.required, type: ['object'] }, this.notJsonSchema()); | ||
} | ||
toJsonSchemaMap() { | ||
return object_subset_config_1.getPropertyNames(this.config).reduce((acc, propertyName) => { | ||
acc[propertyName] = object_subset_config_1.getPropertySet(this.config, propertyName).toJsonSchema(); | ||
return acc; | ||
}, {}); | ||
notJsonSchema() { | ||
if (this.config.not) { | ||
return { | ||
not: { | ||
additionalProperties: this.config.not.additionalProperties.toJsonSchema(), | ||
properties: SomeObjectSubset.toSchemaProperties(this.config.not.properties), | ||
type: ['object'] | ||
} | ||
}; | ||
} | ||
return {}; | ||
} | ||
@@ -48,0 +51,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const Combinatorics = require("js-combinatorics"); | ||
const json_set_1 = require("../../json-set"); | ||
@@ -40,40 +39,13 @@ const keyword_defaults_1 = require("../../keyword-defaults"); | ||
}); | ||
const limitToAvoidPerformanceProblems = 32; | ||
const getPropertyPermutationGroups = (propertyNames) => { | ||
const propertyPermutationGroupGenerator = Combinatorics.power(propertyNames); | ||
const propertyPermutationGroups = []; | ||
for (let i = 0; i < limitToAvoidPerformanceProblems; i += 1) { | ||
const next = propertyPermutationGroupGenerator.next(); | ||
if (!next) { | ||
break; | ||
} | ||
propertyPermutationGroups.push(next); | ||
} | ||
return propertyPermutationGroups; | ||
}; | ||
const complementAdditionalPropertiesPermutationGroup = (propertyPermutationGroup, config) => { | ||
const propertiesForGroup = {}; | ||
for (const propertyName of object_subset_config_1.getPropertyNames(config)) { | ||
propertiesForGroup[propertyName] = propertyPermutationGroup.indexOf(propertyName) > -1 | ||
? json_set_1.allJsonSet | ||
: json_set_1.emptyJsonSet; | ||
} | ||
return { | ||
additionalProperties: config.additionalProperties.complement(), | ||
maxProperties: keyword_defaults_1.defaultMaxProperties, | ||
minProperties: propertyPermutationGroup.length + 1, | ||
properties: propertiesForGroup, | ||
required: propertyPermutationGroup | ||
}; | ||
}; | ||
const complementAdditionalProperties = (config) => { | ||
const complementedAdditionalProperties = []; | ||
const propertyNames = object_subset_config_1.getPropertyNames(config); | ||
const propertyPermutationGroups = getPropertyPermutationGroups(propertyNames); | ||
for (const propertyPermutationGroup of propertyPermutationGroups) { | ||
const complementForGroup = complementAdditionalPropertiesPermutationGroup(propertyPermutationGroup, config); | ||
complementedAdditionalProperties.push(complementForGroup); | ||
} | ||
return complementedAdditionalProperties; | ||
}; | ||
const complementAdditionalProperties = (config) => ({ | ||
additionalProperties: json_set_1.allJsonSet, | ||
maxProperties: keyword_defaults_1.defaultMaxProperties, | ||
minProperties: keyword_defaults_1.defaultMinProperties, | ||
not: { | ||
additionalProperties: config.additionalProperties, | ||
properties: config.properties | ||
}, | ||
properties: keyword_defaults_1.defaultProperties, | ||
required: keyword_defaults_1.defaultRequired | ||
}); | ||
exports.complementObjectSubsetConfig = (config) => { | ||
@@ -86,3 +58,3 @@ const complementedProperties = complementProperties(config); | ||
return [ | ||
...complementedAdditionalProperties, | ||
complementedAdditionalProperties, | ||
...complementedProperties, | ||
@@ -89,0 +61,0 @@ ...complementedRequiredProperties, |
@@ -22,4 +22,8 @@ "use strict"; | ||
minProperties: intersectMinProperties(configA, configB), | ||
// TODO: This is wrong but we cannot expose it without the not keyword | ||
// {type: 'object', additionalProperties: {type: 'number'}} | ||
// -> {not: {type: 'object', additionalProperties: {type: 'number'}}} | ||
not: configA.not, | ||
properties: intersectProperties(configA, configB), | ||
required: intersectRequired(configA, configB) | ||
}); |
@@ -17,2 +17,5 @@ "use strict"; | ||
const isMaxPropertiesAndRequiredContradiction = (config) => config.required.length > config.maxProperties; | ||
const isAdditionalPropertiesAndNotAdditionalPropertiesContradiction = (config) => | ||
// TODO: When we support 'not', does this need to look at not.properties? | ||
config.not ? config.additionalProperties.equal(config.not.additionalProperties) : false; | ||
const contradictionTests = [ | ||
@@ -23,4 +26,5 @@ isRequiredAndPropertiesAndAdditionalPropertiesContradiction, | ||
isMinPropertiesContradiction, | ||
isMaxPropertiesAndRequiredContradiction | ||
isMaxPropertiesAndRequiredContradiction, | ||
isAdditionalPropertiesAndNotAdditionalPropertiesContradiction | ||
]; | ||
exports.objectSubsetConfigHasContradictions = (config) => contradictionTests.some((contradictionTest) => contradictionTest(config)); |
@@ -5,7 +5,14 @@ "use strict"; | ||
const keyword_defaults_1 = require("../../keyword-defaults"); | ||
const maxPropertiesAllowsNoProperties = (config) => config.maxProperties === 0; | ||
const notDisallowsObjectsWithNoProperties = (config) => config.not !== undefined | ||
&& config.not.additionalProperties.type === 'empty' | ||
&& Object.keys(config.not.properties).length === 0; | ||
exports.simplifyObjectSubsetConfig = (config) => { | ||
if (config.maxProperties === 0) { | ||
if (maxPropertiesAllowsNoProperties(config)) { | ||
return Object.assign({}, config, { maxProperties: keyword_defaults_1.defaultMaxProperties, additionalProperties: json_set_1.emptyJsonSet }); | ||
} | ||
if (notDisallowsObjectsWithNoProperties(config)) { | ||
return Object.assign({}, config, { not: undefined, minProperties: 1 }); | ||
} | ||
return config; | ||
}; |
@@ -15,2 +15,5 @@ "use strict"; | ||
} | ||
equal() { | ||
throw new Error('Not Implemented'); | ||
} | ||
toJsonSchema() { | ||
@@ -32,2 +35,5 @@ return { type: [this.setType] }; | ||
} | ||
equal() { | ||
throw new Error('Not Implemented'); | ||
} | ||
toJsonSchema() { | ||
@@ -34,0 +40,0 @@ return false; |
@@ -58,9 +58,2 @@ # Json Schema Diff Supported Keywords | ||
### Additional Properties and Properties | ||
The `additionalProperties` and `properties` keywords interact with each other in a way that can create multiple sets of | ||
differences. In order to make json-schema-diff execute in a reasonable amount of time and consume a reasonable amount of | ||
memory a compromise was made to return a limited number of these differences when the number of sets grows too large. | ||
This means that there are some circumstances where the added or removed schema does not completely represent every | ||
single value that has been added or removed. | ||
## Keywords for Applying Subschemas Conditionally | ||
@@ -67,0 +60,0 @@ |
import {DiffResult} from '../api-types'; | ||
import {logSetDebug} from './common/logger'; | ||
import {dereferenceSchema} from './diff-schemas/dereference-schema'; | ||
import {parseAsJsonSet} from './diff-schemas/parse-as-json-set'; | ||
import {Set} from './diff-schemas/set/set'; | ||
import {diffSets} from './diff-schemas/set/diff-sets'; | ||
import {validateSchemas} from './diff-schemas/validate-schemas'; | ||
const logDebug = (setName: string, set: Set<'json'>) => { | ||
if (process.env.JSON_SCHEMA_DIFF_ENABLE_DEBUG === 'true') { | ||
console.log(`\n${setName}`); | ||
console.log(JSON.stringify(set.toJsonSchema(), null, 2)); | ||
} | ||
}; | ||
export const diffSchemas = async (sourceSchema: any, destinationSchema: any): Promise<DiffResult> => { | ||
@@ -22,14 +16,7 @@ const [dereferencedSourceSchema, dereferencedDestinationSchema] = await Promise.all([ | ||
const sourceSet = parseAsJsonSet(dereferencedSourceSchema); | ||
logDebug('sourceSet', sourceSet); | ||
logSetDebug('sourceSet', sourceSet); | ||
const destinationSet = parseAsJsonSet(dereferencedDestinationSchema); | ||
logDebug('destinationSet', destinationSet); | ||
logSetDebug('destinationSet', destinationSet); | ||
const intersectionOfSets = sourceSet.intersect(destinationSet); | ||
logDebug('intersectionOfSets', intersectionOfSets); | ||
const intersectionOfSetsComplement = intersectionOfSets.complement(); | ||
logDebug('intersectionOfSetsComplement', intersectionOfSetsComplement); | ||
const addedToDestinationSet = intersectionOfSetsComplement.intersect(destinationSet); | ||
logDebug('addedToDestinationSet', addedToDestinationSet); | ||
const removedFromDestinationSet = intersectionOfSetsComplement.intersect(sourceSet); | ||
logDebug('removedFromDestinationSet', removedFromDestinationSet); | ||
const {addedToDestinationSet, removedFromDestinationSet} = diffSets(sourceSet, destinationSet, logSetDebug); | ||
@@ -36,0 +23,0 @@ return { |
@@ -11,3 +11,4 @@ import {CoreSchemaMetaSchema, JsonSchema, JsonSchemaMap, SimpleTypes} from 'json-schema-spec-types'; | ||
} from './set/keyword-defaults'; | ||
import {ParsedPropertiesKeyword, Set} from './set/set'; | ||
import {Set} from './set/set'; | ||
import {ParsedPropertiesKeyword} from './set/subset/object-subset/object-subset-config'; | ||
@@ -14,0 +15,0 @@ const parseSchemaProperties = (schemaProperties: JsonSchemaMap = {}): ParsedPropertiesKeyword => { |
import {SimpleTypes} from 'json-schema-spec-types'; | ||
import {allJsonSet, emptyJsonSet, SomeJsonSet, TypeSets} from '../set/json-set'; | ||
import {ParsedPropertiesKeyword, Set} from '../set/set'; | ||
import {Set} from '../set/set'; | ||
import {ParsedPropertiesKeyword} from '../set/subset/object-subset/object-subset-config'; | ||
import {createArraySet} from './create-array-set'; | ||
@@ -5,0 +6,0 @@ import {createObjectSet} from './create-object-set'; |
import {SimpleTypes} from 'json-schema-spec-types'; | ||
import * as _ from 'lodash'; | ||
import {defaultMaxProperties, defaultMinProperties, defaultRequired} from '../set/keyword-defaults'; | ||
import {ParsedPropertiesKeyword, Set, Subset} from '../set/set'; | ||
import {Set, Subset} from '../set/set'; | ||
import {SetOfSubsets} from '../set/set-of-subsets'; | ||
import {allObjectSubset, createObjectSubsetFromConfig, emptyObjectSubset} from '../set/subset/object-subset'; | ||
import {ParsedPropertiesKeyword} from '../set/subset/object-subset/object-subset-config'; | ||
import {isTypeSupported} from './is-type-supported'; | ||
@@ -8,0 +9,0 @@ |
// tslint:disable:max-classes-per-file | ||
import {SimpleTypes} from 'json-schema-spec-types'; | ||
import {diffSets} from './diff-sets'; | ||
import {omitDefaults} from './json-set/omit-defaults'; | ||
@@ -26,2 +27,6 @@ import { | ||
const noop = (): void => { | ||
return; | ||
}; | ||
export class AllJsonSet implements JsonSet { | ||
@@ -43,2 +48,6 @@ public readonly setType = 'json'; | ||
public equal(other: Set<'json'>): boolean { | ||
return other === this; | ||
} | ||
public toJsonSchema(): RepresentationJsonSchema { | ||
@@ -67,2 +76,6 @@ return true; | ||
public equal(other: Set<'json'>): boolean { | ||
return other === this; | ||
} | ||
public toJsonSchema(): RepresentationJsonSchema { | ||
@@ -94,3 +107,3 @@ return false; | ||
return { | ||
const mergedSchema = { | ||
...schema, | ||
@@ -100,2 +113,8 @@ ...otherSchema, | ||
}; | ||
if (schema.not && otherSchema.not) { | ||
mergedSchema.not = this.mergeCoreRepresentationJsonSchemas(schema.not, otherSchema.not); | ||
} | ||
return mergedSchema; | ||
} | ||
@@ -169,2 +188,7 @@ | ||
public equal(other: Set<'json'>): boolean { | ||
const {addedToDestinationSet, removedFromDestinationSet} = diffSets(this, other, noop); | ||
return addedToDestinationSet.type === 'empty' && removedFromDestinationSet.type === 'empty'; | ||
} | ||
public toJsonSchema(): RepresentationJsonSchema { | ||
@@ -171,0 +195,0 @@ const typeSetSchemas = Object |
@@ -67,12 +67,19 @@ import * as _ from 'lodash'; | ||
const omitEmptyNotProperties = (schema: CoreRepresentationJsonSchema): CoreRepresentationJsonSchema => { | ||
return schema.not | ||
? {...schema, not: omitEmptyProperties(schema.not)} | ||
: schema; | ||
}; | ||
export const omitDefaults = | ||
(originalSchema: CoreRepresentationJsonSchema): CoreRepresentationJsonSchema => | ||
omitDefaultsFromAnyOfSchema( | ||
omitDefaultAdditionalProperties( | ||
omitEmptyProperties( | ||
omitEmptyRequired( | ||
omitDefaultMaxProperties( | ||
omitDefaultMinProperties( | ||
omitDefaultMinItems( | ||
omitDefaultMaxItems( | ||
omitDefaultItems(originalSchema))))))))); | ||
omitEmptyNotProperties( | ||
omitDefaultsFromAnyOfSchema( | ||
omitDefaultAdditionalProperties( | ||
omitEmptyProperties( | ||
omitEmptyRequired( | ||
omitDefaultMaxProperties( | ||
omitDefaultMinProperties( | ||
omitDefaultMinItems( | ||
omitDefaultMaxItems( | ||
omitDefaultItems(originalSchema)))))))))); |
import {SimpleTypes} from 'json-schema-spec-types'; | ||
import {ParsedPropertiesKeyword} from './set'; | ||
import {ParsedPropertiesKeyword} from './subset/object-subset/object-subset-config'; | ||
@@ -4,0 +4,0 @@ export const defaultProperties: ParsedPropertiesKeyword = {}; |
@@ -40,2 +40,6 @@ import * as _ from 'lodash'; | ||
public equal(): boolean { | ||
throw new Error('Not implemented'); | ||
} | ||
public toJsonSchema(): RepresentationJsonSchema { | ||
@@ -42,0 +46,0 @@ const schemas = this.subsets |
import {CoreSchemaMetaSchema, SimpleTypes} from 'json-schema-spec-types'; | ||
export interface ParsedPropertiesKeyword { | ||
[key: string]: Set<'json'>; | ||
} | ||
export interface SchemaProperties { | ||
@@ -16,2 +12,3 @@ [name: string]: RepresentationJsonSchema; | ||
anyOf?: RepresentationJsonSchema[]; | ||
not?: CoreRepresentationJsonSchema; | ||
} | ||
@@ -26,2 +23,3 @@ | ||
intersect(other: Set<T>): Set<T>; | ||
equal(other: Set<T>): boolean; | ||
toJsonSchema(): RepresentationJsonSchema; | ||
@@ -28,0 +26,0 @@ } |
// tslint:disable:max-classes-per-file | ||
import {RepresentationJsonSchema, Subset} from '../set'; | ||
import {CoreRepresentationJsonSchema, RepresentationJsonSchema, Subset} from '../set'; | ||
import {ArraySubsetConfig} from './array-subset/array-subset-config'; | ||
@@ -35,5 +35,18 @@ import {arraySubsetConfigHasContradictions} from './array-subset/array-subset-config-has-contradictions'; | ||
minItems: this.config.minItems, | ||
type: ['array'] | ||
type: ['array'], | ||
...this.notJsonSchema() | ||
}; | ||
} | ||
private notJsonSchema(): CoreRepresentationJsonSchema { | ||
if (this.config.notItems) { | ||
return { | ||
not: { | ||
items: this.config.notItems.toJsonSchema(), | ||
type: ['array'] | ||
} | ||
}; | ||
} | ||
return {}; | ||
} | ||
} | ||
@@ -40,0 +53,0 @@ |
@@ -9,14 +9,19 @@ import {ArraySubsetConfig} from './array-subset-config'; | ||
const isMaxItemsAndMinItemsContradiction = (config: ArraySubsetConfig): boolean => { | ||
return config.minItems > config.maxItems; | ||
}; | ||
const isMaxItemsAndMinItemsContradiction = (config: ArraySubsetConfig): boolean => config.minItems > config.maxItems; | ||
const isMinItemsContradiction = (config: ArraySubsetConfig): boolean => { | ||
return config.minItems === Infinity; | ||
}; | ||
const isMinItemsContradiction = (config: ArraySubsetConfig): boolean => config.minItems === Infinity; | ||
export const arraySubsetConfigHasContradictions = (config: ArraySubsetConfig): boolean => { | ||
return isItemsAndMinItemsContradiction(config) | ||
|| isMaxItemsAndMinItemsContradiction(config) | ||
|| isMinItemsContradiction(config); | ||
}; | ||
const isItemsAndNotItemsContradiction = (config: ArraySubsetConfig): boolean => | ||
config.notItems ? config.items.equal(config.notItems) : false; | ||
type contradictionTestFn = (config: ArraySubsetConfig) => boolean; | ||
const contradictionTests: contradictionTestFn[] = [ | ||
isItemsAndMinItemsContradiction, | ||
isMaxItemsAndMinItemsContradiction, | ||
isMinItemsContradiction, | ||
isItemsAndNotItemsContradiction | ||
]; | ||
export const arraySubsetConfigHasContradictions = (config: ArraySubsetConfig): boolean => | ||
contradictionTests.some((contradictionTest) => contradictionTest(config)); |
@@ -7,2 +7,3 @@ import {Set} from '../../set'; | ||
minItems: number; | ||
notItems?: Set<'json'>; | ||
} |
@@ -6,5 +6,6 @@ import {allJsonSet} from '../../json-set'; | ||
const complementItems = (config: ArraySubsetConfig): ArraySubsetConfig => ({ | ||
items: config.items.complement(), | ||
items: allJsonSet, | ||
maxItems: defaultMaxItems, | ||
minItems: 1 | ||
minItems: defaultMinItems, | ||
notItems: config.items | ||
}); | ||
@@ -11,0 +12,0 @@ |
@@ -15,3 +15,6 @@ import {ArraySubsetConfig} from './array-subset-config'; | ||
maxItems: intersectMaxItems(configA, configB), | ||
minItems: intersectMinItems(configA, configB) | ||
minItems: intersectMinItems(configA, configB), | ||
// TODO: This is wrong but we cannot expose it without the not keyword | ||
// {type: 'array', items: {type: 'number'}} -> {not: {type: 'array', items: {type: 'number'}}} | ||
notItems: configA.notItems | ||
}); |
@@ -5,8 +5,17 @@ import {emptyJsonSet} from '../../json-set'; | ||
const maxItemsAllowsNoItems = (config: ArraySubsetConfig): boolean => config.maxItems === 0; | ||
const notItemsDisallowsEmptyArrays = (config: ArraySubsetConfig): boolean => | ||
config.notItems !== undefined && config.notItems.type === 'empty'; | ||
export const simplifyArraySubsetConfig = (config: ArraySubsetConfig): ArraySubsetConfig => { | ||
if (config.maxItems === 0) { | ||
if (maxItemsAllowsNoItems(config)) { | ||
return {...config, items: emptyJsonSet, maxItems: defaultMaxItems}; | ||
} | ||
if (notItemsDisallowsEmptyArrays(config)) { | ||
return {...config, notItems: undefined, minItems: 1}; | ||
} | ||
return config; | ||
}; |
@@ -1,5 +0,5 @@ | ||
import {RepresentationJsonSchema, SchemaProperties, Subset} from '../set'; | ||
import {CoreRepresentationJsonSchema, RepresentationJsonSchema, SchemaProperties, Subset} from '../set'; | ||
import {complementObjectSubsetConfig} from './object-subset/complement-object-subset-config'; | ||
import {intersectObjectSubsetConfig} from './object-subset/intersect-object-subset-config'; | ||
import {getPropertyNames, getPropertySet, ObjectSubsetConfig} from './object-subset/object-subset-config'; | ||
import {ObjectSubsetConfig, ParsedPropertiesKeyword} from './object-subset/object-subset-config'; | ||
import {objectSubsetConfigHasContradictions} from './object-subset/object-subset-config-has-contradictions'; | ||
@@ -10,3 +10,13 @@ import {simplifyObjectSubsetConfig} from './object-subset/simplify-object-subset-config'; | ||
class SomeObjectSubset implements Subset<'object'> { | ||
private static toSchemaProperties(properties: ParsedPropertiesKeyword): SchemaProperties { | ||
const schemaProperties: SchemaProperties = {}; | ||
for (const propertyName of Object.keys(properties)) { | ||
schemaProperties[propertyName] = properties[propertyName].toJsonSchema(); | ||
} | ||
return schemaProperties; | ||
} | ||
public readonly setType = 'object'; | ||
public readonly type = 'some'; | ||
@@ -34,20 +44,26 @@ | ||
public toJsonSchema(): RepresentationJsonSchema { | ||
const properties = this.toJsonSchemaMap(); | ||
const additionalProperties = this.config.additionalProperties.toJsonSchema(); | ||
return { | ||
additionalProperties, | ||
additionalProperties: this.config.additionalProperties.toJsonSchema(), | ||
maxProperties: this.config.maxProperties, | ||
minProperties: this.config.minProperties, | ||
properties, | ||
properties: SomeObjectSubset.toSchemaProperties(this.config.properties), | ||
required: this.config.required, | ||
type: ['object'] | ||
type: ['object'], | ||
...this.notJsonSchema() | ||
}; | ||
} | ||
private toJsonSchemaMap(): SchemaProperties { | ||
return getPropertyNames(this.config).reduce<SchemaProperties>((acc, propertyName) => { | ||
acc[propertyName] = getPropertySet(this.config, propertyName).toJsonSchema(); | ||
return acc; | ||
}, {}); | ||
private notJsonSchema(): CoreRepresentationJsonSchema { | ||
if (this.config.not) { | ||
return { | ||
not: { | ||
additionalProperties: this.config.not.additionalProperties.toJsonSchema(), | ||
properties: SomeObjectSubset.toSchemaProperties(this.config.not.properties), | ||
type: ['object'] | ||
} | ||
}; | ||
} | ||
return {}; | ||
} | ||
} | ||
@@ -54,0 +70,0 @@ |
@@ -1,5 +0,3 @@ | ||
import * as Combinatorics from 'js-combinatorics'; | ||
import {allJsonSet, emptyJsonSet} from '../../json-set'; | ||
import {defaultMaxProperties, defaultMinProperties, defaultProperties, defaultRequired} from '../../keyword-defaults'; | ||
import {ParsedPropertiesKeyword} from '../../set'; | ||
import {getPropertyNames, getPropertySet, ObjectSubsetConfig} from './object-subset-config'; | ||
@@ -47,53 +45,14 @@ | ||
const limitToAvoidPerformanceProblems = 32; | ||
const complementAdditionalProperties = (config: ObjectSubsetConfig): ObjectSubsetConfig => ({ | ||
additionalProperties: allJsonSet, | ||
maxProperties: defaultMaxProperties, | ||
minProperties: defaultMinProperties, | ||
not: { | ||
additionalProperties: config.additionalProperties, | ||
properties: config.properties | ||
}, | ||
properties: defaultProperties, | ||
required: defaultRequired | ||
}); | ||
const getPropertyPermutationGroups = (propertyNames: string[]): string[][] => { | ||
const propertyPermutationGroupGenerator = Combinatorics.power(propertyNames); | ||
const propertyPermutationGroups: string[][] = []; | ||
for (let i = 0; i < limitToAvoidPerformanceProblems; i += 1) { | ||
const next = propertyPermutationGroupGenerator.next(); | ||
if (!next) { | ||
break; | ||
} | ||
propertyPermutationGroups.push(next); | ||
} | ||
return propertyPermutationGroups; | ||
}; | ||
const complementAdditionalPropertiesPermutationGroup = ( | ||
propertyPermutationGroup: string[], | ||
config: ObjectSubsetConfig | ||
): ObjectSubsetConfig => { | ||
const propertiesForGroup: ParsedPropertiesKeyword = {}; | ||
for (const propertyName of getPropertyNames(config)) { | ||
propertiesForGroup[propertyName] = propertyPermutationGroup.indexOf(propertyName) > -1 | ||
? allJsonSet | ||
: emptyJsonSet; | ||
} | ||
return { | ||
additionalProperties: config.additionalProperties.complement(), | ||
maxProperties: defaultMaxProperties, | ||
minProperties: propertyPermutationGroup.length + 1, | ||
properties: propertiesForGroup, | ||
required: propertyPermutationGroup | ||
}; | ||
}; | ||
const complementAdditionalProperties = (config: ObjectSubsetConfig): ObjectSubsetConfig[] => { | ||
const complementedAdditionalProperties: ObjectSubsetConfig[] = []; | ||
const propertyNames = getPropertyNames(config); | ||
const propertyPermutationGroups = getPropertyPermutationGroups(propertyNames); | ||
for (const propertyPermutationGroup of propertyPermutationGroups) { | ||
const complementForGroup = complementAdditionalPropertiesPermutationGroup(propertyPermutationGroup, config); | ||
complementedAdditionalProperties.push(complementForGroup); | ||
} | ||
return complementedAdditionalProperties; | ||
}; | ||
export const complementObjectSubsetConfig = (config: ObjectSubsetConfig): ObjectSubsetConfig[] => { | ||
@@ -107,3 +66,3 @@ const complementedProperties = complementProperties(config); | ||
return [ | ||
...complementedAdditionalProperties, | ||
complementedAdditionalProperties, | ||
...complementedProperties, | ||
@@ -110,0 +69,0 @@ ...complementedRequiredProperties, |
@@ -1,3 +0,2 @@ | ||
import {ParsedPropertiesKeyword} from '../../set'; | ||
import {getPropertyNames, getPropertySet, ObjectSubsetConfig} from './object-subset-config'; | ||
import {getPropertyNames, getPropertySet, ObjectSubsetConfig, ParsedPropertiesKeyword} from './object-subset-config'; | ||
import {unique} from './unique'; | ||
@@ -34,4 +33,8 @@ | ||
minProperties: intersectMinProperties(configA, configB), | ||
// TODO: This is wrong but we cannot expose it without the not keyword | ||
// {type: 'object', additionalProperties: {type: 'number'}} | ||
// -> {not: {type: 'object', additionalProperties: {type: 'number'}}} | ||
not: configA.not, | ||
properties: intersectProperties(configA, configB), | ||
required: intersectRequired(configA, configB) | ||
}); |
@@ -30,2 +30,6 @@ import {getPropertySet, ObjectSubsetConfig} from './object-subset-config'; | ||
const isAdditionalPropertiesAndNotAdditionalPropertiesContradiction = (config: ObjectSubsetConfig): boolean => | ||
// TODO: When we support 'not', does this need to look at not.properties? | ||
config.not ? config.additionalProperties.equal(config.not.additionalProperties) : false; | ||
type contradictionTestFn = (config: ObjectSubsetConfig) => boolean; | ||
@@ -38,3 +42,4 @@ | ||
isMinPropertiesContradiction, | ||
isMaxPropertiesAndRequiredContradiction | ||
isMaxPropertiesAndRequiredContradiction, | ||
isAdditionalPropertiesAndNotAdditionalPropertiesContradiction | ||
]; | ||
@@ -41,0 +46,0 @@ |
@@ -1,8 +0,16 @@ | ||
import {ParsedPropertiesKeyword, Set} from '../../set'; | ||
import {Set} from '../../set'; | ||
export interface ParsedPropertiesKeyword { | ||
[key: string]: Set<'json'>; | ||
} | ||
export interface ObjectSubsetConfig { | ||
additionalProperties: Set<'json'>; | ||
properties: ParsedPropertiesKeyword; | ||
maxProperties: number; | ||
minProperties: number; | ||
not?: { | ||
additionalProperties: Set<'json'>; | ||
properties: ParsedPropertiesKeyword; | ||
}; | ||
properties: ParsedPropertiesKeyword; | ||
required: string[]; | ||
@@ -9,0 +17,0 @@ } |
@@ -5,8 +5,19 @@ import {emptyJsonSet} from '../../json-set'; | ||
const maxPropertiesAllowsNoProperties = (config: ObjectSubsetConfig): boolean => config.maxProperties === 0; | ||
const notDisallowsObjectsWithNoProperties = (config: ObjectSubsetConfig): boolean => | ||
config.not !== undefined | ||
&& config.not.additionalProperties.type === 'empty' | ||
&& Object.keys(config.not.properties).length === 0; | ||
export const simplifyObjectSubsetConfig = (config: ObjectSubsetConfig): ObjectSubsetConfig => { | ||
if (config.maxProperties === 0) { | ||
if (maxPropertiesAllowsNoProperties(config)) { | ||
return {...config, maxProperties: defaultMaxProperties, additionalProperties: emptyJsonSet}; | ||
} | ||
if (notDisallowsObjectsWithNoProperties(config)) { | ||
return {...config, not: undefined, minProperties: 1}; | ||
} | ||
return config; | ||
}; |
@@ -22,2 +22,6 @@ // tslint:disable:max-classes-per-file | ||
public equal(): boolean { | ||
throw new Error('Not Implemented'); | ||
} | ||
public toJsonSchema(): RepresentationJsonSchema { | ||
@@ -42,2 +46,6 @@ return {type: [this.setType]}; | ||
public equal(): boolean { | ||
throw new Error('Not Implemented'); | ||
} | ||
public toJsonSchema(): RepresentationJsonSchema { | ||
@@ -44,0 +52,0 @@ return false; |
{ | ||
"name": "json-schema-diff", | ||
"version": "0.12.0", | ||
"version": "0.12.1", | ||
"description": "A language agnostic CLI tool and nodejs api to identify differences between two json schema files.", | ||
@@ -39,3 +39,2 @@ "bin": { | ||
"@types/jasmine": "^3.3.9", | ||
"@types/js-combinatorics": "^0.5.31", | ||
"@types/json-schema": "^7.0.3", | ||
@@ -65,3 +64,2 @@ "@types/lodash": "^4.14.108", | ||
"commander": "^2.15.1", | ||
"js-combinatorics": "^0.5.4", | ||
"json-schema-ref-parser": "^6.1.0", | ||
@@ -68,0 +66,0 @@ "json-schema-spec-types": "^0.1.2", |
@@ -1,439 +0,531 @@ | ||
import {JsonSchema, SimpleTypes} from 'json-schema-spec-types'; | ||
import {invokeDiff} from '../support/invoke-diff'; | ||
import {DiffTestCase, registerDiffTestCases} from '../support/register-diff-test-cases'; | ||
describe('diff-schemas type array', () => { | ||
describe('items', () => { | ||
it('should find removed differences within array schemas that have constrained', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: { | ||
type: ['string', 'number'] | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'array with items constraint has items constraint made more restrictive', | ||
examples: [[], [1], ['a'], [1, 'a'], [true], [true, 1], 'a'], | ||
input: { | ||
a: { | ||
items: { | ||
type: ['string', 'number'] | ||
}, | ||
type: 'array' | ||
}, | ||
b: { | ||
items: { | ||
type: 'number' | ||
}, | ||
type: 'array' | ||
} | ||
}, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
items: { | ||
type: 'number' | ||
output: { | ||
added: false, | ||
removed: { | ||
items: { | ||
type: ['number', 'string'] | ||
}, | ||
not: { | ||
items: { | ||
type: ['number'] | ||
}, | ||
type: ['array'] | ||
}, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'numbers and array gets numbers constrained and items constraint', | ||
examples: [[], 1, [1], ['a'], [1, 'a'], ['a', 'a'], [1, 1], 'foo'], | ||
input: { | ||
a: { | ||
type: ['array', 'number'] | ||
}, | ||
b: { | ||
items: {type: 'number'}, | ||
type: ['array'] | ||
} | ||
}, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allArraysOfTypeStringWithAtLeastOneItem: JsonSchema = { | ||
items: { | ||
type: ['string'] | ||
output: { | ||
added: false, | ||
removed: { | ||
not: { | ||
items: {type: ['number']}, | ||
type: ['array'] | ||
}, | ||
type: ['array', 'number'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'array with items constraint has items constraint changed', | ||
examples: [[], [1], ['a'], [1, 'a'], [true], [true, 1], 'a'], | ||
input: { | ||
a: { | ||
items: { | ||
type: 'number' | ||
}, | ||
type: 'array' | ||
}, | ||
b: { | ||
items: { | ||
type: 'string' | ||
}, | ||
type: 'array' | ||
} | ||
}, | ||
minItems: 1, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allArraysOfTypeStringWithAtLeastOneItem); | ||
}); | ||
it('should find added differences within array schemas that have expanded', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: { | ||
type: 'number' | ||
output: { | ||
added: { | ||
items: { | ||
type: ['string'] | ||
}, | ||
minItems: 1, | ||
type: ['array'] | ||
}, | ||
removed: { | ||
items: { | ||
type: ['number'] | ||
}, | ||
minItems: 1, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'remove array with items constraint', | ||
examples: [[], [1], ['a'], [1, 'a'], 'a'], | ||
input: { | ||
a: { | ||
items: { | ||
type: 'number' | ||
}, | ||
type: 'array' | ||
}, | ||
b: false | ||
}, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
items: { | ||
type: ['string', 'number'] | ||
output: { | ||
added: false, | ||
removed: { | ||
items: { | ||
type: ['number'] | ||
}, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'unconstrained array has items constraint added', | ||
examples: [[], [1], ['a'], [1, 'a'], 'a'], | ||
input: { | ||
a: { | ||
type: 'array' | ||
}, | ||
b: { | ||
items: { | ||
type: 'string' | ||
}, | ||
type: 'array' | ||
} | ||
}, | ||
type: 'array' | ||
}; | ||
output: { | ||
added: false, | ||
removed: { | ||
not: { | ||
items: { | ||
type: ['string'] | ||
}, | ||
type: ['array'] | ||
}, | ||
type: ['array'] | ||
} | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
registerDiffTestCases(testCases); | ||
}); | ||
const allArraysOfTypeStringWithAtLeastOneItem: JsonSchema = { | ||
items: { | ||
type: ['string'] | ||
describe('minItems', () => { | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'array with minItems constraint removed', | ||
examples: [[], [1], [1, 2], 'a'], | ||
input: { | ||
a: { | ||
minItems: 1, | ||
type: 'array' | ||
}, | ||
b: false | ||
}, | ||
minItems: 1, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allArraysOfTypeStringWithAtLeastOneItem); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find differences within array schemas that have changed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: { | ||
type: 'number' | ||
output: { | ||
added: false, | ||
removed: { | ||
minItems: 1, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'unconstrained array constrained with minItems', | ||
examples: [[], [1], [1, 2], 'a'], | ||
input: { | ||
a: { | ||
type: 'array' | ||
}, | ||
b: { | ||
minItems: 2, | ||
type: 'array' | ||
} | ||
}, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
items: { | ||
type: 'string' | ||
output: { | ||
added: false, | ||
removed: { | ||
maxItems: 1, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'array constrained with minItems has minItems constraint made more restrictive', | ||
examples: [[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], 'a'], | ||
input: { | ||
a: { | ||
minItems: 2, | ||
type: 'array' | ||
}, | ||
b: { | ||
minItems: 4, | ||
type: 'array' | ||
} | ||
}, | ||
type: 'array' | ||
}; | ||
output: { | ||
added: false, | ||
removed: { | ||
maxItems: 3, | ||
minItems: 2, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'empty array support in the result is represented as items=false', | ||
examples: [[], [1]], | ||
input: { | ||
a: { | ||
type: 'array' | ||
}, | ||
b: { | ||
minItems: 1, | ||
type: 'array' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: { | ||
items: false, | ||
type: ['array'] | ||
} | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
registerDiffTestCases(testCases); | ||
}); | ||
const allArraysOfTypeStringWithAtLeastOneItem: JsonSchema = { | ||
items: { | ||
type: ['string'] | ||
describe('maxItems', () => { | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'array with maxItems constraint removed altogether', | ||
examples: [[], [1, 2], [1, 2, 3], 'foo'], | ||
input: { | ||
a: { | ||
maxItems: 2, | ||
type: 'array' | ||
}, | ||
b: false | ||
}, | ||
minItems: 1, | ||
type: ['array'] | ||
}; | ||
const allArraysOfTypeNumberWithAtLeastOneItem: JsonSchema = { | ||
items: { | ||
type: ['number'] | ||
output: { | ||
added: false, | ||
removed: { | ||
maxItems: 2, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'unconstrained array constrained with maxItems', | ||
examples: [[], [1, 2], [1, 2, 3], [1, 2, 3, 4], 'foo'], | ||
input: { | ||
a: { | ||
type: 'array' | ||
}, | ||
b: { | ||
maxItems: 2, | ||
type: 'array' | ||
} | ||
}, | ||
minItems: 1, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allArraysOfTypeStringWithAtLeastOneItem); | ||
expect(diffResult.removedJsonSchema).toEqual(allArraysOfTypeNumberWithAtLeastOneItem); | ||
}); | ||
it('should find a remove difference when a constrained array is removed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: { | ||
type: 'number' | ||
output: { | ||
added: false, | ||
removed: { | ||
minItems: 3, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'array constrained with maxItems has maxItems constraint made less restrictive', | ||
examples: [[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5]], | ||
input: { | ||
a: { | ||
maxItems: 1, | ||
type: 'array' | ||
}, | ||
b: { | ||
maxItems: 4, | ||
type: 'array' | ||
} | ||
}, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = false; | ||
output: { | ||
added: { | ||
maxItems: 4, | ||
minItems: 2, | ||
type: ['array'] | ||
}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
registerDiffTestCases(testCases); | ||
}); | ||
const allArraysOfTypeNumber: JsonSchema = { | ||
items: { | ||
type: ['number'] | ||
describe('items + minItems', () => { | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'array constrained with items and minItems has the items constraint made less restrictive', | ||
examples: [[], [1], ['foo'], [1, 2], ['foo', 'bar'], [1, 'foo'], 'foo'], | ||
input: { | ||
a: { | ||
items: {type: ['string', 'number']}, | ||
minItems: 2, | ||
type: 'array' | ||
}, | ||
b: { | ||
items: {type: 'string'}, | ||
minItems: 2, | ||
type: 'array' | ||
} | ||
}, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allArraysOfTypeNumber); | ||
}); | ||
it('should find a removed difference when a constraint is added to arrays', async () => { | ||
const sourceSchema: JsonSchema = { | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
items: { | ||
type: 'number' | ||
output: { | ||
added: false, | ||
removed: { | ||
items: { | ||
type: ['number', 'string'] | ||
}, | ||
minItems: 2, | ||
not: { | ||
items: { | ||
type: ['string'] | ||
}, | ||
type: ['array'] | ||
}, | ||
type: ['array'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'array constrained with contradicting minItems and items', | ||
examples: [[], [1], 'foo'], | ||
input: { | ||
a: { | ||
items: false, | ||
minItems: 1, | ||
type: 'array' | ||
}, | ||
b: false | ||
}, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allTypesExceptNumber: SimpleTypes[] = ['array', 'boolean', 'integer', 'null', 'object', 'string']; | ||
const allArraysOfAllTypesExceptNumberWithAtLeastOneItem: JsonSchema = { | ||
items: { | ||
type: allTypesExceptNumber | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
}, | ||
{ | ||
description: 'array constrained with contradicting minItems and items has minItems removed', | ||
examples: [[], [1], 'foo'], | ||
input: { | ||
a: { | ||
items: false, | ||
minItems: 1, | ||
type: 'array' | ||
}, | ||
b: { | ||
items: false, | ||
type: 'array' | ||
} | ||
}, | ||
minItems: 1, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allArraysOfAllTypesExceptNumberWithAtLeastOneItem); | ||
}); | ||
}); | ||
output: { | ||
added: { | ||
items: false, | ||
type: ['array'] | ||
}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
describe('minItems', () => { | ||
it('should find a remove difference if array with minItems constraint is removed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
minItems: 1, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = false; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const arraysWithAtLeastOneItem: JsonSchema = { | ||
minItems: 1, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(arraysWithAtLeastOneItem); | ||
}); | ||
it('should find a remove difference when array is constrained with minItems', async () => { | ||
const sourceSchema: JsonSchema = { | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
minItems: 2, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const arraysWithUpToOneItem: JsonSchema = { | ||
maxItems: 1, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(arraysWithUpToOneItem); | ||
}); | ||
it('should find a removed difference with correct minItems-maxItems constraint range', async () => { | ||
const sourceSchema: JsonSchema = { | ||
minItems: 2, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
minItems: 4, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const arraysWithNoItems: JsonSchema = { | ||
maxItems: 3, | ||
minItems: 2, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(arraysWithNoItems); | ||
}); | ||
it('should represent an array with no items in a consistent way', async () => { | ||
const sourceSchema: JsonSchema = { | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
minItems: 1, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const anArrayWithNoItems: JsonSchema = { | ||
items: false, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(anArrayWithNoItems); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('maxItems', () => { | ||
it('should find a remove difference when an array with maxItems constraint is removed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
maxItems: 2, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = false; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allArraysWithAtMostTwoItems: JsonSchema = { | ||
maxItems: 2, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allArraysWithAtMostTwoItems); | ||
}); | ||
it('should find a removed difference when an array is constrained with maxItems', async () => { | ||
const sourceSchema: JsonSchema = { | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
maxItems: 2, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allArraysWithAtLeastThreeItems: JsonSchema = { | ||
minItems: 3, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allArraysWithAtLeastThreeItems); | ||
}); | ||
it('should find an added difference when an maxItems is increased', async () => { | ||
const sourceSchema: JsonSchema = { | ||
maxItems: 10, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
maxItems: 15, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allArraysWithAtLeastElevenAndAtMostFifteenItems: JsonSchema = { | ||
maxItems: 15, | ||
minItems: 11, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allArraysWithAtLeastElevenAndAtMostFifteenItems); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
describe('items + minItems', () => { | ||
it('should find a remove difference if items schema is constrained but minItems remains the same', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: {type: ['string', 'number']}, | ||
minItems: 2, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
items: {type: 'string'}, | ||
minItems: 2, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const arraysOfTypeNumberWithAtLeastTwoItems: JsonSchema = { | ||
items: {type: ['number']}, | ||
minItems: 2, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(arraysOfTypeNumberWithAtLeastTwoItems); | ||
}); | ||
it('should consider a contradiction to be equivalent to a schema accepting nothing', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: false, | ||
minItems: 1, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = false; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find added differences when a contradiction is removed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: false, | ||
minItems: 1, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
items: false, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const anArrayWithNoItems: JsonSchema = { | ||
items: false, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(anArrayWithNoItems); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
describe('items + maxItems', () => { | ||
it('should not find a difference when comparing different ways of an array with no items', async () => { | ||
const sourceSchema: JsonSchema = { | ||
maxItems: 0, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
items: false, | ||
type: 'array' | ||
}; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'different ways of representing arrays with no items', | ||
examples: [[], [1], 'foo'], | ||
input: { | ||
a: { | ||
maxItems: 0, | ||
type: 'array' | ||
}, | ||
b: { | ||
items: false, | ||
type: 'array' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
}, | ||
{ | ||
description: 'array with no items has items constraint relaxed and the maxItems constraint restricted', | ||
examples: [[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4], 'foo'], | ||
input: { | ||
a: { | ||
items: false, | ||
type: 'array' | ||
}, | ||
b: { | ||
maxItems: 3, | ||
type: 'array' | ||
} | ||
}, | ||
output: { | ||
added: { | ||
maxItems: 3, | ||
minItems: 1, | ||
type: ['array'] | ||
}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('minItems + maxItems', () => { | ||
it('should find two sets of added differences when an item range is expanded', async () => { | ||
const sourceSchema: JsonSchema = { | ||
maxItems: 10, | ||
minItems: 5, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
maxItems: 15, | ||
minItems: 1, | ||
type: 'array' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allArraysOfLengthOneToFourOrElevenToFifteen: JsonSchema = { | ||
anyOf: [ | ||
{ | ||
maxItems: 4, | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'array range is expanded in both directions', | ||
examples: [ | ||
[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6], | ||
[1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7, 8], 'foo' | ||
], | ||
input: { | ||
a: { | ||
maxItems: 5, | ||
minItems: 3, | ||
type: 'array' | ||
}, | ||
b: { | ||
maxItems: 7, | ||
minItems: 1, | ||
type: ['array'] | ||
type: 'array' | ||
} | ||
}, | ||
output: { | ||
added: { | ||
anyOf: [ | ||
{ | ||
maxItems: 2, | ||
minItems: 1, | ||
type: ['array'] | ||
}, | ||
{ | ||
maxItems: 7, | ||
minItems: 6, | ||
type: ['array'] | ||
} | ||
] | ||
}, | ||
{ | ||
maxItems: 15, | ||
minItems: 11, | ||
type: ['array'] | ||
} | ||
] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allArraysOfLengthOneToFourOrElevenToFifteen); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
removed: false | ||
} | ||
} | ||
]; | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('items + minItems + maxItems', () => { | ||
it('should find three sets of added differences when an item range and schema is expanded', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: { | ||
type: ['array', 'boolean', 'integer', 'null', 'number', 'object'] | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'array items, minItems and maxItems constraints are all made less restrictive', | ||
examples: [ | ||
[], [1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6], | ||
[1, 2, 3, 4, 5, 6, 7], [1, 2, 3, 4, 5, 6, 7, 8], ['a'], ['a', 'b'], ['a', 'b', 'c'], | ||
['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd', 'e'], ['a', 'b', 'c', 'd', 'e', 'f'], | ||
['a', 'b', 'c', 'd', 'e', 'f', 'g'], ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], [1, 'a'], | ||
[1, 2, 'a'], [1, 2, 3, 'a'], [1, 2, 3, 4, 'a'], [1, 2, 3, 4, 5, 'a'], [1, 2, 3, 4, 5, 6, 'a'], | ||
[1, 2, 3, 4, 5, 6, 7, 'a'], 'foo' | ||
], | ||
input: { | ||
a: { | ||
items: { | ||
type: ['string'] | ||
}, | ||
maxItems: 5, | ||
minItems: 3, | ||
type: 'array' | ||
}, | ||
b: { | ||
maxItems: 7, | ||
minItems: 1, | ||
type: 'array' | ||
} | ||
}, | ||
maxItems: 10, | ||
minItems: 5, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
maxItems: 15, | ||
minItems: 1, | ||
type: 'array' | ||
}; | ||
output: { | ||
added: { | ||
anyOf: [ | ||
{ | ||
maxItems: 7, | ||
minItems: 1, | ||
not: { | ||
items: { | ||
type: ['string'] | ||
}, | ||
type: ['array'] | ||
}, | ||
type: ['array'] | ||
}, | ||
{ | ||
maxItems: 2, | ||
minItems: 1, | ||
type: ['array'] | ||
}, | ||
{ | ||
maxItems: 7, | ||
minItems: 6, | ||
type: ['array'] | ||
} | ||
] | ||
}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allArraysWithLengthOneToFour: JsonSchema = { | ||
maxItems: 4, | ||
minItems: 1, | ||
type: ['array'] | ||
}; | ||
const allArraysWithLengthElevenToFifteen: JsonSchema = { | ||
maxItems: 15, | ||
minItems: 11, | ||
type: ['array'] | ||
}; | ||
const allArraysOfTypeStringWithLengthFiveToTen: JsonSchema = { | ||
items: { | ||
type: ['string'] | ||
}, | ||
maxItems: 15, | ||
minItems: 1, | ||
type: ['array'] | ||
}; | ||
const allArraysMatchingConstraints: JsonSchema = { | ||
anyOf: [ | ||
allArraysOfTypeStringWithLengthFiveToTen, | ||
allArraysWithLengthOneToFour, | ||
allArraysWithLengthElevenToFifteen | ||
] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allArraysMatchingConstraints); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
}); |
@@ -1,846 +0,820 @@ | ||
import {CoreSchemaMetaSchema, JsonSchema, SimpleTypes} from 'json-schema-spec-types'; | ||
import {invokeDiff} from '../support/invoke-diff'; | ||
import {DiffTestCase, registerDiffTestCases} from '../support/register-diff-test-cases'; | ||
describe('diff-schemas type object', () => { | ||
describe('additionalProperties', () => { | ||
it('should find a difference between schemas with boolean additional properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: true, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
type: 'object' | ||
}; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'object additionalProperties constrained to accept nothing', | ||
examples: [{}, {a: 1}, {a: 1, b: 'a'}, 'foo'], | ||
input: { | ||
a: { | ||
additionalProperties: true, | ||
type: 'object' | ||
}, | ||
b: { | ||
additionalProperties: false, | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: { | ||
minProperties: 1, | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'additionalProperties constrained to one type gets relaxed with support for another type', | ||
examples: [ | ||
{}, {a: 1}, {a: 'a'}, {a: 1, b: 1}, {a: 'a', b: 'b'}, {a: 1, b: 'b'}, {a: 1, b: true}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
additionalProperties: {type: ['number']}, | ||
type: 'object' | ||
}, | ||
b: { | ||
additionalProperties: {type: ['number', 'string']}, | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: { | ||
additionalProperties: { | ||
type: ['number', 'string'] | ||
}, | ||
not: { | ||
additionalProperties: { | ||
type: ['number'] | ||
}, | ||
type: ['object'] | ||
}, | ||
type: ['object'] | ||
}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithAtLeastOneProperty); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('properties', () => { | ||
it('should find removed differences when a property constraint is added', async () => { | ||
const allTypesButNull: SimpleTypes[] = ['object', 'string', 'array', 'integer', 'number', 'boolean']; | ||
const sourceSchema: JsonSchema = { | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
properties: { | ||
name: { | ||
type: allTypesButNull | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'object with no constraints gets a property constrained to some types', | ||
examples: [{}, {name: 1}, {name: null}, {other: 1}, {name: 1, other: 1}, {name: null, other: 1}, 'foo'], | ||
input: { | ||
a: { | ||
type: 'object' | ||
}, | ||
b: { | ||
properties: { | ||
name: { | ||
type: ['object', 'string', 'array', 'integer', 'number', 'boolean'] | ||
} | ||
}, | ||
type: 'object' | ||
} | ||
}, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithRequiredNullName: JsonSchema = { | ||
properties: { | ||
name: {type: ['null']} | ||
output: { | ||
added: false, | ||
removed: { | ||
properties: { | ||
name: {type: ['null']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'object property constrained to a type is changed to be constrained to a different type', | ||
examples: [ | ||
{}, {other: 1}, {name: []}, {name: 'a'}, {name: 1}, {name: [], other: 1}, {name: 'a', other: 1}, | ||
{name: true, other: 1}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
properties: { | ||
name: {type: 'array'} | ||
}, | ||
type: 'object' | ||
}, | ||
b: { | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: 'object' | ||
} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithRequiredNullName); | ||
}); | ||
it('should find an add and a remove differences when a property constraint is changed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
name: {type: 'array'} | ||
output: { | ||
added: { | ||
properties: { | ||
name: {type: ['string']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}, | ||
removed: { | ||
properties: { | ||
name: {type: ['array']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'objects get a hard constraint on a property', | ||
examples: [ | ||
{}, {first: 1}, {last: 1}, {first: 1, middle: 1}, {last: 1, middle: 1}, {first: 1, last: 1}, | ||
{first: 1, last: 1, middle: 1}, 'foo'], | ||
input: { | ||
a: { | ||
type: 'object' | ||
}, | ||
b: { | ||
properties: { | ||
first: true, | ||
last: false | ||
}, | ||
type: 'object' | ||
} | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}; | ||
output: { | ||
added: false, | ||
removed: { | ||
properties: { | ||
last: true | ||
}, | ||
required: ['last'], | ||
type: ['object'] | ||
} | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithRequiredStringName: JsonSchema = { | ||
properties: { | ||
name: {type: ['string']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
const allObjectsWithRequiredArrayName: JsonSchema = { | ||
properties: { | ||
name: {type: ['array']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithRequiredStringName); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithRequiredArrayName); | ||
}); | ||
it('should not infer that all objects are supported when some properties are constrained ', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
first: true, | ||
last: false | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithRequiredLastProperty: JsonSchema = { | ||
properties: { | ||
last: true | ||
}, | ||
required: ['last'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithRequiredLastProperty); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('required', () => { | ||
it('should find a difference between schemas with required property completely removed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
required: ['first'], | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = {type: 'object'}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithNoFirst: JsonSchema = { | ||
properties: { | ||
first: false | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'object with required property constraint is relaxed to support all objects', | ||
examples: [{}, {first: 1}, {last: 1}, {first: 1, last: 1}, 'foo'], | ||
input: { | ||
a: { | ||
required: ['first'], | ||
type: 'object' | ||
}, | ||
b: {type: 'object'} | ||
}, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithNoFirst); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
output: { | ||
added: { | ||
properties: { | ||
first: false | ||
}, | ||
type: ['object'] | ||
}, | ||
removed: false | ||
} | ||
}, | ||
{ | ||
description: '', | ||
examples: [ | ||
{}, {first: 1}, {last: 1}, {other: 1}, {first: 1, other: 1}, {last: 1, other: 1}, | ||
{first: 1, last: 1}, {first: 1, last: 1, other: 1}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
required: ['first', 'last'], | ||
type: 'object' | ||
}, | ||
b: { | ||
required: ['last'], | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: { | ||
properties: {first: false}, | ||
required: ['last'], | ||
type: ['object'] | ||
}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
it('should find a difference between schemas with required property removed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
required: ['name', 'first'], | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
required: ['name'], | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithRequiredNameAndNoFirst: JsonSchema = { | ||
properties: {first: false}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithRequiredNameAndNoFirst); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('minProperties', () => { | ||
it('should find a removed difference when a minProperties constraint is added', async () => { | ||
const sourceSchema: JsonSchema = { | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
minProperties: 10, | ||
type: 'object' | ||
}; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'objects gets a minProperties constraint', | ||
examples: [{}, {a: 1}, {a: 1, b: 1}, {a: 1, b: 1, c: 1}, {a: 1, b: 1, c: 1, d: 1}, 'foo'], | ||
input: { | ||
a: { | ||
type: 'object' | ||
}, | ||
b: { | ||
minProperties: 3, | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: { | ||
maxProperties: 2, | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'objects with minProperties constraint gets minProperties made more restrictive', | ||
examples: [ | ||
{}, {a: 1}, {a: 1, b: 1}, {a: 1, b: 1, c: 1}, {a: 1, b: 1, c: 1, d: 1}, | ||
{a: 1, b: 1, c: 1, d: 1, e: 1}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
minProperties: 2, | ||
type: 'object' | ||
}, | ||
b: { | ||
minProperties: 5, | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: { | ||
maxProperties: 4, | ||
minProperties: 2, | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'objects get constrained to have at least one property', | ||
examples: [{}, {a: 1}, {a: 1, b: 1}, 'foo'], | ||
input: { | ||
a: { | ||
type: 'object' | ||
}, | ||
b: { | ||
minProperties: 1, | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: { | ||
additionalProperties: false, | ||
type: ['object'] | ||
} | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithZeroToNineProperties: JsonSchema = { | ||
maxProperties: 9, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithZeroToNineProperties); | ||
}); | ||
it('should find an removed difference when a minProperties constraint made more strict', async () => { | ||
const sourceSchema: JsonSchema = { | ||
minProperties: 5, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
minProperties: 10, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithFiveToNineProperties: JsonSchema = { | ||
maxProperties: 9, | ||
minProperties: 5, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithFiveToNineProperties); | ||
}); | ||
it('should find an object with no properties removed when minProperties 1 constraint is added', async () => { | ||
const sourceSchema: JsonSchema = { | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
minProperties: 1, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const anObjectWithNoProperties: JsonSchema = { | ||
additionalProperties: false, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(anObjectWithNoProperties); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('maxProperties', () => { | ||
it('should find a removed difference when a maxProperties constraint is added', async () => { | ||
const sourceSchema: JsonSchema = { | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
maxProperties: 10, | ||
type: 'object' | ||
}; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'objects gets a maxProperties constraint', | ||
examples: [{}, {a: 1}, {a: 1, b: 1}, {a: 1, b: 1, c: 1}, 'foo'], | ||
input: { | ||
a: { | ||
type: 'object' | ||
}, | ||
b: { | ||
maxProperties: 1, | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: { | ||
minProperties: 2, | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'objects with a maxProperties constraint gets maxProperties made more restrictive', | ||
examples: [ | ||
{}, {a: 1}, {a: 1, b: 1}, {a: 1, b: 1, c: 1}, {a: 1, b: 1, c: 1, d: 1}, | ||
{a: 1, b: 1, c: 1, d: 1, e: 1}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
maxProperties: 4, | ||
type: 'object' | ||
}, | ||
b: { | ||
maxProperties: 1, | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: { | ||
maxProperties: 4, | ||
minProperties: 2, | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'object with maxProperties=0 constraint gets constrained to accept nothing', | ||
examples: [{}, {a: 1}, 'foo'], | ||
input: { | ||
a: { | ||
maxProperties: 0, | ||
type: 'object' | ||
}, | ||
b: false | ||
}, | ||
output: { | ||
added: false, | ||
removed: { | ||
additionalProperties: false, | ||
type: ['object'] | ||
} | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithAtLeastElevenProperties: JsonSchema = { | ||
minProperties: 11, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithAtLeastElevenProperties); | ||
}); | ||
it('should find an removed difference when a maxProperties constraint is made more strict', async () => { | ||
const sourceSchema: JsonSchema = { | ||
maxProperties: 10, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
maxProperties: 5, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithSixToTenProperties: JsonSchema = { | ||
maxProperties: 10, | ||
minProperties: 6, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithSixToTenProperties); | ||
}); | ||
it('should consider a maxProperties constraint of 0 to be an object with no properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
maxProperties: 0, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = false; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const anObjectWithNoProperties: JsonSchema = { | ||
additionalProperties: false, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(anObjectWithNoProperties); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('additionalProperties + maxProperties', () => { | ||
it('should find no differences between different representations of an object with no properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: false, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
maxProperties: 0, | ||
type: 'object' | ||
}; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'hard additionalProperties constraint replaced with equivalent maxProperties=0 constraint', | ||
examples: [{}, {a: 1}, 'foo'], | ||
input: { | ||
a: { | ||
additionalProperties: false, | ||
type: 'object' | ||
}, | ||
b: { | ||
maxProperties: 0, | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('additionalProperties + minProperties', () => { | ||
it('should infer that no objects are supported when additional and min properties contradict', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: false, | ||
minProperties: 1, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = false; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'objects with contradicting additionalProperties and minProperties is considered false', | ||
examples: [{}, {a: 1}, 'foo'], | ||
input: { | ||
a: { | ||
additionalProperties: false, | ||
minProperties: 1, | ||
type: 'object' | ||
}, | ||
b: false | ||
}, | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('additionalProperties + properties', () => { | ||
it('should infer that all objects are supported when no constraints are applied by properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
name: true | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'object with redundant relaxed property constraint gets additionalProperties constraint', | ||
examples: [{}, {name: 1}, {other: 1}, {name: 1, other: 1}, 'foo'], | ||
input: { | ||
a: { | ||
properties: { | ||
name: true | ||
}, | ||
type: 'object' | ||
}, | ||
b: { | ||
additionalProperties: false, | ||
type: 'object' | ||
} | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithAtLeastOneProperty); | ||
}); | ||
it('should find no differences when a property matching additionalProperties is removed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: {type: 'string'}, | ||
properties: { | ||
name: {type: 'string'} | ||
output: { | ||
added: false, | ||
removed: { | ||
minProperties: 1, | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'redundant property constraint matching additionalProperties constraint is removed', | ||
examples: [ | ||
{}, {other: 1}, {other: 'a'}, {name: 1}, {name: 'a'}, {name: 'a', other: 1}, {name: 1, other: 'a'}, | ||
{name: 'a', other: 'b'}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
additionalProperties: {type: 'string'}, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}, | ||
b: { | ||
additionalProperties: {type: 'string'}, | ||
type: 'object' | ||
} | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: {type: 'string'}, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find differences between schemas with properties and non boolean additionalProperties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: {type: ['string', 'number']}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: {type: ['string', 'number']}, | ||
properties: { | ||
name: { | ||
type: 'string' | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
}, | ||
{ | ||
description: 'object with additionalProperties constraint gets a more restrictive property constraint', | ||
examples: [ | ||
{}, {a: 1}, {a: 'a'}, {a: true}, {a: 'a', b: 1}, {a: 'a', b: 1, c: true}, {name: 1}, {name: 'a'}, | ||
{name: true}, {name: 1, other: 1}, {name: 1, other: 'a'}, {name: 1, other: true}, | ||
{name: 'a', other: 1}, {name: 'a', other: 'a'}, {name: 'a', other: true}, {name: true, other: 1}, | ||
'foo' | ||
], | ||
input: { | ||
a: { | ||
additionalProperties: {type: ['string', 'number']}, | ||
type: 'object' | ||
}, | ||
b: { | ||
additionalProperties: {type: ['string', 'number']}, | ||
properties: { | ||
name: { | ||
type: 'string' | ||
} | ||
}, | ||
type: 'object' | ||
} | ||
}, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithRequiredNumberNameAndStringOrNumberAdditionalProperties: JsonSchema = { | ||
additionalProperties: {type: ['number', 'string']}, | ||
properties: { | ||
name: {type: ['number']} | ||
output: { | ||
added: false, | ||
removed: { | ||
additionalProperties: {type: ['number', 'string']}, | ||
properties: { | ||
name: {type: ['number']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'object with property constraint gets a fully restrictive additionalProperties constraint', | ||
examples: [ | ||
{}, {name: 'a'}, {name: 1}, {other: true}, {name: 'a', other: true}, {name: 1, other: true}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}, | ||
b: { | ||
additionalProperties: false, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: 'object' | ||
} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema) | ||
.toEqual(allObjectsWithRequiredNumberNameAndStringOrNumberAdditionalProperties); | ||
}); | ||
it('should find differences in schemas with boolean additionalProperties and optional properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
name: {type: 'string'} | ||
output: { | ||
added: false, | ||
removed: { | ||
not: { | ||
additionalProperties: false, | ||
properties: { | ||
name: {type: ['string']} | ||
}, | ||
type: ['object'] | ||
}, | ||
properties: { | ||
name: {type: ['string']} | ||
}, | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'numbers and property-constrained object gets numbers constrained ' + | ||
'and additionalProperties constraint', | ||
examples: [ | ||
{}, 1, {name: 1}, {name: 'a'}, {other: 1}, {name: 1, other: 'a'}, {name: 'a', other: 'a'}, | ||
{name: 'a', other: 1}, {name: 1, other: 1}, 'foo'], | ||
input: { | ||
a: { | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: ['object', 'number'] | ||
}, | ||
b: { | ||
additionalProperties: false, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
} | ||
}, | ||
type: ['object'] | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
name: {type: 'string'} | ||
output: { | ||
added: false, | ||
removed: { | ||
not: { | ||
additionalProperties: false, | ||
properties: { | ||
name: {type: ['string']} | ||
}, | ||
type: ['object'] | ||
}, | ||
properties: { | ||
name: {type: ['string']} | ||
}, | ||
type: ['number', 'object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'objects with 2 property constraints get additionalProperties that accepts nothing', | ||
examples: [ | ||
{}, {first: 'a'}, {first: 1}, {last: 'a'}, {last: 1}, {other: 'a'}, {first: 'a', last: 'a'}, | ||
{first: 1, last: 'a'}, {first: 'a', last: 1}, {first: 1, last: 1}, | ||
{first: 'a', last: 'a', other: 1}, {first: 1, last: 'a', other: 1}, {first: 'a', last: 1, other: 1}, | ||
{first: 1, last: 1, other: 1}, 'foo'], | ||
input: { | ||
a: { | ||
properties: { | ||
first: {type: 'string'}, | ||
last: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}, | ||
b: { | ||
additionalProperties: false, | ||
properties: { | ||
first: {type: 'string'}, | ||
last: {type: 'string'} | ||
}, | ||
type: 'object' | ||
} | ||
}, | ||
type: ['object'] | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithNoNameAndAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
properties: { | ||
name: false | ||
}, | ||
type: ['object'] | ||
}; | ||
const allObjectsWithRequiredStringNameAndAtLeastAnotherProperty: JsonSchema = { | ||
minProperties: 2, | ||
properties: { | ||
name: { | ||
type: ['string'] | ||
output: { | ||
added: false, | ||
removed: { | ||
not: { | ||
additionalProperties: false, | ||
properties: { | ||
first: {type: ['string']}, | ||
last: {type: ['string']} | ||
}, | ||
type: ['object'] | ||
}, | ||
properties: { | ||
first: {type: ['string']}, | ||
last: {type: ['string']} | ||
}, | ||
type: ['object'] | ||
} | ||
} | ||
}, | ||
{ | ||
description: 'objects with 2 property constraints has additionalProperties constrained', | ||
examples: [ | ||
{}, {first: 'a'}, {first: 1}, {last: 'a'}, {last: 1}, {a: 1}, {a: []}, | ||
{first: 'a', a: 1}, {first: 'a', a: true}, {first: 'a', a: true, b: 1}, {last: 'a', a: true, b: 1}, | ||
{last: 'a', a: 1}, {last: 'a', a: true}, {first: 'a', last: 'a', a: true, b: 1}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
additionalProperties: { | ||
type: ['boolean', 'number'] | ||
}, | ||
properties: { | ||
first: {type: 'string'}, | ||
last: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}, | ||
b: { | ||
additionalProperties: { | ||
type: 'number' | ||
}, | ||
properties: { | ||
first: {type: 'string'}, | ||
last: {type: 'string'} | ||
}, | ||
type: 'object' | ||
} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual({ | ||
anyOf: [ | ||
allObjectsWithNoNameAndAtLeastOneProperty, | ||
allObjectsWithRequiredStringNameAndAtLeastAnotherProperty | ||
] | ||
}); | ||
}); | ||
it('should add numbers into the anyOf when the object differences result in an anyOf', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: ['object', 'number'] | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithNoNameAndAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
properties: { | ||
name: false | ||
}, | ||
type: ['object'] | ||
}; | ||
const allObjectsWithRequiredStringNameAndAtLeastAnotherProperty: JsonSchema = { | ||
minProperties: 2, | ||
properties: { | ||
name: { | ||
type: ['string'] | ||
output: { | ||
added: false, | ||
removed: { | ||
additionalProperties: { | ||
type: ['boolean', 'number'] | ||
}, | ||
not: { | ||
additionalProperties: { | ||
type: ['number'] | ||
}, | ||
properties: { | ||
first: {type: ['string']}, | ||
last: {type: ['string']} | ||
}, | ||
type: ['object'] | ||
}, | ||
properties: { | ||
first: {type: ['string']}, | ||
last: {type: ['string']} | ||
}, | ||
type: ['object'] | ||
} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
const allNumbers: JsonSchema = {type: ['number']}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual({ | ||
anyOf: [ | ||
allObjectsWithNoNameAndAtLeastOneProperty, | ||
allObjectsWithRequiredStringNameAndAtLeastAnotherProperty, | ||
allNumbers | ||
] | ||
}); | ||
}); | ||
} | ||
} | ||
]; | ||
it('should find 4 sets of differences when additional properties is changed with 2 properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
first: {type: 'string'}, | ||
last: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
first: {type: 'string'}, | ||
last: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const anyObjectWithNoFirstAndNoLastAndAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
properties: { | ||
first: false, | ||
last: false | ||
}, | ||
type: ['object'] | ||
}; | ||
const anyObjectWithNoFirstAndRequiredStringLastAndAtLeastTwoProperties: JsonSchema = { | ||
minProperties: 2, | ||
properties: { | ||
first: false, | ||
last: {type: ['string']} | ||
}, | ||
required: ['last'], | ||
type: ['object'] | ||
}; | ||
const anyObjectWithRequiredStringFirstAndNoLastAndAtLeastTwoProperties: JsonSchema = { | ||
minProperties: 2, | ||
properties: { | ||
first: {type: ['string']}, | ||
last: false | ||
}, | ||
required: ['first'], | ||
type: ['object'] | ||
}; | ||
const anyObjectWithRequiredStringFirstAndRequiredStringLastAndAtLeastThreeProperties: JsonSchema = { | ||
minProperties: 3, | ||
properties: { | ||
first: {type: ['string']}, | ||
last: {type: ['string']} | ||
}, | ||
required: ['first', 'last'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual({ | ||
anyOf: [ | ||
anyObjectWithNoFirstAndNoLastAndAtLeastOneProperty, | ||
anyObjectWithRequiredStringFirstAndNoLastAndAtLeastTwoProperties, | ||
anyObjectWithNoFirstAndRequiredStringLastAndAtLeastTwoProperties, | ||
anyObjectWithRequiredStringFirstAndRequiredStringLastAndAtLeastThreeProperties | ||
] | ||
}); | ||
}); | ||
it('should find 8 sets of differences when additional properties is changed with 3 properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
p1: {type: 'string'}, | ||
p2: {type: 'string'}, | ||
p3: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
p1: {type: 'string'}, | ||
p2: {type: 'string'}, | ||
p3: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const anyObjectWithNoP123AndAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
properties: { | ||
p1: false, | ||
p2: false, | ||
p3: false | ||
}, | ||
type: ['object'] | ||
}; | ||
const anyObjectWithRequiredStringP1AndNoP23AndAtLeastTwoProperties: JsonSchema = { | ||
minProperties: 2, | ||
properties: { | ||
p1: {type: ['string']}, | ||
p2: false, | ||
p3: false | ||
}, | ||
required: ['p1'], | ||
type: ['object'] | ||
}; | ||
const anyObjectWithNoP13AndRequiredStringP2AndAtLeastTwoProperties: JsonSchema = { | ||
minProperties: 2, | ||
properties: { | ||
p1: false, | ||
p2: {type: ['string']}, | ||
p3: false | ||
}, | ||
required: ['p2'], | ||
type: ['object'] | ||
}; | ||
const anyObjectWithNoP12AndRequiredStringP3AndAtLeastTwoProperties: JsonSchema = { | ||
minProperties: 2, | ||
properties: { | ||
p1: false, | ||
p2: false, | ||
p3: {type: ['string']} | ||
}, | ||
required: ['p3'], | ||
type: ['object'] | ||
}; | ||
const anyObjectWithRequiredStringP12AndNoP3AndAtLeastThreeProperties: JsonSchema = { | ||
minProperties: 3, | ||
properties: { | ||
p1: {type: ['string']}, | ||
p2: {type: ['string']}, | ||
p3: false | ||
}, | ||
required: ['p1', 'p2'], | ||
type: ['object'] | ||
}; | ||
const anyObjectWithRequiredStringP13AndNoP2AtLeastThreeProperties: JsonSchema = { | ||
minProperties: 3, | ||
properties: { | ||
p1: {type: ['string']}, | ||
p2: false, | ||
p3: {type: ['string']} | ||
}, | ||
required: ['p1', 'p3'], | ||
type: ['object'] | ||
}; | ||
const anyObjectWithNoP1AndRequiredStringP23AndAtLeastThreeProperties: JsonSchema = { | ||
minProperties: 3, | ||
properties: { | ||
p1: false, | ||
p2: {type: ['string']}, | ||
p3: {type: ['string']} | ||
}, | ||
required: ['p2', 'p3'], | ||
type: ['object'] | ||
}; | ||
const anyObjectWithRequiredStringP123AndAtLeastFourProperties: JsonSchema = { | ||
minProperties: 4, | ||
properties: { | ||
p1: {type: ['string']}, | ||
p2: {type: ['string']}, | ||
p3: {type: ['string']} | ||
}, | ||
required: ['p1', 'p2', 'p3'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual({ | ||
anyOf: [ | ||
anyObjectWithNoP123AndAtLeastOneProperty, | ||
anyObjectWithRequiredStringP1AndNoP23AndAtLeastTwoProperties, | ||
anyObjectWithNoP13AndRequiredStringP2AndAtLeastTwoProperties, | ||
anyObjectWithRequiredStringP12AndNoP3AndAtLeastThreeProperties, | ||
anyObjectWithNoP12AndRequiredStringP3AndAtLeastTwoProperties, | ||
anyObjectWithRequiredStringP13AndNoP2AtLeastThreeProperties, | ||
anyObjectWithNoP1AndRequiredStringP23AndAtLeastThreeProperties, | ||
anyObjectWithRequiredStringP123AndAtLeastFourProperties | ||
] | ||
}); | ||
}); | ||
it('should find 32 sets of differences when additional properties is changed with 5 properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
p1: {type: 'string'}, | ||
p2: {type: 'string'}, | ||
p3: {type: 'string'}, | ||
p4: {type: 'string'}, | ||
p5: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
p1: {type: 'string'}, | ||
p2: {type: 'string'}, | ||
p3: {type: 'string'}, | ||
p4: {type: 'string'}, | ||
p5: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const removedJsonSchema = diffResult.removedJsonSchema as CoreSchemaMetaSchema; | ||
const removedJsonSchemaAnyOf = removedJsonSchema.anyOf as JsonSchema[]; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(removedJsonSchemaAnyOf.length).toBe(32); | ||
}); | ||
it('should limit the number of sets of differences found to 32 to avoid performance problems', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
p1: {type: 'string'}, | ||
p2: {type: 'string'}, | ||
p3: {type: 'string'}, | ||
p4: {type: 'string'}, | ||
p5: {type: 'string'}, | ||
p6: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
p1: {type: 'string'}, | ||
p2: {type: 'string'}, | ||
p3: {type: 'string'}, | ||
p4: {type: 'string'}, | ||
p5: {type: 'string'}, | ||
p6: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const removedJsonSchema = diffResult.removedJsonSchema as CoreSchemaMetaSchema; | ||
const removedJsonSchemaAnyOf = removedJsonSchema.anyOf as JsonSchema[]; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(removedJsonSchemaAnyOf.length).toBe(32); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('maxProperties + required', () => { | ||
it('should infer that no objects are supported when required and maxProperties contradict', async () => { | ||
const sourceSchema: JsonSchema = { | ||
maxProperties: 1, | ||
required: ['first', 'last'], | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = false; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'object with required and maxProperties contradiction', | ||
examples: [{}, {first: 1}, {last: 1}, {first: 1, last: 1}, {foo: 1}, 'foo'], | ||
input: { | ||
a: { | ||
maxProperties: 1, | ||
required: ['first', 'last'], | ||
type: 'object' | ||
}, | ||
b: false | ||
}, | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('maxProperties + minProperties', () => { | ||
it('should consider contradictions to accept no objects', async () => { | ||
const sourceSchema: JsonSchema = { | ||
maxProperties: 0, | ||
minProperties: 1, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
type: 'object' | ||
}; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'object with minProperties and maxProperties contradiction', | ||
examples: [{}, {a: 1}, {a: 1, b: 1}, 'foo'], | ||
input: { | ||
a: { | ||
maxProperties: 0, | ||
minProperties: 1, | ||
type: 'object' | ||
}, | ||
b: { | ||
type: 'object' | ||
} | ||
}, | ||
output: { | ||
added: {type: ['object']}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjects: JsonSchema = {type: ['object']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjects); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('properties + required', () => { | ||
it('should not find differences between two different contradictions', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
name: false | ||
}, | ||
required: ['name'], | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
properties: { | ||
address: { | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'comparing object contradictions at different layers', | ||
examples: [{}, {name: 1}, {foo: 1}, {name: 1, foo: 1}, {address: 1}, {address: {street: 1}}, 'foo'], | ||
input: { | ||
a: { | ||
properties: { | ||
street: false | ||
name: false | ||
}, | ||
required: ['street'], | ||
required: ['name'], | ||
type: 'object' | ||
}, | ||
b: { | ||
properties: { | ||
address: { | ||
properties: { | ||
street: false | ||
}, | ||
required: ['street'], | ||
type: 'object' | ||
} | ||
}, | ||
required: ['address'], | ||
type: 'object' | ||
} | ||
}, | ||
required: ['address'], | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find differences when required property with changed schema is added', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
first: { | ||
type: ['string'] | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
}, | ||
{ | ||
description: 'adding a required property with a changed schema', | ||
examples: [ | ||
{}, {name: 1}, {first: 1}, {first: 'a'}, {first: []}, {foo: 1}, {name: 1, first: 1}, | ||
{name: 1, first: 'a'}, {name: 1, first: []}, {name: 1, foo: 1}, 'foo' | ||
], | ||
input: { | ||
a: { | ||
properties: { | ||
first: { | ||
type: ['string'] | ||
} | ||
}, | ||
required: ['name'], | ||
type: 'object' | ||
}, | ||
b: { | ||
properties: { | ||
first: { | ||
type: ['array'] | ||
} | ||
}, | ||
required: ['name', 'first'], | ||
type: 'object' | ||
} | ||
}, | ||
required: ['name'], | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
properties: { | ||
first: { | ||
type: ['array'] | ||
output: { | ||
added: { | ||
properties: { | ||
first: {type: ['array']} | ||
}, | ||
required: ['name', 'first'], | ||
type: ['object'] | ||
}, | ||
removed: { | ||
properties: { | ||
first: {type: ['string']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
} | ||
}, | ||
required: ['name', 'first'], | ||
type: 'object' | ||
}; | ||
} | ||
} | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
]; | ||
const allObjectsWithRequiredNameAndRequiredArrayFirst: JsonSchema = { | ||
properties: { | ||
first: {type: ['array']} | ||
}, | ||
required: ['name', 'first'], | ||
type: ['object'] | ||
}; | ||
const allObjectsWithRequiredNameAndRequiredStringFirst: JsonSchema = { | ||
properties: { | ||
first: {type: ['string']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithRequiredNameAndRequiredArrayFirst); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithRequiredNameAndRequiredStringFirst); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
describe('additionalProperties + properties + required', () => { | ||
it('should infer no objects are accepted when required contradicts properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
name: false | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'object containing a contradiction has all constraints relaxed', | ||
examples: [{}, {foo: 1}, {name: 1}, {foo: 1, name: 1}, 'foo'], | ||
input: { | ||
a: { | ||
additionalProperties: false, | ||
properties: { | ||
name: false | ||
}, | ||
required: ['name'], | ||
type: 'object' | ||
}, | ||
b: { | ||
type: 'object' | ||
} | ||
}, | ||
required: ['name'], | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
type: 'object' | ||
}; | ||
output: { | ||
added: {type: ['object']}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjects: JsonSchema = {type: ['object']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjects); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); | ||
}); |
@@ -1,167 +0,164 @@ | ||
import {JsonSchema} from 'json-schema-spec-types'; | ||
import {invokeDiff} from '../support/invoke-diff'; | ||
import {DiffTestCase, registerDiffTestCases} from '../support/register-diff-test-cases'; | ||
describe('diff-schemas type', () => { | ||
it('should find no differences when the schemas are the same', async () => { | ||
const sourceSchema: JsonSchema = {type: 'string'}; | ||
const destinationsSchema: JsonSchema = {type: 'string'}; | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'types are the same', | ||
examples: ['foo', 1], | ||
input: { | ||
a: {type: 'string'}, | ||
b: {type: 'string'} | ||
}, | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
}, | ||
{ | ||
description: 'type as array vs single type that are the same', | ||
examples: ['foo', 1], | ||
input: { | ||
a: {type: 'string'}, | ||
b: {type: ['string']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
}, | ||
{ | ||
description: 'type number is changed', | ||
examples: ['foo', 1, true], | ||
input: { | ||
a: {type: ['string', 'number']}, | ||
b: {type: ['string']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['number']} | ||
} | ||
}, | ||
{ | ||
description: 'type array is changed', | ||
examples: ['foo', [], 1], | ||
input: { | ||
a: {type: ['string', 'array']}, | ||
b: {type: ['string']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['array']} | ||
} | ||
}, | ||
{ | ||
description: 'type boolean is changed', | ||
examples: ['foo', true, 1], | ||
input: { | ||
a: {type: ['string', 'boolean']}, | ||
b: {type: ['string']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['boolean']} | ||
} | ||
}, | ||
{ | ||
description: 'type integer is changed', | ||
examples: ['foo', 1, []], | ||
input: { | ||
a: {type: ['string', 'integer']}, | ||
b: {type: ['string']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['integer']} | ||
} | ||
}, | ||
{ | ||
description: 'type null is changed', | ||
examples: ['foo', null, 1], | ||
input: { | ||
a: {type: ['string', 'null']}, | ||
b: {type: ['string']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['null']} | ||
} | ||
}, | ||
{ | ||
description: 'type object is changed', | ||
examples: ['foo', {}, 1], | ||
input: { | ||
a: {type: ['string', 'object']}, | ||
b: {type: ['string']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['object']} | ||
} | ||
}, | ||
{ | ||
description: 'type string is changed', | ||
examples: [1, 'foo', true], | ||
input: { | ||
a: {type: ['number', 'string']}, | ||
b: {type: ['number']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['string']} | ||
} | ||
}, | ||
{ | ||
description: 'type added and removed', | ||
examples: [1, 'foo', true], | ||
input: { | ||
a: {type: 'number'}, | ||
b: {type: 'string'} | ||
}, | ||
output: { | ||
added: {type: ['string']}, | ||
removed: {type: ['number']} | ||
} | ||
}, | ||
{ | ||
description: 'multiple types added and removed', | ||
examples: [1, 'foo', true, []], | ||
input: { | ||
a: {type: ['number', 'string', 'boolean']}, | ||
b: {type: ['number']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['boolean', 'string']} | ||
} | ||
}, | ||
{ | ||
description: 'empty schemas', | ||
examples: [1, 'foo'], | ||
input: { | ||
a: {}, | ||
b: {} | ||
}, | ||
output: { | ||
added: false, | ||
removed: false | ||
} | ||
}, | ||
{ | ||
description: 'empty schema vs list of types', | ||
examples: [1, 1.1, {}, null, true, [], 'foo'], | ||
input: { | ||
a: {}, | ||
b: {type: ['integer', 'number', 'object', 'null', 'boolean', 'array']} | ||
}, | ||
output: { | ||
added: false, | ||
removed: {type: ['string']} | ||
} | ||
} | ||
]; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find no differences when the schemas are equivalent', async () => { | ||
const sourceSchema: JsonSchema = {type: 'string'}; | ||
const destinationsSchema: JsonSchema = {type: ['string']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find a remove type difference when a type is removed', async () => { | ||
const sourceSchema: JsonSchema = {type: ['string', 'number']}; | ||
const destinationsSchema: JsonSchema = {type: ['string']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allNumbers: JsonSchema = {type: ['number']}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allNumbers); | ||
}); | ||
it('should find an add type difference when an array is added', async () => { | ||
const sourceSchema: JsonSchema = {type: 'number'}; | ||
const destinationsSchema: JsonSchema = {type: ['number', 'array']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allArrays: JsonSchema = {type: ['array']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allArrays); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find an add type difference when a boolean is added', async () => { | ||
const sourceSchema: JsonSchema = {type: 'number'}; | ||
const destinationsSchema: JsonSchema = {type: ['number', 'boolean']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allBooleans: JsonSchema = {type: ['boolean']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allBooleans); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find an add type difference when an integer is added', async () => { | ||
const sourceSchema: JsonSchema = {type: 'number'}; | ||
const destinationsSchema: JsonSchema = {type: ['number', 'integer']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allIntegers: JsonSchema = {type: ['integer']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allIntegers); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find an add type difference when a null is added', async () => { | ||
const sourceSchema: JsonSchema = {type: 'number'}; | ||
const destinationsSchema: JsonSchema = {type: ['number', 'null']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allNulls: JsonSchema = {type: ['null']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allNulls); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find an add type difference when a number is added', async () => { | ||
const sourceSchema: JsonSchema = {type: 'string'}; | ||
const destinationsSchema: JsonSchema = {type: ['string', 'number']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allNumbers: JsonSchema = {type: ['number']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allNumbers); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find an add type difference when an object is added', async () => { | ||
const sourceSchema: JsonSchema = {type: 'number'}; | ||
const destinationsSchema: JsonSchema = {type: ['number', 'object']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allObjects: JsonSchema = {type: ['object']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjects); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find an add type difference when a string is added', async () => { | ||
const sourceSchema: JsonSchema = {type: 'number'}; | ||
const destinationsSchema: JsonSchema = {type: ['number', 'string']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allStrings: JsonSchema = {type: ['string']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allStrings); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find an add and remove difference when a type is changed ', async () => { | ||
const sourceSchema: JsonSchema = {type: 'number'}; | ||
const destinationsSchema: JsonSchema = {type: 'string'}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allStrings: JsonSchema = {type: ['string']}; | ||
const allNumbers: JsonSchema = {type: ['number']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allStrings); | ||
expect(diffResult.removedJsonSchema).toEqual(allNumbers); | ||
}); | ||
it('should find no differences when given two empty schemas', async () => { | ||
const sourceSchema: JsonSchema = {}; | ||
const destinationsSchema: JsonSchema = {}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find multiple differences when multiple types are removed', async () => { | ||
const sourceSchema: JsonSchema = {type: ['number', 'string', 'boolean']}; | ||
const destinationsSchema: JsonSchema = {type: ['number']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allBooleansAndStrings: JsonSchema = {type: ['boolean', 'string']}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allBooleansAndStrings); | ||
}); | ||
it('should find multiple differences when multiple types are added', async () => { | ||
const sourceSchema: JsonSchema = {type: ['number']}; | ||
const destinationsSchema: JsonSchema = {type: ['number', 'string', 'boolean']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allBooleansAndStrings: JsonSchema = {type: ['boolean', 'string']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allBooleansAndStrings); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find removed differences when there was no type and now there is', async () => { | ||
const sourceSchema: JsonSchema = {}; | ||
const destinationsSchema: JsonSchema = {type: ['integer', 'number', 'object', 'null', 'boolean', 'array']}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationsSchema); | ||
const allStrings: JsonSchema = {type: ['string']}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allStrings); | ||
}); | ||
registerDiffTestCases(testCases); | ||
}); |
import {JsonSchema} from 'json-schema-spec-types'; | ||
import {invokeDiff, invokeDiffAndExpectToFail} from '../support/invoke-diff'; | ||
import {DiffTestCase, registerDiffTestCases} from '../support/register-diff-test-cases'; | ||
@@ -80,2 +81,50 @@ describe('diff-schemas', () => { | ||
}); | ||
describe('type array + type object', () => { | ||
const testCases: DiffTestCase[] = [ | ||
{ | ||
description: 'additionalProperties and items constraints get relaxed to support a new type', | ||
examples: [ | ||
[], [1], ['a'], [1, 'a'], [true], [true, 1], | ||
{}, {a: 1}, {a: 'a'}, {a: 1, b: 1}, {a: 'a', b: 'b'}, {a: 1, b: 'b'}, {a: 1, b: true}, | ||
'foo' | ||
], | ||
input: { | ||
a: { | ||
additionalProperties: {type: ['number']}, | ||
items: {type: 'number'}, | ||
type: ['array', 'object'] | ||
}, | ||
b: { | ||
additionalProperties: {type: ['number', 'string']}, | ||
items: {type: ['number', 'string']}, | ||
type: ['array', 'object'] | ||
} | ||
}, | ||
output: { | ||
added: { | ||
additionalProperties: { | ||
type: ['number', 'string'] | ||
}, | ||
items: { | ||
type: ['number', 'string'] | ||
}, | ||
not: { | ||
additionalProperties: { | ||
type: ['number'] | ||
}, | ||
items: { | ||
type: ['number'] | ||
}, | ||
type: ['array', 'object'] | ||
}, | ||
type: ['array', 'object'] | ||
}, | ||
removed: false | ||
} | ||
} | ||
]; | ||
registerDiffTestCases(testCases); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
221106
6
20
115
4905
2
- Removedjs-combinatorics@^0.5.4
- Removedjs-combinatorics@0.5.5(transitive)