json-schema-merge-allof
Advanced tools
Comparing version 0.5.8 to 0.6.0
{ | ||
"name": "json-schema-merge-allof", | ||
"version": "0.5.8", | ||
"version": "0.6.0", | ||
"description": "Simplify your schema by combining allOf into the root schema, safely.", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
@@ -94,2 +94,6 @@ # json-schema-merge-allof [![Build Status](https://travis-ci.org/mokkabonna/json-schema-merge-allof.svg?branch=master)](https://travis-ci.org/mokkabonna/json-schema-merge-allof) [![Coverage Status](https://coveralls.io/repos/github/mokkabonna/json-schema-merge-allof/badge.svg?branch=master)](https://coveralls.io/github/mokkabonna/json-schema-merge-allof?branch=master) | ||
## Options | ||
**ignoreAdditionalProperties** default **false** | ||
Allows you to combine schema properties even though some schemas have `additionalProperties: false` This is the most common issue people face when trying to expand schemas using allOf and a limitation of the json schema spec. Be aware though that the schema produced will allow more than the original schema. But this is useful if just want to combine schemas using allOf as if additionalProperties wasn't false during the merge process. The resulting schema will still get additionalProperties set to false. | ||
**resolvers** Object | ||
@@ -101,3 +105,3 @@ Override any default resolver like this: | ||
resolvers: { | ||
title: function(values, key, mergeSchemas) { | ||
title: function(values, path, mergeSchemas, options) { | ||
// choose what title you want to be used based on the conflicting values | ||
@@ -112,6 +116,34 @@ // resolvers MUST return a value other than undefined | ||
- **values**: an array of the conflicting values that need to be resolved | ||
- **key** the name of the keyword that caused the resolver to be called (useful if you use the same resolver for multiple keywords) | ||
- **values** an array of the conflicting values that need to be resolved | ||
- **path** an array of strings containing the path to the position in the schema that caused the resolver to be called (useful if you use the same resolver for multiple keywords, or want to implement specific logic for custom paths) | ||
- **mergeSchemas** a function you can call that merges an array of schemas | ||
- **options** the options mergeAllOf was called with | ||
### Combined resolvers | ||
No separate resolver is called for patternProperties and additionalProperties, only the properties resolver is called. Same for additionalItems, only items resolver is called. This is because those keywords need to be resolved together as they affect each other. | ||
Those two resolvers are expected to return an object containing the resolved values of all the associated keywords. The keys must be the name of the keywords. So the properties resolver need to return an object like this containing the resolved values for each keyword: | ||
```js | ||
{ | ||
properties: ..., | ||
patternProperties: ..., | ||
additionalProperties: ..., | ||
} | ||
``` | ||
Also the resolve function is not passed **mergeSchemas**, but an object **mergers** that contains mergers for each of the related keywords. So properties get passed an object like this: | ||
```js | ||
var mergers = { | ||
properties: function mergeSchemas(schemas, childSchemaName){...}, | ||
patternProperties: function mergeSchemas(schemas, childSchemaName){...}, | ||
additionalProperties: function mergeSchemas(schemas){...}, | ||
} | ||
``` | ||
Some of the mergers requires you to supply a string of the name or index of the subschema you are currently merging. This is to make sure the path passed to child resolvers are correct. | ||
### Default resolver | ||
You can set a default resolver that catches any unknown keyword. Let's say you want to use the same strategy as the ones for the meta keywords, to use the first value found. You can accomplish that like this: | ||
@@ -129,7 +161,3 @@ | ||
**ignoreAdditionalProperties** default **false** | ||
Allows you to combine schema properties even though some schemas have `additionalProperties: false` This is the most common issue people face when trying to expand schemas using allOf and a limitation of the json schema spec. Be aware though that the schema produced will allow more than the original schema. But this is useful if just want to combine schemas using allOf as if additionalProperties wasn't false during the merge process. The resulting schema will still get additionalProperties set to false. | ||
## Resolvers | ||
@@ -136,0 +164,0 @@ |
147
src/index.js
@@ -43,5 +43,3 @@ var cloneDeep = require('lodash/cloneDeep') | ||
[key]: a | ||
}, { | ||
[key]: b | ||
}) | ||
}, {[key]: b}) | ||
} | ||
@@ -70,3 +68,5 @@ } | ||
return subSchemas.map(function(sub) { | ||
if (!sub) return | ||
if (!sub) { | ||
return | ||
} | ||
@@ -87,5 +87,5 @@ if (Array.isArray(sub.items)) { | ||
function tryMergeSchemaGroups(schemaGroups, mergeSchemas) { | ||
return schemaGroups.map(function(schemas) { | ||
return schemaGroups.map(function(schemas, index) { | ||
try { | ||
return mergeSchemas(schemas) | ||
return mergeSchemas(schemas, index) | ||
} catch (e) { | ||
@@ -99,3 +99,5 @@ return undefined | ||
return subSchemas.map(function(sub) { | ||
if (!sub) return | ||
if (!sub) { | ||
return | ||
} | ||
if (Array.isArray(sub.items)) { | ||
@@ -139,10 +141,12 @@ return sub.additionalItems | ||
function throwIncompatible(values, key) { | ||
function throwIncompatible(values, paths) { | ||
var asJSON | ||
try { | ||
asJSON = JSON.stringify(values, null, 2) | ||
asJSON = values.map(function(val) { | ||
return JSON.stringify(val, null, 2) | ||
}).join('\n') | ||
} catch (variable) { | ||
asJSON = values.join(', ') | ||
} | ||
throw new Error('Could not resolve values for keyword:"' + key + '". They are probably incompatible. Values: ' + asJSON) | ||
throw new Error('Could not resolve values for path:"' + paths.join('.') + '". They are probably incompatible. Values: \n' + asJSON) | ||
} | ||
@@ -160,3 +164,13 @@ | ||
function callGroupResolver(keys, resolverName, schemas, mergeSchemas, options) { | ||
function createRequiredSubMerger(mergeSchemas, key, parents) { | ||
return function(schemas, subKey) { | ||
if (subKey === undefined) { | ||
throw new Error('You need to call merger with a key for the property name or index if array.') | ||
} | ||
subKey = String(subKey) | ||
return mergeSchemas(schemas, null, parents.concat(key, subKey)) | ||
} | ||
} | ||
function callGroupResolver(keys, resolverName, schemas, mergeSchemas, options, parents) { | ||
if (keys.length) { | ||
@@ -177,6 +191,28 @@ var resolver = options.resolvers[resolverName] | ||
var result = resolver(compacted, resolverName, mergeSchemas, options) | ||
var related = resolverName === 'properties' | ||
? propertyRelated | ||
: itemsRelated | ||
var mergers = related.reduce(function(all, key) { | ||
if (contains(schemaGroupProps, key)) { | ||
all[key] = createRequiredSubMerger(mergeSchemas, key, parents) | ||
} else { | ||
all[key] = function(schemas) { | ||
return mergeSchemas(schemas, null, parents.concat(key)) | ||
} | ||
} | ||
return all | ||
}, {}) | ||
if (resolverName === 'items') { | ||
mergers.itemsArray = createRequiredSubMerger(mergeSchemas, 'items', parents) | ||
mergers.items = function(schemas) { | ||
return mergeSchemas(schemas, null, parents.concat('items')) | ||
} | ||
} | ||
var result = resolver(compacted, parents.concat(resolverName), mergers, options) | ||
if (!isPlainObject(result)) { | ||
throwIncompatible(compacted, resolverName) | ||
throwIncompatible(compacted, parents.concat(resolverName)) | ||
} | ||
@@ -191,9 +227,13 @@ | ||
var allKeys = allUniqueKeys(source || group) | ||
var extractor = source ? getItemSchemas : getValues | ||
var extractor = source | ||
? getItemSchemas | ||
: getValues | ||
return allKeys.reduce(function(all, key) { | ||
var schemas = extractor(group, key) | ||
var compacted = uniqWith(schemas.filter(notUndefined), compare) | ||
all[key] = mergeSchemas(compacted) | ||
all[key] = mergeSchemas(compacted, key) | ||
return all | ||
}, source ? [] : {}) | ||
}, source | ||
? [] | ||
: {}) | ||
} | ||
@@ -218,5 +258,3 @@ | ||
function createRequiredMetaArray(arr) { | ||
return { | ||
required: arr | ||
} | ||
return {required: arr} | ||
} | ||
@@ -238,6 +276,8 @@ | ||
var defaultResolvers = { | ||
type(compacted, key) { | ||
type(compacted) { | ||
if (compacted.some(Array.isArray)) { | ||
var normalized = compacted.map(function(val) { | ||
return Array.isArray(val) ? val : [val] | ||
return Array.isArray(val) | ||
? val | ||
: [val] | ||
}) | ||
@@ -253,3 +293,3 @@ var common = intersection.apply(null, normalized) | ||
}, | ||
properties(values, key, mergeSchemas, options) { | ||
properties(values, key, mergers, options) { | ||
// first get rid of all non permitted properties | ||
@@ -267,3 +307,5 @@ if (!options.ignoreAdditionalProperties) { | ||
additionalKeys.forEach(function(key) { | ||
other.properties[key] = mergeSchemas([other.properties[key], subSchema.additionalProperties]) | ||
other.properties[key] = mergers.properties([ | ||
other.properties[key], subSchema.additionalProperties | ||
], key) | ||
}) | ||
@@ -288,5 +330,5 @@ }) | ||
var returnObject = { | ||
additionalProperties: mergeSchemas(values.map(s => s.additionalProperties)), | ||
patternProperties: mergeSchemaGroup(values.map(s => s.patternProperties), mergeSchemas), | ||
properties: mergeSchemaGroup(values.map(s => s.properties), mergeSchemas) | ||
additionalProperties: mergers.additionalProperties(values.map(s => s.additionalProperties)), | ||
patternProperties: mergeSchemaGroup(values.map(s => s.patternProperties), mergers.patternProperties), | ||
properties: mergeSchemaGroup(values.map(s => s.properties), mergers.properties) | ||
} | ||
@@ -300,3 +342,3 @@ | ||
}, | ||
dependencies(compacted, key, mergeSchemas) { | ||
dependencies(compacted, paths, mergeSchemas) { | ||
var allChildren = allUniqueKeys(compacted) | ||
@@ -317,3 +359,3 @@ | ||
var arrayMetaScheams = innerArrays.map(createRequiredMetaArray) | ||
all[childKey] = mergeSchemas(innerSchemas.concat(arrayMetaScheams)) | ||
all[childKey] = mergeSchemas(innerSchemas.concat(arrayMetaScheams), childKey) | ||
} | ||
@@ -325,7 +367,7 @@ return all | ||
all[childKey] = mergeSchemas(innerCompacted) | ||
all[childKey] = mergeSchemas(innerCompacted, childKey) | ||
return all | ||
}, {}) | ||
}, | ||
items(values, key, mergeSchemas) { | ||
items(values, paths, mergers) { | ||
var items = values.map(s => s.items) | ||
@@ -336,5 +378,5 @@ var itemsCompacted = items.filter(notUndefined) | ||
if (itemsCompacted.every(isSchema)) { | ||
returnObject.items = mergeSchemas(items) | ||
returnObject.items = mergers.items(items) | ||
} else { | ||
returnObject.items = mergeSchemaGroup(values, mergeSchemas, items) | ||
returnObject.items = mergeSchemaGroup(values, mergers.itemsArray, items) | ||
} | ||
@@ -350,3 +392,3 @@ | ||
if (schemasAtLastPos) { | ||
returnObject.additionalItems = mergeSchemas(schemasAtLastPos) | ||
returnObject.additionalItems = mergers.additionalItems(schemasAtLastPos) | ||
} | ||
@@ -360,3 +402,3 @@ | ||
}, | ||
oneOf(compacted, key, mergeSchemas) { | ||
oneOf(compacted, paths, mergeSchemas) { | ||
var combinations = getAnyOfCombinations(cloneDeep(compacted)) | ||
@@ -371,11 +413,8 @@ var result = tryMergeSchemaGroups(combinations, mergeSchemas) | ||
not(compacted) { | ||
return { | ||
anyOf: compacted | ||
} | ||
return {anyOf: compacted} | ||
}, | ||
pattern(compacted, key, mergeSchemas, totalSchemas, reportUnresolved) { | ||
pattern(compacted, paths, mergeSchemas, options, reportUnresolved) { | ||
var key = paths.pop() | ||
reportUnresolved(compacted.map(function(regexp) { | ||
return { | ||
[key]: regexp | ||
} | ||
return {[key]: regexp} | ||
})) | ||
@@ -392,3 +431,3 @@ }, | ||
}, | ||
enum(compacted, key) { | ||
enum(compacted) { | ||
var enums = intersectionWith.apply(null, compacted.concat(isEqual)) | ||
@@ -434,6 +473,8 @@ if (enums.length) { | ||
function mergeSchemas(schemas, base) { | ||
function mergeSchemas(schemas, base, parents) { | ||
schemas = cloneDeep(schemas.filter(notUndefined)) | ||
parents = parents || [] | ||
var merged = isPlainObject(base) | ||
? base : {} | ||
? base | ||
: {} | ||
@@ -490,4 +531,14 @@ // return undefined, an empty schema | ||
var merger | ||
// get custom merger for groups | ||
if (contains(schemaGroupProps, key) || contains(schemaArrays, key)) { | ||
merger = createRequiredSubMerger(mergeSchemas, key, parents) | ||
} else { | ||
merger = function(schemas) { | ||
return mergeSchemas(schemas, null, parents.concat(key)) | ||
} | ||
} | ||
var calledWithArray = false | ||
merged[key] = resolver(compacted, key, mergeSchemas, totalSchemas, function(unresolvedSchemas) { | ||
merged[key] = resolver(compacted, parents.concat(key), merger, options, function(unresolvedSchemas) { | ||
calledWithArray = Array.isArray(unresolvedSchemas) | ||
@@ -498,3 +549,3 @@ return addToAllOf(unresolvedSchemas) | ||
if (merged[key] === undefined && !calledWithArray) { | ||
throwIncompatible(compacted, key) | ||
throwIncompatible(compacted, parents.concat(key)) | ||
} else if (merged[key] === undefined) { | ||
@@ -506,4 +557,4 @@ delete merged[key] | ||
Object.assign(merged, callGroupResolver(propertyKeys, 'properties', schemas, mergeSchemas, options)) | ||
Object.assign(merged, callGroupResolver(itemKeys, 'items', schemas, mergeSchemas, options)) | ||
Object.assign(merged, callGroupResolver(propertyKeys, 'properties', schemas, mergeSchemas, options, parents)) | ||
Object.assign(merged, callGroupResolver(itemKeys, 'items', schemas, mergeSchemas, options, parents)) | ||
@@ -510,0 +561,0 @@ function addToAllOf(unresolvedSchemas) { |
@@ -752,3 +752,3 @@ var chai = require('chai') | ||
it('merges contains using allOf', function() { | ||
it('merges contains', function() { | ||
var result = merger({ | ||
@@ -857,3 +857,3 @@ allOf: [{}, { | ||
it('merges multipleOf by finding common lowest number', function() { | ||
it('merges multipleOf by finding lowest common multiple (LCM)', function() { | ||
var result = merger({ | ||
@@ -860,0 +860,0 @@ allOf: [{}, { |
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
214
86365
17
3142