Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

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.1 to 0.13.0

dist/json-schema-diff/diff-schemas/set/json-set/json-set-config-to-core-representation-schema.js

17

CHANGELOG.md

@@ -0,1 +1,16 @@

<a name="0.13.0"></a>
# [0.13.0](https://bitbucket.org/atlassian/json-schema-diff/compare/0.12.1...0.13.0) (2019-08-19)
### Bug Fixes
* correct repository URL in package.json ([a99f3a5](https://bitbucket.org/atlassian/json-schema-diff/commits/a99f3a5))
### Features
* support for the anyOf keyword ([f17c20e](https://bitbucket.org/atlassian/json-schema-diff/commits/f17c20e))
<a name="0.12.1"></a>

@@ -7,3 +22,3 @@ ## [0.12.1](https://bitbucket.org/atlassian/json-schema-diff/compare/0.12.0...0.12.1) (2019-04-15)

* add missing values to the array complement schema calculation ([d86b249](https://bitbucket.org/atlassian/json-schema-diff/commits/d86b249))
* add missing values to the object complement schema calculation ([9d228e1](https://bitbucket.org/atlassian/json-schema-diff/commits/9d228e1))
* add missing values to the array complement schema calculation ([8869982](https://bitbucket.org/atlassian/json-schema-diff/commits/8869982))

@@ -10,0 +25,0 @@

@@ -24,3 +24,3 @@ "use strict";

const parseNumericKeyword = (keywordValue, defaultValue) => typeof keywordValue === 'number' ? keywordValue : defaultValue;
const parseTypeKeywords = (schema) => create_json_set_1.createSomeJsonSet({
const parseTypeKeywords = (schema) => create_json_set_1.createJsonSetFromParsedSchemaKeywords({
additionalProperties: parseSchemaOrUndefinedAsJsonSet(schema.additionalProperties),

@@ -36,3 +36,11 @@ items: parseSchemaOrUndefinedAsJsonSet(schema.items),

});
const parseBooleanLogicKeywords = (schema) => (schema.allOf || []).map(parseSchemaOrUndefinedAsJsonSet);
const parseAllOfKeyword = (allOfSchemas) => (allOfSchemas || []).map(parseSchemaOrUndefinedAsJsonSet);
const parseAnyOfKeyword = (anyOfSchemas) => {
if (!anyOfSchemas) {
return [];
}
const [head, ...tail] = anyOfSchemas.map(parseSchemaOrUndefinedAsJsonSet);
return [tail.reduce((accumulator, set) => accumulator.union(set), head)];
};
const parseBooleanLogicKeywords = (schema) => [...parseAnyOfKeyword(schema.anyOf), ...parseAllOfKeyword(schema.allOf)];
const parseCoreSchemaMetaSchema = (schema) => {

@@ -39,0 +47,0 @@ const typeKeywordsSet = parseTypeKeywords(schema);

4

dist/json-schema-diff/diff-schemas/set-factories/create-array-set.js
"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 type_set_1 = require("../set/type-set");
const is_type_supported_1 = require("./is-type-supported");

@@ -21,3 +21,3 @@ const supportsAllArrays = (arraySetParsedKeywords) => arraySetParsedKeywords.items.type === 'all'

const arraySubset = createArraySubset(arraySetParsedKeywords);
return new set_of_subsets_1.SetOfSubsets('array', [arraySubset]);
return type_set_1.createTypeSetFromSubsets('array', [arraySubset]);
};

@@ -7,19 +7,12 @@ "use strict";

const create_type_set_1 = require("./create-type-set");
exports.createSomeJsonSet = (parsedSchemaKeywords) => {
const typeSets = {
array: create_array_set_1.createArraySet(parsedSchemaKeywords),
boolean: create_type_set_1.createTypeSet('boolean', parsedSchemaKeywords.type),
integer: create_type_set_1.createTypeSet('integer', parsedSchemaKeywords.type),
null: create_type_set_1.createTypeSet('null', parsedSchemaKeywords.type),
number: create_type_set_1.createTypeSet('number', parsedSchemaKeywords.type),
object: create_object_set_1.createObjectSet(parsedSchemaKeywords),
string: create_type_set_1.createTypeSet('string', parsedSchemaKeywords.type)
};
return new json_set_1.SomeJsonSet(typeSets);
};
exports.createAllJsonSet = () => {
return json_set_1.allJsonSet;
};
exports.createEmptyJsonSet = () => {
return json_set_1.emptyJsonSet;
};
exports.createJsonSetFromParsedSchemaKeywords = (parsedSchemaKeywords) => json_set_1.createJsonSetFromConfig({
array: create_array_set_1.createArraySet(parsedSchemaKeywords),
boolean: create_type_set_1.createTypeSet('boolean', parsedSchemaKeywords.type),
integer: create_type_set_1.createTypeSet('integer', parsedSchemaKeywords.type),
null: create_type_set_1.createTypeSet('null', parsedSchemaKeywords.type),
number: create_type_set_1.createTypeSet('number', parsedSchemaKeywords.type),
object: create_object_set_1.createObjectSet(parsedSchemaKeywords),
string: create_type_set_1.createTypeSet('string', parsedSchemaKeywords.type)
});
exports.createAllJsonSet = () => json_set_1.allJsonSet;
exports.createEmptyJsonSet = () => json_set_1.emptyJsonSet;

@@ -5,4 +5,4 @@ "use strict";

const keyword_defaults_1 = require("../set/keyword-defaults");
const set_of_subsets_1 = require("../set/set-of-subsets");
const object_subset_1 = require("../set/subset/object-subset");
const type_set_1 = require("../set/type-set");
const is_type_supported_1 = require("./is-type-supported");

@@ -30,3 +30,3 @@ const supportsAllObjects = (objectSetParsedKeywords) => {

const objectSubset = createObjectSubset(objectSetParsedKeywords);
return new set_of_subsets_1.SetOfSubsets('object', [objectSubset]);
return type_set_1.createTypeSetFromSubsets('object', [objectSubset]);
};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const subset_1 = require("../set/subset/subset");
const type_set_1 = require("../set/type-set");
const is_type_supported_1 = require("./is-type-supported");
exports.createTypeSet = (setType, types) => is_type_supported_1.isTypeSupported(types, setType)
? new type_set_1.AllTypeSet(setType)
: new type_set_1.EmptyTypeSet(setType);
const createTypeSubset = (setType, types) => is_type_supported_1.isTypeSupported(types, setType) ? new subset_1.AllSubset(setType) : new subset_1.EmptySubset(setType);
exports.createTypeSet = (setType, types) => type_set_1.createTypeSetFromSubsets(setType, [createTypeSubset(setType, types)]);
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// tslint:disable:max-classes-per-file
const diff_sets_1 = require("./diff-sets");
const omit_defaults_1 = require("./json-set/omit-defaults");
const set_1 = require("./set");
const json_set_config_1 = require("./json-set/json-set-config");
const json_set_config_to_core_representation_schema_1 = require("./json-set/json-set-config-to-core-representation-schema");
const noop = () => {

@@ -11,4 +12,4 @@ return;

constructor() {
this.type = 'all';
this.setType = 'json';
this.type = 'all';
}

@@ -24,2 +25,8 @@ complement() {

}
union() {
return this;
}
unionWithSome() {
return this;
}
equal(other) {

@@ -32,8 +39,6 @@ return other === this;

}
exports.AllJsonSet = AllJsonSet;
exports.allJsonSet = new AllJsonSet();
class EmptyJsonSet {
constructor() {
this.type = 'empty';
this.setType = 'json';
this.type = 'empty';
}

@@ -49,2 +54,8 @@ complement() {

}
union(other) {
return other;
}
unionWithSome(other) {
return other;
}
equal(other) {

@@ -57,63 +68,10 @@ return other === this;

}
exports.EmptyJsonSet = EmptyJsonSet;
exports.emptyJsonSet = new EmptyJsonSet();
class SomeJsonSet {
constructor(typeSets) {
this.typeSets = typeSets;
constructor(config) {
this.config = config;
this.type = 'some';
this.setType = 'json';
}
static isSimpleSchema(schema) {
return !schema.anyOf || schema.anyOf.length <= 1;
}
static createEmptyCoreRepresentationJsonSchema() {
return {
type: []
};
}
static mergeCoreRepresentationJsonSchemas(schema, otherSchema) {
const schemaTypes = SomeJsonSet.getJsonSchemaTypeOrEmpty(schema);
const otherSchemaTypes = SomeJsonSet.getJsonSchemaTypeOrEmpty(otherSchema);
const type = schemaTypes.concat(otherSchemaTypes);
const mergedSchema = Object.assign({}, schema, otherSchema, { type });
if (schema.not && otherSchema.not) {
mergedSchema.not = this.mergeCoreRepresentationJsonSchemas(schema.not, otherSchema.not);
}
return mergedSchema;
}
static toDiffJsonSchema(jsonSchema) {
const jsonSchemaTypes = SomeJsonSet.getJsonSchemaTypeOrEmpty(jsonSchema);
const jsonSchemaAnyOf = SomeJsonSet.getJsonSchemaAnyOfOrEmpty(jsonSchema);
const isEmpty = jsonSchemaTypes.length === 0 && jsonSchemaAnyOf.length === 0;
return isEmpty ? false : jsonSchema;
}
static getJsonSchemaTypeOrEmpty(jsonSchema) {
return jsonSchema.type || [];
}
static getJsonSchemaAnyOfOrEmpty(jsonSchema) {
return jsonSchema.anyOf || [];
}
static toCoreRepresentationJsonSchema(schema) {
return set_1.isCoreRepresentationJsonSchema(schema)
? schema
: SomeJsonSet.createEmptyCoreRepresentationJsonSchema();
}
get type() {
if (this.areAllTypeSetsOfType('empty')) {
return 'empty';
}
if (this.areAllTypeSetsOfType('all')) {
return 'all';
}
return 'some';
}
complement() {
return new SomeJsonSet({
array: this.typeSets.array.complement(),
boolean: this.typeSets.boolean.complement(),
integer: this.typeSets.integer.complement(),
null: this.typeSets.null.complement(),
number: this.typeSets.number.complement(),
object: this.typeSets.object.complement(),
string: this.typeSets.string.complement()
});
return exports.createJsonSetFromConfig(json_set_config_1.complementJsonSetConfig(this.config));
}

@@ -124,12 +82,10 @@ intersect(other) {

intersectWithSome(other) {
return new SomeJsonSet({
array: this.typeSets.array.intersect(other.typeSets.array),
boolean: this.typeSets.boolean.intersect(other.typeSets.boolean),
integer: this.typeSets.integer.intersect(other.typeSets.integer),
null: this.typeSets.null.intersect(other.typeSets.null),
number: this.typeSets.number.intersect(other.typeSets.number),
object: this.typeSets.object.intersect(other.typeSets.object),
string: this.typeSets.string.intersect(other.typeSets.string)
});
return exports.createJsonSetFromConfig(json_set_config_1.intersectJsonSetConfigs(this.config, other.config));
}
union(other) {
return other.unionWithSome(this);
}
unionWithSome(other) {
return exports.createJsonSetFromConfig(json_set_config_1.unionJsonSetConfigs(this.config, other.config));
}
equal(other) {

@@ -140,50 +96,20 @@ const { addedToDestinationSet, removedFromDestinationSet } = diff_sets_1.diffSets(this, other, noop);

toJsonSchema() {
const typeSetSchemas = Object
.keys(this.typeSets)
.map((typeSetName) => this.getSubsetAsCoreRepresentationJsonSchema(typeSetName));
const mergedSimpleSubsetSchemas = typeSetSchemas
.filter(SomeJsonSet.isSimpleSchema)
.map((schema) => {
if (schema.anyOf) {
return SomeJsonSet.toCoreRepresentationJsonSchema(schema.anyOf[0]);
}
return schema;
})
.reduce((mergedSchema, schema) => {
return SomeJsonSet.mergeCoreRepresentationJsonSchemas(mergedSchema, schema);
}, SomeJsonSet.createEmptyCoreRepresentationJsonSchema());
const mergedComplexSubsetSchemas = typeSetSchemas
.filter((schema) => !SomeJsonSet.isSimpleSchema(schema))
.reduce((mergedSchema, schema) => {
const mergedSchemaAnyOf = SomeJsonSet.getJsonSchemaAnyOfOrEmpty(mergedSchema);
const schemaAnyOf = SomeJsonSet.getJsonSchemaAnyOfOrEmpty(schema);
return {
anyOf: mergedSchemaAnyOf.concat(schemaAnyOf)
};
}, {});
let result;
if (SomeJsonSet.getJsonSchemaAnyOfOrEmpty(mergedComplexSubsetSchemas).length === 0) {
result = mergedSimpleSubsetSchemas;
}
else if (SomeJsonSet.getJsonSchemaTypeOrEmpty(mergedSimpleSubsetSchemas).length === 0) {
result = mergedComplexSubsetSchemas;
}
else {
const mergedAnyOf = SomeJsonSet.getJsonSchemaAnyOfOrEmpty(mergedComplexSubsetSchemas);
mergedAnyOf.push(mergedSimpleSubsetSchemas);
result = mergedComplexSubsetSchemas;
}
const sanitisedResult = omit_defaults_1.omitDefaults(result);
return SomeJsonSet.toDiffJsonSchema(sanitisedResult);
return json_set_config_to_core_representation_schema_1.jsonSetConfigToCoreRepresentationSchema(this.config);
}
getSubsetAsCoreRepresentationJsonSchema(typeSetName) {
const typeSetSchema = this.typeSets[typeSetName].toJsonSchema();
return SomeJsonSet.toCoreRepresentationJsonSchema(typeSetSchema);
}
const areAllSubsetsInConfigOfType = (config, setType) => {
return Object
.keys(config)
.every((name) => config[name].type === setType);
};
exports.allJsonSet = new AllJsonSet();
exports.emptyJsonSet = new EmptyJsonSet();
exports.createJsonSetFromConfig = (config) => {
if (areAllSubsetsInConfigOfType(config, 'empty')) {
return exports.emptyJsonSet;
}
areAllTypeSetsOfType(setType) {
return Object
.keys(this.typeSets)
.every((name) => this.typeSets[name].type === setType);
else if (areAllSubsetsInConfigOfType(config, 'all')) {
return exports.allJsonSet;
}
}
exports.SomeJsonSet = SomeJsonSet;
return new SomeJsonSet(config);
};

@@ -12,4 +12,4 @@ "use strict";

this.config = config;
this.type = 'some';
this.setType = 'array';
this.type = 'some';
}

@@ -16,0 +16,0 @@ complement() {

@@ -11,3 +11,3 @@ "use strict";

// {type: 'array', items: {type: 'number'}} -> {not: {type: 'array', items: {type: 'number'}}}
notItems: configA.notItems
notItems: configB.notItems
});

@@ -11,4 +11,4 @@ "use strict";

this.config = config;
this.type = 'some';
this.setType = 'object';
this.type = 'some';
}

@@ -15,0 +15,0 @@ static toSchemaProperties(properties) {

@@ -25,5 +25,5 @@ "use strict";

// -> {not: {type: 'object', additionalProperties: {type: 'number'}}}
not: configA.not,
not: configB.not,
properties: intersectProperties(configA, configB),
required: intersectRequired(configA, configB)
});
"use strict";
// tslint:disable:max-classes-per-file
Object.defineProperty(exports, "__esModule", { value: true });
const _ = require("lodash");
class AllTypeSet {

@@ -12,2 +13,11 @@ constructor(setType) {

}
intersectWithSome(other) {
return other;
}
union() {
return this;
}
unionWithSome() {
return this;
}
complement() {

@@ -23,3 +33,2 @@ return new EmptyTypeSet(this.setType);

}
exports.AllTypeSet = AllTypeSet;
class EmptyTypeSet {

@@ -33,2 +42,11 @@ constructor(setType) {

}
intersectWithSome() {
return this;
}
union(other) {
return other;
}
unionWithSome(other) {
return other;
}
complement() {

@@ -44,2 +62,59 @@ return new AllTypeSet(this.setType);

}
exports.EmptyTypeSet = EmptyTypeSet;
class SomeTypeSet {
constructor(setType, subsets) {
this.setType = setType;
this.subsets = subsets;
this.type = 'some';
}
static intersectSubsets(subsetListA, subsetListB) {
const intersectedSubsets = [];
for (const subsetA of subsetListA) {
for (const subsetB of subsetListB) {
intersectedSubsets.push(subsetA.intersect(subsetB));
}
}
return intersectedSubsets;
}
complement() {
const [head, ...tail] = this.subsets.map((set) => set.complement());
const complementedSubsets = tail.reduce(SomeTypeSet.intersectSubsets, head);
return exports.createTypeSetFromSubsets(this.setType, complementedSubsets);
}
intersect(other) {
return other.intersectWithSome(this);
}
intersectWithSome(other) {
const intersectedSubsets = SomeTypeSet.intersectSubsets(this.subsets, other.subsets);
return exports.createTypeSetFromSubsets(this.setType, intersectedSubsets);
}
union(other) {
return other.unionWithSome(this);
}
unionWithSome(other) {
const newSubsets = [...this.subsets, ...other.subsets];
return exports.createTypeSetFromSubsets(this.setType, newSubsets);
}
equal() {
throw new Error('Not Implemented');
}
toJsonSchema() {
const schemas = this.subsets.map((subset) => subset.toJsonSchema());
const dedupedSchemas = _.uniqWith(schemas, _.isEqual);
return { anyOf: dedupedSchemas };
}
}
const isAnySubsetTypeAll = (subsets) => subsets.some((subset) => subset.type === 'all');
const isTypeAll = (subsets) => {
return isAnySubsetTypeAll(subsets);
};
const getNonEmptySubsets = (subsets) => subsets.filter((subset) => subset.type !== 'empty');
exports.createTypeSetFromSubsets = (setType, subsets) => {
const notEmptySubsets = getNonEmptySubsets(subsets);
if (notEmptySubsets.length === 0) {
return new EmptyTypeSet(setType);
}
if (isTypeAll(subsets)) {
return new AllTypeSet(setType);
}
return new SomeTypeSet(setType, notEmptySubsets);
};
'use strict';
const gulp = require('gulp');
const bump = require('gulp-bump');
const clean = require('gulp-clean');
const colors = require('ansi-colors');
const conventionalChangelog = require('gulp-conventional-changelog');
const exec = require('./gulp/exec');
const exec = require('child_process').exec;
const filter = require('gulp-filter');
const fs = require('fs');
const git = require('gulp-git');
const fs = require('fs');
const jasmine = require('gulp-jasmine');
const minimist = require('minimist');
const runSequence = require('run-sequence');
const ts = require('gulp-typescript');
const tslint = require('gulp-tslint');
const filter = require('gulp-filter');
const getVersion = () => JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
const options = minimist(process.argv.slice(2), {strings: ['type']});
const tsProjectBuildOutput = ts.createProject('tsconfig.json', {noEmit: false});
const unitTests = 'build-output/test/unit/**/*.spec.js';
const e2eTests = 'build-output/test/e2e/**/*.spec.js';
const getBumpType = () => {
const validTypes = ['major', 'minor', 'patch', 'prerelease'];
if (validTypes.indexOf(options.type) === -1) {
throw new Error(
`You must specify a release type as one of (${validTypes.join(', ')}), e.g. "--type minor"`
);
const utilities = {
compileBuildOutput: () => {
const tsResult = tsProjectBuildOutput.src().pipe(tsProjectBuildOutput());
return tsResult.js.pipe(gulp.dest('build-output'));
},
exec: (command) => {
return new Promise((resolve, reject) => {
console.log(`Executing command '${colors.yellow(command)}'`);
const childProcess = exec(command, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
childProcess.stdout.pipe(process.stdout);
childProcess.stderr.pipe(process.stderr);
});
},
getBumpType: () => {
const validTypes = ['major', 'minor', 'patch', 'prerelease'];
if (validTypes.indexOf(options.type) === -1) {
throw new Error(
`You must specify a release type as one of (${validTypes.join(', ')}), e.g. "--type minor"`
);
}
return options.type;
},
getPackageJsonVersion: () => {
return JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
}
return options.type;
};
const tsProjectBuildOutput = ts.createProject('tsconfig.json', {noEmit: false});
gulp.task('bump-version', () =>
const bumpVersion = () =>
gulp.src(['./package.json'])
.pipe(bump({type: getBumpType()}))
.pipe(gulp.dest('./'))
);
.pipe(bump({type: utilities.getBumpType()}))
.pipe(gulp.dest('./'));
gulp.task('clean-build-output', () =>
gulp.src('build-output', {force: true, read: false}).pipe(clean())
);
const cleanBuildOutput = () =>
gulp.src('build-output', {force: true, read: false, allowEmpty: true}).pipe(clean());
gulp.task('clean-dist', () =>
gulp.src('dist', {force: true, read: false}).pipe(clean())
);
const cleanDist = () =>
gulp.src('dist', {force: true, read: false, allowEmpty: true}).pipe(clean());
gulp.task('changelog', () =>
gulp.src('CHANGELOG.md', {buffer: false})
const changelog = () => {
return gulp.src('CHANGELOG.md')
.pipe(conventionalChangelog({preset: 'angular'}))
.pipe(gulp.dest('./'))
);
.pipe(gulp.dest('./'));
};
gulp.task('commit-changes', () =>
const commitRelease = () =>
gulp.src('.')
.pipe(git.add({args: '--all'}))
.pipe(git.commit(`chore: release ${getVersion()}`))
);
.pipe(git.commit(`chore: release ${utilities.getPackageJsonVersion()}`));
gulp.task('clean-copy-and-compile-build-output', (callback) => {
runSequence(
'clean-build-output',
['copy-build-output-package-json', 'compile-build-output'],
callback
)
});
const compileAndUnitTest = () =>
utilities.compileBuildOutput()
.pipe(filter([unitTests]))
.pipe(jasmine({includeStackTrace: true}));
const compileBuildOutput = () => {
const tsResult = tsProjectBuildOutput.src().pipe(tsProjectBuildOutput());
return tsResult.js.pipe(gulp.dest('build-output'));
};
const copyBuildOutputPackageJson = () =>
gulp.src('package.json').pipe(gulp.dest('build-output'));
gulp.task('compile-build-output', () => {
return compileBuildOutput();
});
gulp.task('compile-dist', () => {
const compileDist = () => {
const tsProjectDist = ts.createProject('tsconfig.json', {noEmit: false});
const tsResult = gulp.src('lib/**/*.ts').pipe(tsProjectDist());
return tsResult.js.pipe(gulp.dest('dist'));
});
};
gulp.task('copy-build-output-package-json', () =>
gulp.src('package.json').pipe(gulp.dest('build-output'))
);
gulp.task('create-new-tag', (callback) => {
const version = getVersion();
const createNewTag = (callback) => {
const version = utilities.getPackageJsonVersion();
git.tag(version, `Created Tag for version: ${version}`, callback);
});
};
gulp.task('lint', ['lint-typescript', 'lint-commits']);
const lintCommits = () =>
utilities.exec('./node_modules/.bin/conventional-changelog-lint --from=HEAD~20 --preset angular');
gulp.task('lint-commits', () =>
exec('./node_modules/.bin/conventional-changelog-lint --from=39bd36c --preset angular')
);
const lintTypescript = () =>
tsProjectBuildOutput.src()
.pipe(tslint({formatter: 'verbose'}))
.pipe(tslint.report());
gulp.task('lint-typescript', () => tsProjectBuildOutput.src()
.pipe(tslint({formatter: 'verbose'}))
.pipe(tslint.report())
);
const npmPublish = () => utilities.exec('npm publish');
gulp.task('npm-publish', () => exec('npm publish'));
gulp.task('push-changes', (callback) => {
const pushChanges = (callback) => {
git.push('origin', 'master', {args: '--tags'}, callback);
});
};
gulp.task('release', (callback) => {
runSequence(
'default',
'clean-dist',
'compile-dist',
'bump-version',
'changelog',
'commit-changes',
'create-new-tag',
'push-changes',
'npm-publish',
callback
);
});
const test = () => gulp.src([unitTests, e2eTests]).pipe(jasmine({includeStackTrace: true}));
const unitTestHelpers = 'build-output/test/unit/support/spec-helper.js';
const unitTests = 'build-output/test/unit/**/*.spec.js';
const e2eTests = 'build-output/test/e2e/**/*.spec.js';
const testE2e = () => gulp.src([e2eTests]).pipe(jasmine({includeStackTrace: true}));
gulp.task('test', () => gulp.src([unitTestHelpers, unitTests, e2eTests]).pipe(jasmine()));
const watchAndRunUnitTests = () => {
gulp.watch(['lib/**/*.ts', 'test/**/*.ts'], compileAndUnitTest)
};
gulp.task('test:unit', () => gulp.src([unitTestHelpers, unitTests]).pipe(jasmine()));
const watchAndRunE2eTests = () => {
const e2eFiles = ['build-output/lib/**/*', 'build-output/test/e2e/**/*', 'test/e2e/**/*.json'];
gulp.watch(e2eFiles, testE2e);
};
gulp.task('test:e2e', () => gulp.src([e2eTests]).pipe(jasmine()));
const cleanCopyAndCompileBuildOutput = gulp.series(
cleanBuildOutput,
gulp.parallel(copyBuildOutputPackageJson, utilities.compileBuildOutput)
);
gulp.task('compile-and-unit-test', () => {
return compileBuildOutput()
.pipe(filter([unitTestHelpers, unitTests]))
.pipe(jasmine({includeStackTrace: true}));
});
const lint = gulp.parallel(lintTypescript, lintCommits);
gulp.task('watch', ['clean-copy-and-compile-build-output'], () => {
gulp.watch(['lib/**/*.ts', 'test/**/*.ts'], ['compile-and-unit-test'])
});
exports.default = gulp.series(
cleanCopyAndCompileBuildOutput,
gulp.parallel(lint, test)
);
gulp.task('watch-e2e', () => {
gulp.watch(['build-output/lib/**/*', 'build-output/test/e2e/**/*', 'test/e2e/**/*.json'], ['test:e2e']);
});
exports.release = gulp.series(
exports.default,
cleanDist,
compileDist,
bumpVersion,
changelog,
commitRelease,
createNewTag,
pushChanges,
npmPublish
);
gulp.task('default', (callback) => {
runSequence(
['clean-copy-and-compile-build-output'],
['lint', 'test'],
callback
);
});
exports.watch = gulp.series(
cleanCopyAndCompileBuildOutput,
watchAndRunUnitTests
);
exports.watchE2e = watchAndRunE2eTests;

@@ -72,3 +72,3 @@ # Json Schema Diff Supported Keywords

| allOf | yes |
| anyOf | no |
| anyOf | yes |
| oneOf | no |

@@ -75,0 +75,0 @@ | not | no |

import {CoreSchemaMetaSchema, JsonSchema, JsonSchemaMap, SimpleTypes} from 'json-schema-spec-types';
import {createAllJsonSet, createEmptyJsonSet, createSomeJsonSet} from './set-factories/create-json-set';
import {
createAllJsonSet,
createEmptyJsonSet,
createJsonSetFromParsedSchemaKeywords
} from './set-factories/create-json-set';
import {
defaultMaxItems,

@@ -42,3 +46,3 @@ defaultMaxProperties,

const parseTypeKeywords = (schema: CoreSchemaMetaSchema): Set<'json'> =>
createSomeJsonSet({
createJsonSetFromParsedSchemaKeywords({
additionalProperties: parseSchemaOrUndefinedAsJsonSet(schema.additionalProperties),

@@ -55,4 +59,16 @@ items: parseSchemaOrUndefinedAsJsonSet(schema.items),

const parseAllOfKeyword = (allOfSchemas: JsonSchema[] | undefined): Array<Set<'json'>> =>
(allOfSchemas || []).map(parseSchemaOrUndefinedAsJsonSet);
const parseAnyOfKeyword = (anyOfSchemas: JsonSchema[] | undefined): Array<Set<'json'>> => {
if (!anyOfSchemas) {
return [];
}
const [head, ...tail] = anyOfSchemas.map(parseSchemaOrUndefinedAsJsonSet);
return [tail.reduce((accumulator, set) => accumulator.union(set), head)];
};
const parseBooleanLogicKeywords = (schema: CoreSchemaMetaSchema): Array<Set<'json'>> =>
(schema.allOf || []).map(parseSchemaOrUndefinedAsJsonSet);
[...parseAnyOfKeyword(schema.anyOf), ...parseAllOfKeyword(schema.allOf)];

@@ -59,0 +75,0 @@ const parseCoreSchemaMetaSchema = (schema: CoreSchemaMetaSchema): Set<'json'> => {

import {SimpleTypes} from 'json-schema-spec-types';
import {defaultMaxItems, defaultMinItems} from '../set/keyword-defaults';
import {Set, Subset} from '../set/set';
import {SetOfSubsets} from '../set/set-of-subsets';
import {allArraySubset, createArraySubsetFromConfig, emptyArraySubset} from '../set/subset/array-subset';
import {createTypeSetFromSubsets} from '../set/type-set';
import {isTypeSupported} from './is-type-supported';

@@ -34,3 +34,3 @@

const arraySubset = createArraySubset(arraySetParsedKeywords);
return new SetOfSubsets('array', [arraySubset]);
return createTypeSetFromSubsets('array', [arraySubset]);
};
import {SimpleTypes} from 'json-schema-spec-types';
import {allJsonSet, emptyJsonSet, SomeJsonSet, TypeSets} from '../set/json-set';
import {allJsonSet, createJsonSetFromConfig, emptyJsonSet} from '../set/json-set';
import {Set} from '../set/set';

@@ -21,4 +21,4 @@ import {ParsedPropertiesKeyword} from '../set/subset/object-subset/object-subset-config';

export const createSomeJsonSet = (parsedSchemaKeywords: ParsedSchemaKeywords): Set<'json'> => {
const typeSets: TypeSets = {
export const createJsonSetFromParsedSchemaKeywords = (parsedSchemaKeywords: ParsedSchemaKeywords): Set<'json'> =>
createJsonSetFromConfig({
array: createArraySet(parsedSchemaKeywords),

@@ -31,13 +31,6 @@ boolean: createTypeSet('boolean', parsedSchemaKeywords.type),

string: createTypeSet('string', parsedSchemaKeywords.type)
};
});
return new SomeJsonSet(typeSets);
};
export const createAllJsonSet = (): Set<'json'> => allJsonSet;
export const createAllJsonSet = (): Set<'json'> => {
return allJsonSet;
};
export const createEmptyJsonSet = (): Set<'json'> => {
return emptyJsonSet;
};
export const createEmptyJsonSet = (): Set<'json'> => emptyJsonSet;

@@ -5,5 +5,5 @@ import {SimpleTypes} from 'json-schema-spec-types';

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 {createTypeSetFromSubsets} from '../set/type-set';
import {isTypeSupported} from './is-type-supported';

@@ -46,3 +46,3 @@

const objectSubset = createObjectSubset(objectSetParsedKeywords);
return new SetOfSubsets('object', [objectSubset]);
return createTypeSetFromSubsets('object', [objectSubset]);
};
import {SimpleTypes} from 'json-schema-spec-types';
import {Set} from '../set/set';
import {AllTypeSet, EmptyTypeSet} from '../set/type-set';
import {Set, Subset} from '../set/set';
import {AllSubset, EmptySubset} from '../set/subset/subset';
import {createTypeSetFromSubsets} from '../set/type-set';
import {isTypeSupported} from './is-type-supported';
const createTypeSubset = <T extends SimpleTypes> (setType: T, types: SimpleTypes[]): Subset<T> =>
isTypeSupported(types, setType) ? new AllSubset(setType) : new EmptySubset(setType);
export const createTypeSet = <T extends SimpleTypes>(setType: T, types: SimpleTypes[]): Set<T> =>
isTypeSupported(types, setType)
? new AllTypeSet(setType)
: new EmptyTypeSet(setType);
createTypeSetFromSubsets(setType, [createTypeSubset(setType, types)]);
// 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';
import {
CoreRepresentationJsonSchema,
isCoreRepresentationJsonSchema,
RepresentationJsonSchema,
Set
} from './set';
complementJsonSetConfig,
intersectJsonSetConfigs,
JsonSetConfig,
unionJsonSetConfigs
} from './json-set/json-set-config';
import {jsonSetConfigToCoreRepresentationSchema} from './json-set/json-set-config-to-core-representation-schema';
import {CoreRepresentationJsonSchema, RepresentationJsonSchema, Set} from './set';
interface JsonSet extends Set<'json'> {
intersect(other: JsonSet): Set<'json'>;
intersectWithSome(other: SomeJsonSet): Set<'json'>;
unionWithSome(other: SomeJsonSet): Set<'json'>;
}
export interface TypeSets {
array: Set<'array'>;
boolean: Set<'boolean'>;
integer: Set<'integer'>;
number: Set<'number'>;
null: Set<'null'>;
object: Set<'object'>;
string: Set<'string'>;
}
const noop = (): void => {

@@ -31,5 +21,5 @@ return;

export class AllJsonSet implements JsonSet {
class AllJsonSet implements JsonSet {
public readonly type = 'all';
public readonly setType = 'json';
public readonly type = 'all';

@@ -48,2 +38,10 @@ public complement(): Set<'json'> {

public union(): Set<'json'> {
return this;
}
public unionWithSome(): Set<'json'> {
return this;
}
public equal(other: Set<'json'>): boolean {

@@ -58,7 +56,5 @@ return other === this;

export const allJsonSet = new AllJsonSet();
export class EmptyJsonSet implements JsonSet {
class EmptyJsonSet implements JsonSet {
public readonly type = 'empty';
public readonly setType = 'json';
public readonly type = 'empty';

@@ -77,2 +73,10 @@ public complement(): Set<'json'> {

public union(other: JsonSet): Set<'json'> {
return other;
}
public unionWithSome(other: JsonSet): Set<'json'> {
return other;
}
public equal(other: Set<'json'>): boolean {

@@ -87,84 +91,11 @@ return other === this;

export const emptyJsonSet = new EmptyJsonSet();
export class SomeJsonSet implements JsonSet {
private static isSimpleSchema(schema: CoreRepresentationJsonSchema): boolean {
return !schema.anyOf || schema.anyOf.length <= 1;
}
private static createEmptyCoreRepresentationJsonSchema(): CoreRepresentationJsonSchema {
return {
type: []
};
}
private static mergeCoreRepresentationJsonSchemas(
schema: CoreRepresentationJsonSchema,
otherSchema: CoreRepresentationJsonSchema
): CoreRepresentationJsonSchema {
const schemaTypes = SomeJsonSet.getJsonSchemaTypeOrEmpty(schema);
const otherSchemaTypes = SomeJsonSet.getJsonSchemaTypeOrEmpty(otherSchema);
const type: SimpleTypes[] = schemaTypes.concat(otherSchemaTypes);
const mergedSchema = {
...schema,
...otherSchema,
type
};
if (schema.not && otherSchema.not) {
mergedSchema.not = this.mergeCoreRepresentationJsonSchemas(schema.not, otherSchema.not);
}
return mergedSchema;
}
private static toDiffJsonSchema(jsonSchema: CoreRepresentationJsonSchema): RepresentationJsonSchema {
const jsonSchemaTypes = SomeJsonSet.getJsonSchemaTypeOrEmpty(jsonSchema);
const jsonSchemaAnyOf = SomeJsonSet.getJsonSchemaAnyOfOrEmpty(jsonSchema);
const isEmpty = jsonSchemaTypes.length === 0 && jsonSchemaAnyOf.length === 0;
return isEmpty ? false : jsonSchema;
}
private static getJsonSchemaTypeOrEmpty(jsonSchema: CoreRepresentationJsonSchema): SimpleTypes[] {
return jsonSchema.type || [];
}
private static getJsonSchemaAnyOfOrEmpty(jsonSchema: CoreRepresentationJsonSchema): RepresentationJsonSchema[] {
return jsonSchema.anyOf || [];
}
private static toCoreRepresentationJsonSchema(schema: RepresentationJsonSchema): CoreRepresentationJsonSchema {
return isCoreRepresentationJsonSchema(schema)
? schema
: SomeJsonSet.createEmptyCoreRepresentationJsonSchema();
}
class SomeJsonSet implements JsonSet {
public readonly type = 'some';
public readonly setType = 'json';
public get type(): 'some' | 'empty' | 'all' {
if (this.areAllTypeSetsOfType('empty')) {
return 'empty';
}
if (this.areAllTypeSetsOfType('all')) {
return 'all';
}
return 'some';
public constructor(public readonly config: JsonSetConfig) {
}
public constructor(public readonly typeSets: TypeSets) {
}
public complement(): Set<'json'> {
return new SomeJsonSet({
array: this.typeSets.array.complement(),
boolean: this.typeSets.boolean.complement(),
integer: this.typeSets.integer.complement(),
null: this.typeSets.null.complement(),
number: this.typeSets.number.complement(),
object: this.typeSets.object.complement(),
string: this.typeSets.string.complement()
});
return createJsonSetFromConfig(complementJsonSetConfig(this.config));
}

@@ -177,13 +108,13 @@

public intersectWithSome(other: SomeJsonSet): Set<'json'> {
return new SomeJsonSet({
array: this.typeSets.array.intersect(other.typeSets.array),
boolean: this.typeSets.boolean.intersect(other.typeSets.boolean),
integer: this.typeSets.integer.intersect(other.typeSets.integer),
null: this.typeSets.null.intersect(other.typeSets.null),
number: this.typeSets.number.intersect(other.typeSets.number),
object: this.typeSets.object.intersect(other.typeSets.object),
string: this.typeSets.string.intersect(other.typeSets.string)
});
return createJsonSetFromConfig(intersectJsonSetConfigs(this.config, other.config));
}
public union(other: JsonSet): Set<'json'> {
return other.unionWithSome(this);
}
public unionWithSome(other: SomeJsonSet): Set<'json'> {
return createJsonSetFromConfig(unionJsonSetConfigs(this.config, other.config));
}
public equal(other: Set<'json'>): boolean {

@@ -194,56 +125,23 @@ const {addedToDestinationSet, removedFromDestinationSet} = diffSets(this, other, noop);

public toJsonSchema(): RepresentationJsonSchema {
const typeSetSchemas = Object
.keys(this.typeSets)
.map((typeSetName: keyof TypeSets) => this.getSubsetAsCoreRepresentationJsonSchema(typeSetName));
public toJsonSchema(): CoreRepresentationJsonSchema {
return jsonSetConfigToCoreRepresentationSchema(this.config);
}
}
const mergedSimpleSubsetSchemas = typeSetSchemas
.filter(SomeJsonSet.isSimpleSchema)
.map((schema): CoreRepresentationJsonSchema => {
if (schema.anyOf) {
return SomeJsonSet.toCoreRepresentationJsonSchema(schema.anyOf[0]);
}
const areAllSubsetsInConfigOfType = (config: JsonSetConfig, setType: 'all' | 'empty') => {
return Object
.keys(config)
.every((name: keyof JsonSetConfig) => config[name].type === setType);
};
return schema;
})
.reduce((mergedSchema, schema) => {
return SomeJsonSet.mergeCoreRepresentationJsonSchemas(mergedSchema, schema);
}, SomeJsonSet.createEmptyCoreRepresentationJsonSchema());
export const allJsonSet = new AllJsonSet();
export const emptyJsonSet = new EmptyJsonSet();
const mergedComplexSubsetSchemas = typeSetSchemas
.filter((schema) => !SomeJsonSet.isSimpleSchema(schema))
.reduce<CoreRepresentationJsonSchema>((mergedSchema, schema) => {
const mergedSchemaAnyOf = SomeJsonSet.getJsonSchemaAnyOfOrEmpty(mergedSchema);
const schemaAnyOf = SomeJsonSet.getJsonSchemaAnyOfOrEmpty(schema);
return {
anyOf: mergedSchemaAnyOf.concat(schemaAnyOf)
};
}, {});
let result: CoreRepresentationJsonSchema;
if (SomeJsonSet.getJsonSchemaAnyOfOrEmpty(mergedComplexSubsetSchemas).length === 0) {
result = mergedSimpleSubsetSchemas;
} else if (SomeJsonSet.getJsonSchemaTypeOrEmpty(mergedSimpleSubsetSchemas).length === 0) {
result = mergedComplexSubsetSchemas;
} else {
const mergedAnyOf = SomeJsonSet.getJsonSchemaAnyOfOrEmpty(mergedComplexSubsetSchemas);
mergedAnyOf.push(mergedSimpleSubsetSchemas);
result = mergedComplexSubsetSchemas;
}
const sanitisedResult = omitDefaults(result);
return SomeJsonSet.toDiffJsonSchema(sanitisedResult);
export const createJsonSetFromConfig = (config: JsonSetConfig): Set<'json'> => {
if (areAllSubsetsInConfigOfType(config, 'empty')) {
return emptyJsonSet;
} else if (areAllSubsetsInConfigOfType(config, 'all')) {
return allJsonSet;
}
private getSubsetAsCoreRepresentationJsonSchema(typeSetName: keyof TypeSets): CoreRepresentationJsonSchema {
const typeSetSchema = this.typeSets[typeSetName].toJsonSchema();
return SomeJsonSet.toCoreRepresentationJsonSchema(typeSetSchema);
}
private areAllTypeSetsOfType(setType: 'all' | 'empty') {
return Object
.keys(this.typeSets)
.every((name: keyof TypeSets) => this.typeSets[name].type === setType);
}
}
return new SomeJsonSet(config);
};

@@ -22,2 +22,3 @@ import {CoreSchemaMetaSchema, SimpleTypes} from 'json-schema-spec-types';

intersect(other: Set<T>): Set<T>;
union(other: Set<T>): Set<T>;
equal(other: Set<T>): boolean;

@@ -24,0 +25,0 @@ toJsonSchema(): RepresentationJsonSchema;

@@ -12,4 +12,4 @@ // tslint:disable:max-classes-per-file

class SomeArraySubset implements Subset<'array'> {
public readonly type = 'some';
public readonly setType = 'array';
public readonly type = 'some';

@@ -16,0 +16,0 @@ public constructor(private readonly config: ArraySubsetConfig) {

@@ -18,3 +18,3 @@ import {ArraySubsetConfig} from './array-subset-config';

// {type: 'array', items: {type: 'number'}} -> {not: {type: 'array', items: {type: 'number'}}}
notItems: configA.notItems
notItems: configB.notItems
});

@@ -19,5 +19,5 @@ import {CoreRepresentationJsonSchema, RepresentationJsonSchema, SchemaProperties, Subset} from '../set';

}
public readonly setType = 'object';
public readonly type = 'some';
public readonly setType = 'object';

@@ -24,0 +24,0 @@ public constructor(private readonly config: ObjectSubsetConfig) {

@@ -36,5 +36,5 @@ import {getPropertyNames, getPropertySet, ObjectSubsetConfig, ParsedPropertiesKeyword} from './object-subset-config';

// -> {not: {type: 'object', additionalProperties: {type: 'number'}}}
not: configA.not,
not: configB.not,
properties: intersectProperties(configA, configB),
required: intersectRequired(configA, configB)
});
// tslint:disable:max-classes-per-file
import {SimpleTypes} from 'json-schema-spec-types';
import {RepresentationJsonSchema, Set} from './set';
import * as _ from 'lodash';
import {RepresentationJsonSchema, Set, Subset} from './set';
interface TypeSet<T extends SimpleTypes> extends Set<T> {}
interface TypeSet<T extends SimpleTypes> extends Set<T> {
intersectWithSome(other: SomeTypeSet<T>): Set<T>;
unionWithSome(other: SomeTypeSet<T>): Set<T>;
}
export class AllTypeSet<T extends SimpleTypes> implements TypeSet<T> {
class AllTypeSet<T extends SimpleTypes> implements TypeSet<T> {
public readonly type = 'all';

@@ -18,2 +22,14 @@

public intersectWithSome(other: SomeTypeSet<T>): Set<T> {
return other;
}
public union(): TypeSet<T> {
return this;
}
public unionWithSome(): Set<T> {
return this;
}
public complement(): TypeSet<T> {

@@ -32,3 +48,3 @@ return new EmptyTypeSet(this.setType);

export class EmptyTypeSet<T extends SimpleTypes> implements TypeSet<T> {
class EmptyTypeSet<T extends SimpleTypes> implements TypeSet<T> {
public readonly type = 'empty';

@@ -43,2 +59,14 @@

public intersectWithSome(): Set<T> {
return this;
}
public union(other: TypeSet<T>): TypeSet<T> {
return other;
}
public unionWithSome(other: SomeTypeSet<T>): Set<T> {
return other;
}
public complement(): TypeSet<T> {

@@ -56,1 +84,76 @@ return new AllTypeSet(this.setType);

}
class SomeTypeSet<T extends SimpleTypes> implements TypeSet<T> {
public static intersectSubsets<T>(subsetListA: Array<Subset<T>>, subsetListB: Array<Subset<T>>): Array<Subset<T>> {
const intersectedSubsets = [];
for (const subsetA of subsetListA) {
for (const subsetB of subsetListB) {
intersectedSubsets.push(subsetA.intersect(subsetB));
}
}
return intersectedSubsets;
}
public readonly type = 'some';
public constructor(public readonly setType: T, private readonly subsets: Array<Subset<T>>) {}
public complement(): Set<T> {
const [head, ...tail] = this.subsets.map((set) => set.complement());
const complementedSubsets = tail.reduce(SomeTypeSet.intersectSubsets, head);
return createTypeSetFromSubsets(this.setType, complementedSubsets);
}
public intersect(other: TypeSet<T>): Set<T> {
return other.intersectWithSome(this);
}
public intersectWithSome(other: SomeTypeSet<T>): Set<T> {
const intersectedSubsets = SomeTypeSet.intersectSubsets(this.subsets, other.subsets);
return createTypeSetFromSubsets(this.setType, intersectedSubsets);
}
public union(other: TypeSet<T>): Set<T> {
return other.unionWithSome(this);
}
public unionWithSome(other: SomeTypeSet<T>): Set<T> {
const newSubsets = [...this.subsets, ...other.subsets];
return createTypeSetFromSubsets(this.setType, newSubsets);
}
public equal(): boolean {
throw new Error('Not Implemented');
}
public toJsonSchema(): RepresentationJsonSchema {
const schemas = this.subsets.map((subset) => subset.toJsonSchema());
const dedupedSchemas = _.uniqWith(schemas, _.isEqual);
return {anyOf: dedupedSchemas};
}
}
const isAnySubsetTypeAll = <T>(subsets: Array<Subset<T>>): boolean => subsets.some((subset) => subset.type === 'all');
const isTypeAll = <T>(subsets: Array<Subset<T>>): boolean => {
return isAnySubsetTypeAll(subsets);
};
const getNonEmptySubsets = <T>(subsets: Array<Subset<T>>): Array<Subset<T>> =>
subsets.filter((subset) => subset.type !== 'empty');
export const createTypeSetFromSubsets = <T extends SimpleTypes>(setType: T, subsets: Array<Subset<T>>) => {
const notEmptySubsets = getNonEmptySubsets(subsets);
if (notEmptySubsets.length === 0) {
return new EmptyTypeSet(setType);
}
if (isTypeAll(subsets)) {
return new AllTypeSet(setType);
}
return new SomeTypeSet(setType, notEmptySubsets);
};
{
"name": "json-schema-diff",
"version": "0.12.1",
"version": "0.13.0",
"description": "A language agnostic CLI tool and nodejs api to identify differences between two json schema files.",

@@ -16,7 +16,7 @@ "bin": {

"watch-debug": "JSON_SCHEMA_DIFF_ENABLE_DEBUG=true ./node_modules/.bin/gulp watch",
"watch-e2e": "./node_modules/.bin/gulp watch-e2e"
"watch-e2e": "./node_modules/.bin/gulp watchE2e"
},
"repository": {
"type": "git",
"url": "git@bitbucket.org/atlassian/json-schema-diff.git"
"url": "https://bitbucket.org/atlassian/json-schema-diff.git"
},

@@ -42,19 +42,20 @@ "keywords": [

"@types/lodash": "^4.14.108",
"@types/node": "^11.9.6",
"@types/node": "^12.0.4",
"@types/verror": "^1.10.3",
"ansi-colors": "^3.2.4",
"conventional-changelog-lint": "^2.1.1",
"gulp": "^3.9.1",
"gulp": "^4.0.2",
"gulp-bump": "^3.1.1",
"gulp-clean": "^0.4.0",
"gulp-cli": "^2.0.1",
"gulp-conventional-changelog": "^1.1.24",
"gulp-filter": "^5.1.0",
"gulp-conventional-changelog": "^2.0.19",
"gulp-filter": "^6.0.0",
"gulp-git": "^2.5.2",
"gulp-jasmine": "^4.0.0",
"gulp-tslint": "^8.1.3",
"gulp-typescript": "^4.0.2",
"gulp-typescript": "^5.0.1",
"minimist": "^1.2.0",
"run-sequence": "^2.2.1",
"tslint": "5.13.1",
"typescript": "3.3.3333"
"tslint": "5.17.0",
"typescript": "3.5.1"
},

@@ -61,0 +62,0 @@ "types": "lib/api-types.d.ts",

@@ -17,4 +17,3 @@ # Json Schema Diff

This tool identifies what has changed between two json schema files.
These changes are classified into two groups, added and removed. Using an approach based on set theory this tool is able to calculate these differences to a high level of accuracy.
This tool identifies what has changed between two json schema files. These changes are classified into two groups, added and removed. Using an approach based on set theory this tool is able to calculate these differences to a high level of accuracy.

@@ -84,3 +83,3 @@ [KEYWORDS.md](KEYWORDS.md) contains the details of what json schema keywords are supported.

The tool will return two json schemas as output, one representing the values that were added by the destination schema and the other representing the values that were removed by the destination schema.
The tool will fail if any removed differences are detected.

@@ -90,2 +89,3 @@

*/path/to/source-schema.json*
```

@@ -104,2 +104,3 @@ {

*Invoking the tool*
```

@@ -109,2 +110,3 @@ json-schema-diff /path/to/source-schema.json /path/to/destination-schema.json

*Output*
```

@@ -127,4 +129,3 @@ Non-breaking changes found between the two schemas.

Invoke the library with the source schema and the destination schema.
These objects should be simple javascript objects and be valid according to the json schema draft-07 specification.
Invoke the library with the source schema and the destination schema. These objects should be simple javascript objects and be valid according to the json schema draft-07 specification.

@@ -131,0 +132,0 @@ For full details of the nodejs api please refer to [api-types.d.ts](lib/api-types.d.ts)

@@ -1,39 +0,31 @@

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 allOf', () => {
it('should find no differences when a schema is wrapped in allOf', async () => {
const sourceSchema: JsonSchema = {
type: 'string'
};
const destinationSchema: JsonSchema = {
allOf: [
{type: 'string'}
]
};
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
expect(diffResult.addedJsonSchema).toEqual(false);
expect(diffResult.removedJsonSchema).toEqual(false);
});
it('should find no difference when a schema is broken apart using allOf', async () => {
const sourceSchema: JsonSchema = {
properties: {
first: {
type: 'string'
},
last: {
type: 'string'
}
const testCases: DiffTestCase[] = [
{
description: 'type to equivalent allOf that type',
examples: ['a', 1],
input: {
a: {type: 'string'},
b: {allOf: [{type: 'string'}]}
},
type: 'object'
};
const destinationSchema: JsonSchema = {
allOf: [
{
output: {
added: false,
removed: false
}
},
{
description: 'multiple properties in single schema to equivalent allOf schemas of single properties',
examples: [
{}, {first: 'foo'}, {last: 'foo'}, {first: 1}, {last: 1}, {other: 1}, {first: 'foo', last: 'foo'},
{first: 'foo', last: 1}, {first: 'foo', last: 'foo', other: 1}, 'foo'
],
input: {
a: {
properties: {
first: {
type: 'string'
},
last: {
type: 'string'
}

@@ -43,73 +35,92 @@ },

},
{
properties: {
last: {
type: 'string'
b: {
allOf: [
{
properties: {
first: {
type: 'string'
}
},
type: 'object'
},
{
properties: {
last: {
type: 'string'
}
},
type: 'object'
}
},
type: 'object'
]
}
]
};
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
expect(diffResult.addedJsonSchema).toEqual(false);
expect(diffResult.removedJsonSchema).toEqual(false);
});
it('should consider type contradictions within allOf to accept nothing', async () => {
const sourceSchema: JsonSchema = {
allOf: [
{type: 'number'},
{type: 'string'}
]
};
const destinationSchema: JsonSchema = false;
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
expect(diffResult.addedJsonSchema).toEqual(false);
expect(diffResult.removedJsonSchema).toEqual(false);
});
it('should merge together type keywords with the allOf schemas', async () => {
const sourceSchema: JsonSchema = {
allOf: [
{type: 'number'}
],
type: ['number', 'string']
};
const destinationSchema: JsonSchema = {type: 'number'};
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
expect(diffResult.addedJsonSchema).toEqual(false);
expect(diffResult.removedJsonSchema).toEqual(false);
});
it('should merge together nested allOf schemas', async () => {
const sourceSchema: JsonSchema = {
allOf: [
{
},
output: {
added: false,
removed: false
}
},
{
description: 'allOf schemas of different types to equivalent false schema',
examples: [1, 'foo', true],
input: {
a: {
allOf: [
{type: ['number', 'string', 'boolean']},
{type: ['number', 'string']}
{type: 'number'},
{type: 'string'}
]
},
{
b: false
},
output: {
added: false,
removed: false
}
},
{
description: 'allOf restricting top level types to equivalent schema with the type intersection',
examples: [1, 'foo', true],
input: {
a: {
allOf: [
{type: ['number', 'string', 'boolean', 'array']},
{type: ['string']}
{type: 'number'}
],
type: ['number', 'string']
},
b: {type: 'number'}
},
output: {
added: false,
removed: false
}
},
{
description: 'nested allOf schemas to equivalent single type schema',
examples: [1, 'foo', true, [], {}],
input: {
a: {
allOf: [
{
allOf: [
{type: ['number', 'string', 'boolean']},
{type: ['number', 'string']}
]
},
{
allOf: [
{type: ['number', 'string', 'boolean', 'array']},
{type: ['string']}
]
}
]
}
]
};
const destinationSchema: JsonSchema = {type: 'string'};
},
b: {type: 'string'}
},
output: {
added: false,
removed: false
}
}
];
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
expect(diffResult.addedJsonSchema).toEqual(false);
expect(diffResult.removedJsonSchema).toEqual(false);
});
registerDiffTestCases(testCases);
});

@@ -1,60 +0,56 @@

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 boolean schemas', () => {
it('should not find differences between same boolean schemas', async () => {
const sourceSchema: JsonSchema = true;
const destinationSchema: JsonSchema = true;
const testCases: DiffTestCase[] = [
{
description: 'true schema to true schema',
examples: ['a', 1],
input: {
a: true,
b: true
},
output: {
added: false,
removed: false
}
},
{
description: 'false schema to true schema',
examples: ['a', 1],
input: {
a: false,
b: true
},
output: {
added: true,
removed: false
}
},
{
description: 'all but one type schema to true schema',
examples: ['a', 1, {}],
input: {
a: {type: ['string', 'array', 'number', 'integer', 'boolean', 'null']},
b: true
},
output: {
added: {type: ['object']},
removed: false
}
},
{
description: 'single type schema to false',
examples: ['a', 1],
input: {
a: {type: 'string'},
b: false
},
output: {
added: false,
removed: {type: ['string']}
}
}
];
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
expect(diffResult.addedJsonSchema).toEqual(false);
expect(diffResult.removedJsonSchema).toEqual(false);
});
it('should find differences between different boolean schemas', async () => {
const sourceSchema: JsonSchema = false;
const destinationSchema: JsonSchema = true;
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
expect(diffResult.addedJsonSchema).toEqual(true);
expect(diffResult.removedJsonSchema).toEqual(false);
});
it('should find an add difference when schema is changed to true from all but one type', async () => {
const allTypesButObject: SimpleTypes[] = ['string', 'array', 'number', 'integer', 'boolean', 'null'];
const sourceSchema: JsonSchema = {
type: allTypesButObject
};
const destinationSchema: JsonSchema = true;
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
const allObjects: JsonSchema = {type: ['object']};
expect(diffResult.addedJsonSchema).toEqual(allObjects);
expect(diffResult.removedJsonSchema).toEqual(false);
});
it('should find a remove difference when schema is changed to false', async () => {
const sourceSchema: JsonSchema = {type: 'string'};
const destinationSchema: JsonSchema = false;
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
const allStrings: JsonSchema = {type: ['string']};
expect(diffResult.addedJsonSchema).toEqual(false);
expect(diffResult.removedJsonSchema).toEqual(allStrings);
});
it('should find an add difference when schema is changed from false to some type', async () => {
const sourceSchema: JsonSchema = false;
const destinationSchema: JsonSchema = {type: 'string'};
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
const allStrings: JsonSchema = {type: ['string']};
expect(diffResult.addedJsonSchema).toEqual(allStrings);
expect(diffResult.removedJsonSchema).toEqual(false);
});
registerDiffTestCases(testCases);
});
import {JsonSchema} from 'json-schema-spec-types';
import {invokeDiff, invokeDiffAndExpectToFail} from '../support/invoke-diff';
import {invokeDiffAndExpectToFail} from '../support/invoke-diff';
import {DiffTestCase, registerDiffTestCases} from '../support/register-diff-test-cases';
describe('diff-schemas references', () => {
it('should follow references in source schema when detecting differences', async () => {
const sourceSchema: JsonSchema = {
definitions: {
basic_type: {
type: 'string'
}
},
properties: {
id: {
$ref: '#/definitions/basic_type'
}
},
type: 'object'
};
const destinationSchema: JsonSchema = {
properties: {
id: {
type: 'number'
}
},
type: 'object'
};
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
const allObjectsWithPropertyIdOfTypeNumber: JsonSchema = {
properties: {
id: {type: ['number']}
},
required: ['id'],
type: ['object']
};
const allObjectsWithPropertyIdOfTypeString: JsonSchema = {
properties: {
id: {type: ['string']}
},
required: ['id'],
type: ['object']
};
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithPropertyIdOfTypeNumber);
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithPropertyIdOfTypeString);
});
it('should follow references in destination schema when detecting differences', async () => {
const sourceSchema: JsonSchema = {
properties: {
id: {
type: 'string'
}
},
type: 'object'
};
const destinationSchema: JsonSchema = {
definitions: {
basic_type: {
type: 'number'
}
},
properties: {
id: {
$ref: '#/definitions/basic_type'
}
},
type: 'object'
};
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
const allObjectsWithPropertyIdOfTypeNumber: JsonSchema = {
properties: {
id: {type: ['number']}
},
required: ['id'],
type: ['object']
};
const allObjectsWithPropertyIdOfTypeString: JsonSchema = {
properties: {
id: {type: ['string']}
},
required: ['id'],
type: ['object']
};
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithPropertyIdOfTypeNumber);
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithPropertyIdOfTypeString);
});
it('should follow nested references', async () => {
const sourceSchema: JsonSchema = {
definitions: {
basic_object: {
const testCases: DiffTestCase[] = [
{
description: 'schema with string property reference to schema with inline number property',
examples: [{}, {id: 1}, {id: 'foo'}, {id: true}, 'foo'],
input: {
a: {
definitions: {
basic_type: {
type: 'string'
}
},
properties: {
content: {
id: {
$ref: '#/definitions/basic_type'

@@ -102,19 +24,49 @@ }

},
basic_type: {
type: 'string'
b: {
properties: {
id: {
type: 'number'
}
},
type: 'object'
}
},
properties: {
id: {
$ref: '#/definitions/basic_object'
output: {
added: {
properties: {
id: {type: ['number']}
},
required: ['id'],
type: ['object']
},
removed: {
properties: {
id: {type: ['string']}
},
required: ['id'],
type: ['object']
}
},
type: 'object'
};
const destinationSchema: JsonSchema = {
definitions: {
basic_object: {
}
},
{
description: 'schema with nested string reference to schema with nested number reference',
examples: [{}, {id: {}}, {id: {content: 1}}, {id: {content: 'foo'}}, {id: {content: true}}, 'foo'],
input: {
a: {
definitions: {
basic_object: {
properties: {
content: {
$ref: '#/definitions/basic_type'
}
},
type: 'object'
},
basic_type: {
type: 'string'
}
},
properties: {
content: {
$ref: '#/definitions/basic_type'
id: {
$ref: '#/definitions/basic_object'
}

@@ -124,46 +76,57 @@ },

},
basic_type: {
type: 'number'
b: {
definitions: {
basic_object: {
properties: {
content: {
$ref: '#/definitions/basic_type'
}
},
type: 'object'
},
basic_type: {
type: 'number'
}
},
properties: {
id: {
$ref: '#/definitions/basic_object'
}
},
type: 'object'
}
},
properties: {
id: {
$ref: '#/definitions/basic_object'
}
},
type: 'object'
};
const diffResult = await invokeDiff(sourceSchema, destinationSchema);
const allObjectsWithPropertyContentOfTypeString: JsonSchema = {
properties: {
id: {
output: {
added: {
properties: {
content: {type: ['number']}
id: {
properties: {
content: {type: ['number']}
},
required: ['content'],
type: ['object']
}
},
required: ['content'],
required: ['id'],
type: ['object']
}
},
required: ['id'],
type: ['object']
};
const allObjectsWithPropertyContentOfTypeNumber: JsonSchema = {
properties: {
id: {
},
removed: {
properties: {
content: {type: ['string']}
id: {
properties: {
content: {type: ['string']}
},
required: ['content'],
type: ['object']
}
},
required: ['content'],
required: ['id'],
type: ['object']
}
},
required: ['id'],
type: ['object']
};
expect(diffResult.addedJsonSchema).toEqual(allObjectsWithPropertyContentOfTypeString);
expect(diffResult.removedJsonSchema).toEqual(allObjectsWithPropertyContentOfTypeNumber);
});
}
}
];
registerDiffTestCases(testCases);
it('should fail when schema contains circular references', async () => {

@@ -170,0 +133,0 @@ const schemaWithCircularReferences: JsonSchema = {

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