json-schema-diff
Advanced tools
Comparing version 0.11.0 to 0.12.0
@@ -0,1 +1,19 @@ | ||
<a name="0.12.0"></a> | ||
# [0.12.0](https://bitbucket.org/atlassian/json-schema-diff/compare/0.11.0...0.12.0) (2019-04-09) | ||
### Bug Fixes | ||
* generate a complete difference when schemas with both additionalProperties and properties are changed ([56b0149](https://bitbucket.org/atlassian/json-schema-diff/commits/56b0149)) | ||
### Features | ||
* add support for the allOf keyword ([37513a5](https://bitbucket.org/atlassian/json-schema-diff/commits/37513a5)) | ||
* add support for the maxItems keyword ([770b511](https://bitbucket.org/atlassian/json-schema-diff/commits/770b511)) | ||
* add support for the maxProperties keyword ([dfe643b](https://bitbucket.org/atlassian/json-schema-diff/commits/dfe643b)) | ||
* add support for the minProperties keyword ([a5f428f](https://bitbucket.org/atlassian/json-schema-diff/commits/a5f428f)) | ||
<a name="0.11.0"></a> | ||
@@ -2,0 +20,0 @@ # [0.11.0](https://bitbucket.org/atlassian/json-schema-diff/compare/0.10.0...0.11.0) (2019-03-01) |
@@ -12,8 +12,7 @@ "use strict"; | ||
const RefParser = require("json-schema-ref-parser"); | ||
const util_1 = require("util"); | ||
exports.dereferenceSchema = (schema) => __awaiter(this, void 0, void 0, function* () { | ||
const refParser = new RefParser(); | ||
return util_1.isBoolean(schema) | ||
return typeof schema === 'boolean' | ||
? schema | ||
: yield refParser.dereference(schema, { dereference: { circular: false } }); | ||
}); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const util_1 = require("util"); | ||
const create_json_set_1 = require("./set-factories/create-json-set"); | ||
const set_1 = require("./set/set"); | ||
const keyword_defaults_1 = require("./set/keyword-defaults"); | ||
const parseSchemaProperties = (schemaProperties = {}) => { | ||
@@ -16,3 +15,3 @@ const objectSetProperties = {}; | ||
if (!type) { | ||
return set_1.allSchemaTypes; | ||
return keyword_defaults_1.defaultTypes; | ||
} | ||
@@ -24,22 +23,27 @@ if (typeof type === 'string') { | ||
}; | ||
const parseRequiredKeyword = (schema) => schema.required || []; | ||
const generateDefaultMinPropertiesKeyword = () => 0; | ||
const generateDefaultMaxItemsKeyword = () => Infinity; | ||
const parseMinItemsKeyword = (schema) => schema.minItems || 0; | ||
const parseCoreSchemaMetaSchema = (schema) => create_json_set_1.createSomeJsonSet({ | ||
const parseRequiredKeyword = (required) => required || keyword_defaults_1.defaultRequired; | ||
const parseNumericKeyword = (keywordValue, defaultValue) => typeof keywordValue === 'number' ? keywordValue : defaultValue; | ||
const parseTypeKeywords = (schema) => create_json_set_1.createSomeJsonSet({ | ||
additionalProperties: parseSchemaOrUndefinedAsJsonSet(schema.additionalProperties), | ||
items: parseSchemaOrUndefinedAsJsonSet(schema.items), | ||
maxItems: generateDefaultMaxItemsKeyword(), | ||
minItems: parseMinItemsKeyword(schema), | ||
minProperties: generateDefaultMinPropertiesKeyword(), | ||
maxItems: parseNumericKeyword(schema.maxItems, keyword_defaults_1.defaultMaxItems), | ||
maxProperties: parseNumericKeyword(schema.maxProperties, keyword_defaults_1.defaultMaxProperties), | ||
minItems: parseNumericKeyword(schema.minItems, keyword_defaults_1.defaultMinItems), | ||
minProperties: parseNumericKeyword(schema.minProperties, keyword_defaults_1.defaultMinProperties), | ||
properties: parseSchemaProperties(schema.properties), | ||
required: parseRequiredKeyword(schema), | ||
required: parseRequiredKeyword(schema.required), | ||
type: parseType(schema.type) | ||
}); | ||
const parseBooleanLogicKeywords = (schema) => (schema.allOf || []).map(parseSchemaOrUndefinedAsJsonSet); | ||
const parseCoreSchemaMetaSchema = (schema) => { | ||
const typeKeywordsSet = parseTypeKeywords(schema); | ||
const booleanLogicKeywordSets = parseBooleanLogicKeywords(schema); | ||
return booleanLogicKeywordSets.reduce((accumulator, set) => accumulator.intersect(set), typeKeywordsSet); | ||
}; | ||
const parseBooleanSchema = (schema) => { | ||
const allowsAllJsonValues = util_1.isUndefined(schema) ? true : schema; | ||
const allowsAllJsonValues = schema === undefined ? true : schema; | ||
return allowsAllJsonValues ? create_json_set_1.createAllJsonSet() : create_json_set_1.createEmptyJsonSet(); | ||
}; | ||
const parseSchemaOrUndefinedAsJsonSet = (schema) => { | ||
return (util_1.isBoolean(schema) || util_1.isUndefined(schema)) | ||
return (typeof schema === 'boolean' || schema === undefined) | ||
? parseBooleanSchema(schema) | ||
@@ -46,0 +50,0 @@ : parseCoreSchemaMetaSchema(schema); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const keyword_defaults_1 = require("../set/keyword-defaults"); | ||
const set_of_subsets_1 = require("../set/set-of-subsets"); | ||
const array_subset_1 = require("../set/subset/array-subset"); | ||
const is_type_supported_1 = require("./is-type-supported"); | ||
const supportsAllArrays = (arraySetParsedKeywords) => arraySetParsedKeywords.items.type === 'all' && arraySetParsedKeywords.minItems === 0; | ||
const supportsAllArrays = (arraySetParsedKeywords) => arraySetParsedKeywords.items.type === 'all' | ||
&& arraySetParsedKeywords.minItems === keyword_defaults_1.defaultMinItems | ||
&& arraySetParsedKeywords.maxItems === keyword_defaults_1.defaultMaxItems; | ||
const createArraySubset = (arraySetParsedKeywords) => { | ||
@@ -8,0 +11,0 @@ if (!is_type_supported_1.isTypeSupported(arraySetParsedKeywords.type, 'array')) { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const _ = require("lodash"); | ||
const keyword_defaults_1 = require("../set/keyword-defaults"); | ||
const set_of_subsets_1 = require("../set/set-of-subsets"); | ||
@@ -7,9 +9,10 @@ const object_subset_1 = require("../set/subset/object-subset"); | ||
const supportsAllObjects = (objectSetParsedKeywords) => { | ||
// TODO: This should look at the minProperties keyword, but we need minProperties support to do that | ||
const everyPropertyIsAll = Object | ||
.keys(objectSetParsedKeywords.properties) | ||
// tslint:disable:cyclomatic-complexity | ||
const everyPropertyIsAll = Object.keys(objectSetParsedKeywords.properties) | ||
.every((propertyName) => objectSetParsedKeywords.properties[propertyName].type === 'all'); | ||
return everyPropertyIsAll | ||
&& objectSetParsedKeywords.required.length === 0 | ||
&& objectSetParsedKeywords.additionalProperties.type === 'all'; | ||
&& _.isEqual(objectSetParsedKeywords.required, keyword_defaults_1.defaultRequired) | ||
&& objectSetParsedKeywords.additionalProperties.type === 'all' | ||
&& objectSetParsedKeywords.minProperties === keyword_defaults_1.defaultMinProperties | ||
&& objectSetParsedKeywords.maxProperties === keyword_defaults_1.defaultMaxProperties; | ||
}; | ||
@@ -23,8 +26,3 @@ const createObjectSubset = (objectSetParsedKeywords) => { | ||
} | ||
return object_subset_1.createObjectSubsetFromConfig({ | ||
additionalProperties: objectSetParsedKeywords.additionalProperties, | ||
minProperties: objectSetParsedKeywords.minProperties, | ||
properties: objectSetParsedKeywords.properties, | ||
required: objectSetParsedKeywords.required | ||
}); | ||
return object_subset_1.createObjectSubsetFromConfig(objectSetParsedKeywords); | ||
}; | ||
@@ -31,0 +29,0 @@ exports.createObjectSet = (objectSetParsedKeywords) => { |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const _ = require("lodash"); | ||
const keyword_defaults_1 = require("../keyword-defaults"); | ||
const omitDefaultAdditionalProperties = (schema) => { | ||
@@ -10,13 +11,16 @@ return schema.additionalProperties === true | ||
const omitEmptyProperties = (schema) => { | ||
return schema.properties && Object.keys(schema.properties).length > 0 | ||
? schema | ||
: _.omit(schema, ['properties']); | ||
return _.isEqual(schema.properties, keyword_defaults_1.defaultProperties) ? _.omit(schema, ['properties']) : schema; | ||
}; | ||
const omitEmptyRequired = (schema) => { | ||
return schema.required && schema.required.length > 0 | ||
? schema | ||
: _.omit(schema, ['required']); | ||
return _.isEqual(schema.required, keyword_defaults_1.defaultRequired) | ||
? _.omit(schema, ['required']) | ||
: schema; | ||
}; | ||
const omitDefaultMaxProperties = (schema) => { | ||
return schema.maxProperties === keyword_defaults_1.defaultMaxProperties | ||
? _.omit(schema, ['maxProperties']) | ||
: schema; | ||
}; | ||
const omitDefaultMinProperties = (schema) => { | ||
return schema.minProperties === 0 | ||
return schema.minProperties === keyword_defaults_1.defaultMinProperties | ||
? _.omit(schema, ['minProperties']) | ||
@@ -31,3 +35,3 @@ : schema; | ||
const omitDefaultMinItems = (schema) => { | ||
return schema.minItems === 0 | ||
return schema.minItems === keyword_defaults_1.defaultMinItems | ||
? _.omit(schema, ['minItems']) | ||
@@ -37,3 +41,3 @@ : schema; | ||
const omitDefaultMaxItems = (schema) => { | ||
return schema.maxItems === Infinity | ||
return schema.maxItems === keyword_defaults_1.defaultMaxItems | ||
? _.omit(schema, ['maxItems']) | ||
@@ -47,2 +51,2 @@ : schema; | ||
}; | ||
exports.omitDefaults = (originalSchema) => omitDefaultsFromAnyOfSchema(omitDefaultAdditionalProperties(omitEmptyProperties(omitEmptyRequired(omitDefaultMinProperties(omitDefaultMinItems(omitDefaultMaxItems(omitDefaultItems(originalSchema)))))))); | ||
exports.omitDefaults = (originalSchema) => omitDefaultsFromAnyOfSchema(omitDefaultAdditionalProperties(omitEmptyProperties(omitEmptyRequired(omitDefaultMaxProperties(omitDefaultMinProperties(omitDefaultMinItems(omitDefaultMaxItems(omitDefaultItems(originalSchema))))))))); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.allSchemaTypes = ['array', 'boolean', 'integer', 'null', 'number', 'object', 'string']; | ||
exports.isCoreRepresentationJsonSchema = (schema) => !!schema; |
"use strict"; | ||
// tslint:disable:max-classes-per-file | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const json_set_1 = require("../json-set"); | ||
const array_has_contradictions_1 = require("./array-subset/array-has-contradictions"); | ||
const array_subset_config_has_contradictions_1 = require("./array-subset/array-subset-config-has-contradictions"); | ||
const complement_array_subset_config_1 = require("./array-subset/complement-array-subset-config"); | ||
const intersect_array_subset_config_1 = require("./array-subset/intersect-array-subset-config"); | ||
const simplify_array_subset_config_1 = require("./array-subset/simplify-array-subset-config"); | ||
const subset_1 = require("./subset"); | ||
@@ -14,6 +16,3 @@ class SomeArraySubset { | ||
complement() { | ||
// TODO: This should complement the maxItems keyword, however cannot be tested without maxItems support | ||
const complementedItems = this.complementItems(); | ||
const complementedMinItems = this.complementMinItems(); | ||
return [complementedItems, complementedMinItems]; | ||
return complement_array_subset_config_1.complementArraySubsetConfig(this.config).map(exports.createArraySubsetFromConfig); | ||
} | ||
@@ -24,7 +23,3 @@ intersect(other) { | ||
intersectWithSome(other) { | ||
return exports.createArraySubsetFromConfig({ | ||
items: this.config.items.intersect(other.config.items), | ||
maxItems: this.config.maxItems, | ||
minItems: this.intersectMinItems(other) | ||
}); | ||
return exports.createArraySubsetFromConfig(intersect_array_subset_config_1.intersectArraySubsetConfig(this.config, other.config)); | ||
} | ||
@@ -39,28 +34,10 @@ toJsonSchema() { | ||
} | ||
complementItems() { | ||
return exports.createArraySubsetFromConfig({ | ||
items: this.config.items.complement(), | ||
maxItems: Infinity, | ||
minItems: 1 | ||
}); | ||
} | ||
complementMinItems() { | ||
return exports.createArraySubsetFromConfig({ | ||
items: json_set_1.allJsonSet, | ||
maxItems: this.config.minItems - 1, | ||
minItems: 0 | ||
}); | ||
} | ||
intersectMinItems(other) { | ||
return Math.max(this.config.minItems, other.config.minItems); | ||
} | ||
} | ||
const simplifyArraySubsetConfig = (config) => config.maxItems === 0 ? Object.assign({}, config, { items: json_set_1.allJsonSet }) : config; | ||
exports.allArraySubset = new subset_1.AllSubset('array'); | ||
exports.emptyArraySubset = new subset_1.EmptySubset('array'); | ||
exports.createArraySubsetFromConfig = (config) => { | ||
const simplifiedConfig = simplifyArraySubsetConfig(config); | ||
return array_has_contradictions_1.arrayHasContradictions(simplifiedConfig) | ||
const simplifiedConfig = simplify_array_subset_config_1.simplifyArraySubsetConfig(config); | ||
return array_subset_config_has_contradictions_1.arraySubsetConfigHasContradictions(simplifiedConfig) | ||
? exports.emptyArraySubset | ||
: new SomeArraySubset(simplifiedConfig); | ||
}; |
"use strict"; | ||
// tslint:disable:max-classes-per-file | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const json_set_1 = require("../json-set"); | ||
const object_has_contradictions_1 = require("./object-subset/object-has-contradictions"); | ||
const unique_1 = require("./object-subset/unique"); | ||
const complement_object_subset_config_1 = require("./object-subset/complement-object-subset-config"); | ||
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"); | ||
const simplify_object_subset_config_1 = require("./object-subset/simplify-object-subset-config"); | ||
const subset_1 = require("./subset"); | ||
@@ -18,6 +19,3 @@ class SomeObjectSubset { | ||
complement() { | ||
const complementedProperties = this.complementProperties(); | ||
const complementedAdditionalProperties = this.complementAdditionalProperties(); | ||
const complementedRequiredProperties = this.complementRequiredProperties(); | ||
return [...complementedAdditionalProperties, ...complementedProperties, ...complementedRequiredProperties]; | ||
return complement_object_subset_config_1.complementObjectSubsetConfig(this.config).map(exports.createObjectSubsetFromConfig); | ||
} | ||
@@ -28,8 +26,3 @@ intersect(other) { | ||
intersectWithSome(other) { | ||
return exports.createObjectSubsetFromConfig({ | ||
additionalProperties: this.config.additionalProperties.intersect(other.config.additionalProperties), | ||
minProperties: this.intersectMinProperties(other), | ||
properties: this.intersectProperties(other), | ||
required: this.intersectRequired(other) | ||
}); | ||
return exports.createObjectSubsetFromConfig(intersect_object_subset_config_1.intersectObjectSubsetConfig(this.config, other.config)); | ||
} | ||
@@ -41,2 +34,3 @@ toJsonSchema() { | ||
additionalProperties, | ||
maxProperties: this.config.maxProperties, | ||
minProperties: this.config.minProperties, | ||
@@ -48,81 +42,8 @@ properties, | ||
} | ||
getPropertySet(propertyName) { | ||
return this.config.properties[propertyName] || this.config.additionalProperties; | ||
} | ||
getPropertyNames() { | ||
return Object.keys(this.config.properties); | ||
} | ||
toJsonSchemaMap() { | ||
return this.getPropertyNames().reduce((acc, propertyName) => { | ||
acc[propertyName] = this.getPropertySet(propertyName).toJsonSchema(); | ||
return object_subset_config_1.getPropertyNames(this.config).reduce((acc, propertyName) => { | ||
acc[propertyName] = object_subset_config_1.getPropertySet(this.config, propertyName).toJsonSchema(); | ||
return acc; | ||
}, {}); | ||
} | ||
complementProperties() { | ||
return this.getPropertyNames().map((propertyName) => { | ||
const complementedPropertySchema = this.getPropertySet(propertyName).complement(); | ||
return exports.createObjectSubsetFromConfig({ | ||
additionalProperties: json_set_1.allJsonSet, | ||
// TODO: Untestable today, need: | ||
// not: {properties: {name: {type: 'string'}, minProperties: 1, type: 'object'} -> true | ||
minProperties: 0, | ||
properties: { [propertyName]: complementedPropertySchema }, | ||
required: [propertyName] | ||
}); | ||
}); | ||
} | ||
complementRequiredProperties() { | ||
return this.config.required.map((requiredPropertyName) => exports.createObjectSubsetFromConfig({ | ||
additionalProperties: json_set_1.allJsonSet, | ||
minProperties: 0, | ||
properties: { | ||
[requiredPropertyName]: json_set_1.emptyJsonSet | ||
}, | ||
required: [] | ||
})); | ||
} | ||
complementAdditionalProperties() { | ||
const defaultComplementedAdditionalProperties = [this.complementAdditionalPropertiesWithEmpty()]; | ||
return this.getPropertyNames().length > 0 | ||
? defaultComplementedAdditionalProperties.concat(this.complementAdditionalPropertiesWithRequired()) | ||
: defaultComplementedAdditionalProperties; | ||
} | ||
complementAdditionalPropertiesWithEmpty() { | ||
const propertyNames = this.getPropertyNames(); | ||
const emptyProperties = propertyNames | ||
.reduce((acc, propertyName) => { | ||
acc[propertyName] = json_set_1.emptyJsonSet; | ||
return acc; | ||
}, {}); | ||
return exports.createObjectSubsetFromConfig({ | ||
additionalProperties: this.config.additionalProperties.complement(), | ||
minProperties: 1, | ||
properties: emptyProperties, | ||
required: [] | ||
}); | ||
} | ||
complementAdditionalPropertiesWithRequired() { | ||
return exports.createObjectSubsetFromConfig({ | ||
additionalProperties: this.config.additionalProperties.complement(), | ||
minProperties: 1 + Object.keys(this.config.properties).length, | ||
// TODO: All the tests went green when I made this an empty object, whats up with that? | ||
properties: this.config.properties, | ||
required: Object.keys(this.config.properties) | ||
}); | ||
} | ||
intersectMinProperties(other) { | ||
// TODO: can't be asserted without minProperties support: | ||
// {minProperties: 1, type: 'object'} -> {minProperties: 2, type: 'object'} | ||
return Math.max(this.config.minProperties, other.config.minProperties); | ||
} | ||
intersectProperties(other) { | ||
const allPropertyNames = unique_1.unique(this.getPropertyNames(), other.getPropertyNames()); | ||
return allPropertyNames.reduce((accumulator, propertyName) => { | ||
accumulator[propertyName] = this.getPropertySet(propertyName).intersect(other.getPropertySet(propertyName)); | ||
return accumulator; | ||
}, {}); | ||
} | ||
intersectRequired(other) { | ||
return unique_1.unique(this.config.required, other.config.required); | ||
} | ||
} | ||
@@ -132,5 +53,6 @@ exports.allObjectSubset = new subset_1.AllSubset('object'); | ||
exports.createObjectSubsetFromConfig = (config) => { | ||
return object_has_contradictions_1.objectHasContradictions(config) | ||
const simplifiedConfig = simplify_object_subset_config_1.simplifyObjectSubsetConfig(config); | ||
return object_subset_config_has_contradictions_1.objectSubsetConfigHasContradictions(simplifiedConfig) | ||
? exports.emptyObjectSubset | ||
: new SomeObjectSubset(config); | ||
: new SomeObjectSubset(simplifiedConfig); | ||
}; |
@@ -39,3 +39,3 @@ # Json Schema Diff Supported Keywords | ||
| additionalItems | no | | ||
| maxItems | no | | ||
| maxItems | yes | | ||
| minItems | yes | | ||
@@ -50,4 +50,4 @@ | uniqueItems | no | | ||
|---|---| | ||
| maxProperties | no | | ||
| minProperties | no | | ||
| maxProperties | yes | | ||
| minProperties | yes | | ||
| required | yes | | ||
@@ -60,2 +60,8 @@ | properties | yes | | ||
### 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. | ||
@@ -75,3 +81,3 @@ ## Keywords for Applying Subschemas Conditionally | ||
|---|---| | ||
| allOf | no | | ||
| allOf | yes | | ||
| anyOf | no | | ||
@@ -78,0 +84,0 @@ | oneOf | no | |
import RefParser = require('json-schema-ref-parser'); | ||
import {JsonSchema} from 'json-schema-spec-types'; | ||
import {isBoolean} from 'util'; | ||
export const dereferenceSchema = async (schema: JsonSchema): Promise<JsonSchema> => { | ||
const refParser = new RefParser(); | ||
return isBoolean(schema) | ||
return typeof schema === 'boolean' | ||
? schema | ||
: await refParser.dereference(schema as object, {dereference: {circular: false}}) as JsonSchema; | ||
}; |
import {CoreSchemaMetaSchema, JsonSchema, JsonSchemaMap, SimpleTypes} from 'json-schema-spec-types'; | ||
import {isBoolean, isUndefined} from 'util'; | ||
import {createAllJsonSet, createEmptyJsonSet, createSomeJsonSet} from './set-factories/create-json-set'; | ||
import {allSchemaTypes, ParsedPropertiesKeyword, Set} from './set/set'; | ||
import { | ||
defaultMaxItems, | ||
defaultMaxProperties, | ||
defaultMinItems, | ||
defaultMinProperties, | ||
defaultRequired, | ||
defaultTypes | ||
} from './set/keyword-defaults'; | ||
import {ParsedPropertiesKeyword, Set} from './set/set'; | ||
@@ -18,3 +25,3 @@ const parseSchemaProperties = (schemaProperties: JsonSchemaMap = {}): ParsedPropertiesKeyword => { | ||
if (!type) { | ||
return allSchemaTypes; | ||
return defaultTypes; | ||
} | ||
@@ -29,24 +36,31 @@ | ||
const parseRequiredKeyword = (schema: CoreSchemaMetaSchema): string[] => schema.required || []; | ||
const parseRequiredKeyword = (required: string[] | undefined): string[] => required || defaultRequired; | ||
const generateDefaultMinPropertiesKeyword = (): number => 0; | ||
const parseNumericKeyword = (keywordValue: number | undefined, defaultValue: number): number => | ||
typeof keywordValue === 'number' ? keywordValue : defaultValue; | ||
const generateDefaultMaxItemsKeyword = (): number => Infinity; | ||
const parseMinItemsKeyword = (schema: CoreSchemaMetaSchema): number => schema.minItems || 0; | ||
const parseCoreSchemaMetaSchema = (schema: CoreSchemaMetaSchema): Set<'json'> => | ||
const parseTypeKeywords = (schema: CoreSchemaMetaSchema): Set<'json'> => | ||
createSomeJsonSet({ | ||
additionalProperties: parseSchemaOrUndefinedAsJsonSet(schema.additionalProperties), | ||
items: parseSchemaOrUndefinedAsJsonSet(schema.items), | ||
maxItems: generateDefaultMaxItemsKeyword(), | ||
minItems: parseMinItemsKeyword(schema), | ||
minProperties: generateDefaultMinPropertiesKeyword(), | ||
maxItems: parseNumericKeyword(schema.maxItems, defaultMaxItems), | ||
maxProperties: parseNumericKeyword(schema.maxProperties, defaultMaxProperties), | ||
minItems: parseNumericKeyword(schema.minItems, defaultMinItems), | ||
minProperties: parseNumericKeyword(schema.minProperties, defaultMinProperties), | ||
properties: parseSchemaProperties(schema.properties), | ||
required: parseRequiredKeyword(schema), | ||
required: parseRequiredKeyword(schema.required), | ||
type: parseType(schema.type) | ||
}); | ||
const parseBooleanLogicKeywords = (schema: CoreSchemaMetaSchema): Array<Set<'json'>> => | ||
(schema.allOf || []).map(parseSchemaOrUndefinedAsJsonSet); | ||
const parseCoreSchemaMetaSchema = (schema: CoreSchemaMetaSchema): Set<'json'> => { | ||
const typeKeywordsSet = parseTypeKeywords(schema); | ||
const booleanLogicKeywordSets = parseBooleanLogicKeywords(schema); | ||
return booleanLogicKeywordSets.reduce((accumulator, set) => accumulator.intersect(set), typeKeywordsSet); | ||
}; | ||
const parseBooleanSchema = (schema: boolean | undefined): Set<'json'> => { | ||
const allowsAllJsonValues = isUndefined(schema) ? true : schema; | ||
const allowsAllJsonValues = schema === undefined ? true : schema; | ||
return allowsAllJsonValues ? createAllJsonSet() : createEmptyJsonSet(); | ||
@@ -56,3 +70,3 @@ }; | ||
const parseSchemaOrUndefinedAsJsonSet = (schema: JsonSchema | undefined): Set<'json'> => { | ||
return (isBoolean(schema) || isUndefined(schema)) | ||
return (typeof schema === 'boolean' || schema === undefined) | ||
? parseBooleanSchema(schema) | ||
@@ -59,0 +73,0 @@ : parseCoreSchemaMetaSchema(schema); |
import {SimpleTypes} from 'json-schema-spec-types'; | ||
import {defaultMaxItems, defaultMinItems} from '../set/keyword-defaults'; | ||
import {Set, Subset} from '../set/set'; | ||
@@ -15,3 +16,5 @@ import {SetOfSubsets} from '../set/set-of-subsets'; | ||
const supportsAllArrays = (arraySetParsedKeywords: ArraySetParsedKeywords): boolean => | ||
arraySetParsedKeywords.items.type === 'all' && arraySetParsedKeywords.minItems === 0; | ||
arraySetParsedKeywords.items.type === 'all' | ||
&& arraySetParsedKeywords.minItems === defaultMinItems | ||
&& arraySetParsedKeywords.maxItems === defaultMaxItems; | ||
@@ -18,0 +21,0 @@ const createArraySubset = (arraySetParsedKeywords: ArraySetParsedKeywords): Subset<'array'> => { |
@@ -12,2 +12,3 @@ import {SimpleTypes} from 'json-schema-spec-types'; | ||
maxItems: number; | ||
maxProperties: number; | ||
minItems: number; | ||
@@ -14,0 +15,0 @@ minProperties: number; |
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'; | ||
@@ -9,17 +11,19 @@ import {SetOfSubsets} from '../set/set-of-subsets'; | ||
additionalProperties: Set<'json'>; | ||
type: SimpleTypes[]; | ||
maxProperties: number; | ||
minProperties: number; | ||
properties: ParsedPropertiesKeyword; | ||
required: string[]; | ||
minProperties: number; | ||
type: SimpleTypes[]; | ||
} | ||
const supportsAllObjects = (objectSetParsedKeywords: ObjectSetParsedKeywords): boolean => { | ||
// TODO: This should look at the minProperties keyword, but we need minProperties support to do that | ||
const everyPropertyIsAll = Object | ||
.keys(objectSetParsedKeywords.properties) | ||
// tslint:disable:cyclomatic-complexity | ||
const everyPropertyIsAll = Object.keys(objectSetParsedKeywords.properties) | ||
.every((propertyName) => objectSetParsedKeywords.properties[propertyName].type === 'all'); | ||
return everyPropertyIsAll | ||
&& objectSetParsedKeywords.required.length === 0 | ||
&& objectSetParsedKeywords.additionalProperties.type === 'all'; | ||
&& _.isEqual(objectSetParsedKeywords.required, defaultRequired) | ||
&& objectSetParsedKeywords.additionalProperties.type === 'all' | ||
&& objectSetParsedKeywords.minProperties === defaultMinProperties | ||
&& objectSetParsedKeywords.maxProperties === defaultMaxProperties; | ||
}; | ||
@@ -36,8 +40,3 @@ | ||
return createObjectSubsetFromConfig({ | ||
additionalProperties: objectSetParsedKeywords.additionalProperties, | ||
minProperties: objectSetParsedKeywords.minProperties, | ||
properties: objectSetParsedKeywords.properties, | ||
required: objectSetParsedKeywords.required | ||
}); | ||
return createObjectSubsetFromConfig(objectSetParsedKeywords); | ||
}; | ||
@@ -44,0 +43,0 @@ |
import * as _ from 'lodash'; | ||
import { | ||
defaultMaxItems, | ||
defaultMaxProperties, | ||
defaultMinItems, | ||
defaultMinProperties, | ||
defaultProperties, | ||
defaultRequired | ||
} from '../keyword-defaults'; | ||
import {CoreRepresentationJsonSchema, RepresentationJsonSchema} from '../set'; | ||
@@ -11,15 +19,19 @@ | ||
const omitEmptyProperties = (schema: CoreRepresentationJsonSchema): CoreRepresentationJsonSchema => { | ||
return schema.properties && Object.keys(schema.properties).length > 0 | ||
? schema | ||
: _.omit(schema, ['properties']); | ||
return _.isEqual(schema.properties, defaultProperties) ? _.omit(schema, ['properties']) : schema; | ||
}; | ||
const omitEmptyRequired = (schema: CoreRepresentationJsonSchema): CoreRepresentationJsonSchema => { | ||
return schema.required && schema.required.length > 0 | ||
? schema | ||
: _.omit(schema, ['required']); | ||
return _.isEqual(schema.required, defaultRequired) | ||
? _.omit(schema, ['required']) | ||
: schema; | ||
}; | ||
const omitDefaultMaxProperties = (schema: CoreRepresentationJsonSchema): CoreRepresentationJsonSchema => { | ||
return schema.maxProperties === defaultMaxProperties | ||
? _.omit(schema, ['maxProperties']) | ||
: schema; | ||
}; | ||
const omitDefaultMinProperties = (schema: CoreRepresentationJsonSchema): CoreRepresentationJsonSchema => { | ||
return schema.minProperties === 0 | ||
return schema.minProperties === defaultMinProperties | ||
? _.omit(schema, ['minProperties']) | ||
@@ -36,3 +48,3 @@ : schema; | ||
const omitDefaultMinItems = (schema: CoreRepresentationJsonSchema): CoreRepresentationJsonSchema => { | ||
return schema.minItems === 0 | ||
return schema.minItems === defaultMinItems | ||
? _.omit(schema, ['minItems']) | ||
@@ -43,3 +55,3 @@ : schema; | ||
const omitDefaultMaxItems = (schema: CoreRepresentationJsonSchema): CoreRepresentationJsonSchema => { | ||
return schema.maxItems === Infinity | ||
return schema.maxItems === defaultMaxItems | ||
? _.omit(schema, ['maxItems']) | ||
@@ -64,5 +76,6 @@ : schema; | ||
omitEmptyRequired( | ||
omitDefaultMinProperties( | ||
omitDefaultMinItems( | ||
omitDefaultMaxItems( | ||
omitDefaultItems(originalSchema)))))))); | ||
omitDefaultMaxProperties( | ||
omitDefaultMinProperties( | ||
omitDefaultMinItems( | ||
omitDefaultMaxItems( | ||
omitDefaultItems(originalSchema))))))))); |
@@ -37,5 +37,3 @@ import {CoreSchemaMetaSchema, SimpleTypes} from 'json-schema-spec-types'; | ||
export const allSchemaTypes: SimpleTypes[] = ['array', 'boolean', 'integer', 'null', 'number', 'object', 'string']; | ||
export const isCoreRepresentationJsonSchema = | ||
(schema: RepresentationJsonSchema): schema is CoreRepresentationJsonSchema => !!schema; |
// tslint:disable:max-classes-per-file | ||
import {allJsonSet} from '../json-set'; | ||
import {RepresentationJsonSchema, Set, Subset} from '../set'; | ||
import {arrayHasContradictions} from './array-subset/array-has-contradictions'; | ||
import {RepresentationJsonSchema, Subset} from '../set'; | ||
import {ArraySubsetConfig} from './array-subset/array-subset-config'; | ||
import {arraySubsetConfigHasContradictions} from './array-subset/array-subset-config-has-contradictions'; | ||
import {complementArraySubsetConfig} from './array-subset/complement-array-subset-config'; | ||
import {intersectArraySubsetConfig} from './array-subset/intersect-array-subset-config'; | ||
import {simplifyArraySubsetConfig} from './array-subset/simplify-array-subset-config'; | ||
import {AllSubset, EmptySubset} from './subset'; | ||
export interface SomeArraySubsetConfig { | ||
items: Set<'json'>; | ||
maxItems: number; | ||
minItems: number; | ||
} | ||
class SomeArraySubset implements Subset<'array'> { | ||
@@ -18,10 +15,7 @@ public readonly setType = 'array'; | ||
public constructor(private readonly config: SomeArraySubsetConfig) { | ||
public constructor(private readonly config: ArraySubsetConfig) { | ||
} | ||
public complement(): Array<Subset<'array'>> { | ||
// TODO: This should complement the maxItems keyword, however cannot be tested without maxItems support | ||
const complementedItems = this.complementItems(); | ||
const complementedMinItems = this.complementMinItems(); | ||
return [complementedItems, complementedMinItems]; | ||
return complementArraySubsetConfig(this.config).map(createArraySubsetFromConfig); | ||
} | ||
@@ -34,7 +28,3 @@ | ||
public intersectWithSome(other: SomeArraySubset): Subset<'array'> { | ||
return createArraySubsetFromConfig({ | ||
items: this.config.items.intersect(other.config.items), | ||
maxItems: this.config.maxItems, | ||
minItems: this.intersectMinItems(other) | ||
}); | ||
return createArraySubsetFromConfig(intersectArraySubsetConfig(this.config, other.config)); | ||
} | ||
@@ -50,36 +40,13 @@ | ||
} | ||
private complementItems(): Subset<'array'> { | ||
return createArraySubsetFromConfig({ | ||
items: this.config.items.complement(), | ||
maxItems: Infinity, | ||
minItems: 1 | ||
}); | ||
} | ||
private complementMinItems(): Subset<'array'> { | ||
return createArraySubsetFromConfig({ | ||
items: allJsonSet, | ||
maxItems: this.config.minItems - 1, | ||
minItems: 0 | ||
}); | ||
} | ||
private intersectMinItems(other: SomeArraySubset): number { | ||
return Math.max(this.config.minItems, other.config.minItems); | ||
} | ||
} | ||
const simplifyArraySubsetConfig = (config: SomeArraySubsetConfig): SomeArraySubsetConfig => | ||
config.maxItems === 0 ? {...config, items: allJsonSet} : config; | ||
export const allArraySubset = new AllSubset('array'); | ||
export const emptyArraySubset = new EmptySubset('array'); | ||
export const createArraySubsetFromConfig = (config: SomeArraySubsetConfig): Subset<'array'> => { | ||
export const createArraySubsetFromConfig = (config: ArraySubsetConfig): Subset<'array'> => { | ||
const simplifiedConfig = simplifyArraySubsetConfig(config); | ||
return arrayHasContradictions(simplifiedConfig) | ||
return arraySubsetConfigHasContradictions(simplifiedConfig) | ||
? emptyArraySubset | ||
: new SomeArraySubset(simplifiedConfig); | ||
}; |
@@ -1,16 +0,9 @@ | ||
// tslint:disable:max-classes-per-file | ||
import {allJsonSet, emptyJsonSet} from '../json-set'; | ||
import {ParsedPropertiesKeyword, RepresentationJsonSchema, SchemaProperties, Set, Subset} from '../set'; | ||
import {objectHasContradictions} from './object-subset/object-has-contradictions'; | ||
import {unique} from './object-subset/unique'; | ||
import {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 {objectSubsetConfigHasContradictions} from './object-subset/object-subset-config-has-contradictions'; | ||
import {simplifyObjectSubsetConfig} from './object-subset/simplify-object-subset-config'; | ||
import {AllSubset, EmptySubset} from './subset'; | ||
export interface SomeObjectSubsetConfig { | ||
properties: ParsedPropertiesKeyword; | ||
additionalProperties: Set<'json'>; | ||
minProperties: number; | ||
required: string[]; | ||
} | ||
class SomeObjectSubset implements Subset<'object'> { | ||
@@ -20,3 +13,3 @@ public readonly setType = 'object'; | ||
public constructor(private readonly config: SomeObjectSubsetConfig) { | ||
public constructor(private readonly config: ObjectSubsetConfig) { | ||
} | ||
@@ -29,6 +22,3 @@ | ||
public complement(): Array<Subset<'object'>> { | ||
const complementedProperties = this.complementProperties(); | ||
const complementedAdditionalProperties = this.complementAdditionalProperties(); | ||
const complementedRequiredProperties = this.complementRequiredProperties(); | ||
return [...complementedAdditionalProperties, ...complementedProperties, ...complementedRequiredProperties]; | ||
return complementObjectSubsetConfig(this.config).map(createObjectSubsetFromConfig); | ||
} | ||
@@ -41,8 +31,3 @@ | ||
public intersectWithSome(other: SomeObjectSubset): Subset<'object'> { | ||
return createObjectSubsetFromConfig({ | ||
additionalProperties: this.config.additionalProperties.intersect(other.config.additionalProperties), | ||
minProperties: this.intersectMinProperties(other), | ||
properties: this.intersectProperties(other), | ||
required: this.intersectRequired(other) | ||
}); | ||
return createObjectSubsetFromConfig(intersectObjectSubsetConfig(this.config, other.config)); | ||
} | ||
@@ -55,2 +40,3 @@ | ||
additionalProperties, | ||
maxProperties: this.config.maxProperties, | ||
minProperties: this.config.minProperties, | ||
@@ -63,99 +49,8 @@ properties, | ||
private getPropertySet(propertyName: string): Set<'json'> { | ||
return this.config.properties[propertyName] || this.config.additionalProperties; | ||
} | ||
private getPropertyNames(): string[] { | ||
return Object.keys(this.config.properties); | ||
} | ||
private toJsonSchemaMap(): SchemaProperties { | ||
return this.getPropertyNames().reduce<SchemaProperties>((acc, propertyName) => { | ||
acc[propertyName] = this.getPropertySet(propertyName).toJsonSchema(); | ||
return getPropertyNames(this.config).reduce<SchemaProperties>((acc, propertyName) => { | ||
acc[propertyName] = getPropertySet(this.config, propertyName).toJsonSchema(); | ||
return acc; | ||
}, {}); | ||
} | ||
private complementProperties(): Array<Subset<'object'>> { | ||
return this.getPropertyNames().map((propertyName) => { | ||
const complementedPropertySchema = this.getPropertySet(propertyName).complement(); | ||
return createObjectSubsetFromConfig({ | ||
additionalProperties: allJsonSet, | ||
// TODO: Untestable today, need: | ||
// not: {properties: {name: {type: 'string'}, minProperties: 1, type: 'object'} -> true | ||
minProperties: 0, | ||
properties: {[propertyName]: complementedPropertySchema}, | ||
required: [propertyName] | ||
} | ||
); | ||
}); | ||
} | ||
private complementRequiredProperties(): Array<Subset<'object'>> { | ||
return this.config.required.map((requiredPropertyName) => | ||
createObjectSubsetFromConfig({ | ||
additionalProperties: allJsonSet, | ||
minProperties: 0, | ||
properties: { | ||
[requiredPropertyName]: emptyJsonSet | ||
}, | ||
required: [] | ||
})); | ||
} | ||
private complementAdditionalProperties(): Array<Subset<'object'>> { | ||
const defaultComplementedAdditionalProperties = [this.complementAdditionalPropertiesWithEmpty()]; | ||
return this.getPropertyNames().length > 0 | ||
? defaultComplementedAdditionalProperties.concat(this.complementAdditionalPropertiesWithRequired()) | ||
: defaultComplementedAdditionalProperties; | ||
} | ||
private complementAdditionalPropertiesWithEmpty(): Subset<'object'> { | ||
const propertyNames = this.getPropertyNames(); | ||
const emptyProperties = propertyNames | ||
.reduce<ParsedPropertiesKeyword>((acc, propertyName) => { | ||
acc[propertyName] = emptyJsonSet; | ||
return acc; | ||
}, {}); | ||
return createObjectSubsetFromConfig({ | ||
additionalProperties: this.config.additionalProperties.complement(), | ||
minProperties: 1, | ||
properties: emptyProperties, | ||
required: [] | ||
}); | ||
} | ||
private complementAdditionalPropertiesWithRequired(): Subset<'object'> { | ||
return createObjectSubsetFromConfig({ | ||
additionalProperties: this.config.additionalProperties.complement(), | ||
minProperties: 1 + Object.keys(this.config.properties).length, | ||
// TODO: All the tests went green when I made this an empty object, whats up with that? | ||
properties: this.config.properties, | ||
required: Object.keys(this.config.properties) | ||
}); | ||
} | ||
private intersectMinProperties(other: SomeObjectSubset): number { | ||
// TODO: can't be asserted without minProperties support: | ||
// {minProperties: 1, type: 'object'} -> {minProperties: 2, type: 'object'} | ||
return Math.max(this.config.minProperties, other.config.minProperties); | ||
} | ||
private intersectProperties(other: SomeObjectSubset): ParsedPropertiesKeyword { | ||
const allPropertyNames = unique(this.getPropertyNames(), other.getPropertyNames()); | ||
return allPropertyNames.reduce<ParsedPropertiesKeyword>((accumulator, propertyName) => { | ||
accumulator[propertyName] = this.getPropertySet(propertyName).intersect(other.getPropertySet(propertyName)); | ||
return accumulator; | ||
}, {}); | ||
} | ||
private intersectRequired(other: SomeObjectSubset): string[] { | ||
return unique(this.config.required, other.config.required); | ||
} | ||
} | ||
@@ -165,6 +60,8 @@ | ||
export const emptyObjectSubset = new EmptySubset('object'); | ||
export const createObjectSubsetFromConfig = (config: SomeObjectSubsetConfig): Subset<'object'> => { | ||
return objectHasContradictions(config) | ||
export const createObjectSubsetFromConfig = (config: ObjectSubsetConfig): Subset<'object'> => { | ||
const simplifiedConfig = simplifyObjectSubsetConfig(config); | ||
return objectSubsetConfigHasContradictions(simplifiedConfig) | ||
? emptyObjectSubset | ||
: new SomeObjectSubset(config); | ||
: new SomeObjectSubset(simplifiedConfig); | ||
}; |
{ | ||
"name": "json-schema-diff", | ||
"version": "0.11.0", | ||
"version": "0.12.0", | ||
"description": "A language agnostic CLI tool and nodejs api to identify differences between two json schema files.", | ||
@@ -38,6 +38,7 @@ "bin": { | ||
"devDependencies": { | ||
"@types/jasmine": "^2.8.6", | ||
"@types/json-schema": "^6.0.1", | ||
"@types/jasmine": "^3.3.9", | ||
"@types/js-combinatorics": "^0.5.31", | ||
"@types/json-schema": "^7.0.3", | ||
"@types/lodash": "^4.14.108", | ||
"@types/node": "^9.6.5", | ||
"@types/node": "^11.9.6", | ||
"@types/verror": "^1.10.3", | ||
@@ -57,4 +58,4 @@ "conventional-changelog-lint": "^2.1.1", | ||
"run-sequence": "^2.2.1", | ||
"tslint": "5.9.1", | ||
"typescript": "2.8.3" | ||
"tslint": "5.13.1", | ||
"typescript": "3.3.3333" | ||
}, | ||
@@ -65,3 +66,4 @@ "types": "lib/api-types.d.ts", | ||
"commander": "^2.15.1", | ||
"json-schema-ref-parser": "^5.0.3", | ||
"js-combinatorics": "^0.5.4", | ||
"json-schema-ref-parser": "^6.1.0", | ||
"json-schema-spec-types": "^0.1.2", | ||
@@ -68,0 +70,0 @@ "lodash": "^4.17.10", |
@@ -5,4 +5,4 @@ # Json Schema Diff | ||
## Requirements | ||
- nodejs 6.x or higher (tested using 6.x, 8.x and 9.x) | ||
- npm 2.x or higher (tested using 2.x, 3.x and 5x) | ||
- nodejs 6.x or higher (tested using 6.x, 8.x, 10.x and 11.x) | ||
- npm 3.x or higher (tested using 3.x and 6.x) | ||
@@ -9,0 +9,0 @@ ## Installation |
@@ -178,10 +178,9 @@ import {JsonSchema, SimpleTypes} from 'json-schema-spec-types'; | ||
it('should return a remove difference representing the empty array value', async () => { | ||
it('should find a removed difference with correct minItems-maxItems constraint range', async () => { | ||
const sourceSchema: JsonSchema = { | ||
items: {type: 'string'}, | ||
minItems: 2, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
items: {type: 'string'}, | ||
minItems: 1, | ||
minItems: 4, | ||
type: 'array' | ||
@@ -193,3 +192,4 @@ }; | ||
const arraysWithNoItems: JsonSchema = { | ||
maxItems: 0, | ||
maxItems: 3, | ||
minItems: 2, | ||
type: ['array'] | ||
@@ -201,9 +201,8 @@ }; | ||
it('should find a removed difference with correct minItems-maxItems constraint range', async () => { | ||
it('should represent an array with no items in a consistent way', async () => { | ||
const sourceSchema: JsonSchema = { | ||
minItems: 2, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
minItems: 4, | ||
minItems: 1, | ||
type: 'array' | ||
@@ -214,12 +213,70 @@ }; | ||
const arraysWithNoItems: JsonSchema = { | ||
maxItems: 3, | ||
minItems: 2, | ||
const anArrayWithNoItems: JsonSchema = { | ||
items: false, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(arraysWithNoItems); | ||
expect(diffResult.removedJsonSchema).toEqual(anArrayWithNoItems); | ||
}); | ||
}); | ||
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', () => { | ||
@@ -278,10 +335,110 @@ it('should find a remove difference if items schema is constrained but minItems remains the same', async () => { | ||
const allArraysWithNoItems: JsonSchema = { | ||
const anArrayWithNoItems: JsonSchema = { | ||
items: false, | ||
type: ['array'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allArraysWithNoItems); | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
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, | ||
minItems: 1, | ||
type: ['array'] | ||
}, | ||
{ | ||
maxItems: 15, | ||
minItems: 11, | ||
type: ['array'] | ||
} | ||
] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allArraysOfLengthOneToFourOrElevenToFifteen); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
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'] | ||
}, | ||
maxItems: 10, | ||
minItems: 5, | ||
type: 'array' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
maxItems: 15, | ||
minItems: 1, | ||
type: 'array' | ||
}; | ||
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); | ||
}); | ||
}); | ||
}); |
@@ -1,323 +0,365 @@ | ||
import {JsonSchema, SimpleTypes} from 'json-schema-spec-types'; | ||
import {CoreSchemaMetaSchema, JsonSchema, SimpleTypes} from 'json-schema-spec-types'; | ||
import {invokeDiff} from '../support/invoke-diff'; | ||
describe('diff-schemas type object', () => { | ||
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' | ||
}; | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithAtLeastOneProperty); | ||
const allObjectsWithAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithAtLeastOneProperty); | ||
}); | ||
}); | ||
it('should infer that all objects are supported when no constraints are applied by properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
name: true | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
type: 'object' | ||
}; | ||
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 | ||
} | ||
}, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithAtLeastOneProperty: JsonSchema = { | ||
minProperties: 1, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithAtLeastOneProperty); | ||
}); | ||
const allObjectsWithRequiredNullName: JsonSchema = { | ||
properties: { | ||
name: {type: ['null']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithRequiredNullName); | ||
}); | ||
it('should not infer that all objects are supported when constraints are applied by some properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
first: true, | ||
last: false | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: true, | ||
type: 'object' | ||
}; | ||
it('should find an add and a remove differences when a property constraint is changed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
name: {type: 'array'} | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
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); | ||
}); | ||
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 infer empty object set when required contradicts properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
name: false | ||
}, | ||
required: ['name'], | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: true, | ||
type: 'object' | ||
}; | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjects: JsonSchema = {type: ['object']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjects); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
const allObjectsWithRequiredLastProperty: JsonSchema = { | ||
properties: { | ||
last: true | ||
}, | ||
required: ['last'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithRequiredLastProperty); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
it('should find an add and a remove differences when changing properties type', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
name: {type: 'array'} | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}; | ||
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 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); | ||
}); | ||
const allObjectsWithNoFirst: JsonSchema = { | ||
properties: { | ||
first: false | ||
}, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithNoFirst); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
it('should find no differences when a property matching additionalProperties is removed', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: {type: 'string'}, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: {type: 'string'}, | ||
type: 'object' | ||
}; | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
const allObjectsWithRequiredNameAndNoFirst: JsonSchema = { | ||
properties: {first: false}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithRequiredNameAndNoFirst); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
it('should find differences between schemas with properties and additionalProperties', async () => { | ||
const allTypesButNull: SimpleTypes[] = ['object', 'string', 'array', 'integer', 'number', 'boolean']; | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: true, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: true, | ||
properties: { | ||
name: { | ||
type: allTypesButNull | ||
} | ||
}, | ||
type: 'object' | ||
}; | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithRequiredNullName: JsonSchema = { | ||
properties: { | ||
name: {type: ['null']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithRequiredNullName); | ||
}); | ||
const allObjectsWithZeroToNineProperties: JsonSchema = { | ||
maxProperties: 9, | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithZeroToNineProperties); | ||
}); | ||
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' | ||
} | ||
}, | ||
type: 'object' | ||
}; | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithRequiredNumberNameAndStringOrNumberAdditionalProperties: JsonSchema = { | ||
additionalProperties: {type: ['number', 'string']}, | ||
properties: { | ||
name: {type: ['number']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema) | ||
.toEqual(allObjectsWithRequiredNumberNameAndStringOrNumberAdditionalProperties); | ||
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); | ||
}); | ||
}); | ||
it('should find differences in schemas with boolean additionalProperties and optional properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: true, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
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'] | ||
} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual({ | ||
anyOf: [ | ||
allObjectsWithNoNameAndAtLeastOneProperty, | ||
allObjectsWithRequiredStringNameAndAtLeastAnotherProperty | ||
] | ||
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); | ||
}); | ||
}); | ||
it('should add numbers into the anyOf when the object differences result in an anyOf', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: true, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: ['object', 'number'] | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
name: {type: 'string'} | ||
}, | ||
type: ['object'] | ||
}; | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
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'] | ||
} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
const allNumbers: JsonSchema = {type: ['number']}; | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual({ | ||
anyOf: [ | ||
allObjectsWithNoNameAndAtLeastOneProperty, | ||
allObjectsWithRequiredStringNameAndAtLeastAnotherProperty, | ||
allNumbers | ||
] | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
describe('required properties', () => { | ||
it('should find a difference between schemas with required property completely removed', async () => { | ||
describe('additionalProperties + minProperties', () => { | ||
it('should infer that no objects are supported when additional and min properties contradict', async () => { | ||
const sourceSchema: JsonSchema = { | ||
required: ['first'], | ||
additionalProperties: false, | ||
minProperties: 1, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = {type: 'object'}; | ||
const destinationSchema: JsonSchema = false; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjectsWithNoFirst: JsonSchema = { | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
describe('additionalProperties + properties', () => { | ||
it('should infer that all objects are supported when no constraints are applied by properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
properties: { | ||
first: false | ||
name: true | ||
}, | ||
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(allObjectsWithNoFirst); | ||
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'} | ||
}, | ||
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 a difference between schemas with required property removed', async () => { | ||
it('should find differences between schemas with properties and non boolean additionalProperties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
required: ['name', 'first'], | ||
additionalProperties: {type: ['string', 'number']}, | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
required: ['name'], | ||
additionalProperties: {type: ['string', 'number']}, | ||
properties: { | ||
name: { | ||
type: 'string' | ||
} | ||
}, | ||
type: 'object' | ||
@@ -328,11 +370,415 @@ }; | ||
const allObjectsWithRequiredNameAndNoFirst: JsonSchema = { | ||
properties: { first: false }, | ||
const allObjectsWithRequiredNumberNameAndStringOrNumberAdditionalProperties: JsonSchema = { | ||
additionalProperties: {type: ['number', 'string']}, | ||
properties: { | ||
name: {type: ['number']} | ||
}, | ||
required: ['name'], | ||
type: ['object'] | ||
}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithRequiredNameAndNoFirst); | ||
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'} | ||
}, | ||
type: ['object'] | ||
}; | ||
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'] | ||
} | ||
}, | ||
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'] | ||
} | ||
}, | ||
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); | ||
}); | ||
}); | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
expect(diffResult.addedJsonSchema).toEqual(false); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
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 diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjects: JsonSchema = {type: ['object']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjects); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
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: { | ||
properties: { | ||
street: false | ||
}, | ||
required: ['street'], | ||
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 () => { | ||
@@ -379,2 +825,24 @@ const sourceSchema: JsonSchema = { | ||
}); | ||
describe('additionalProperties + properties + required', () => { | ||
it('should infer no objects are accepted when required contradicts properties', async () => { | ||
const sourceSchema: JsonSchema = { | ||
additionalProperties: false, | ||
properties: { | ||
name: false | ||
}, | ||
required: ['name'], | ||
type: 'object' | ||
}; | ||
const destinationSchema: JsonSchema = { | ||
type: 'object' | ||
}; | ||
const diffResult = await invokeDiff(sourceSchema, destinationSchema); | ||
const allObjects: JsonSchema = {type: ['object']}; | ||
expect(diffResult.addedJsonSchema).toEqual(allObjects); | ||
expect(diffResult.removedJsonSchema).toEqual(false); | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
214930
110
4471
7
21
+ Addedjs-combinatorics@^0.5.4
+ Addedjs-combinatorics@0.5.5(transitive)
+ Addedjson-schema-ref-parser@6.1.0(transitive)
- Removeddebug@3.2.7(transitive)
- Removedjson-schema-ref-parser@5.1.3(transitive)
- Removedms@2.1.3(transitive)