Socket
Socket
Sign inDemoInstall

json-schema-diff

Package Overview
Dependencies
Maintainers
3
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

json-schema-diff - npm Package Compare versions

Comparing version 0.12.0 to 0.12.1

dist/json-schema-diff/common/logger.js

11

CHANGELOG.md

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

21

dist/json-schema-diff/diff-schemas.js

@@ -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);
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc