Skip to content

Commit

Permalink
WIP if-then-else
Browse files Browse the repository at this point in the history
  • Loading branch information
mokkabonna authored and Martin Hansen committed Feb 16, 2021
1 parent 7edaab5 commit 274bea4
Show file tree
Hide file tree
Showing 2 changed files with 335 additions and 6 deletions.
50 changes: 44 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var without = require('lodash/without')
var withoutArr = (arr, ...rest) => without.apply(null, [arr].concat(flatten(rest)))
var isPropertyRelated = (key) => contains(propertyRelated, key)
var isItemsRelated = (key) => contains(itemsRelated, key)
var isConditionalRelated = (key) => contains(conditonalRelated, key)
var contains = (arr, val) => arr.indexOf(val) !== -1
var isEmptySchema = (obj) => (!keys(obj).length) && obj !== false && obj !== true
var isSchema = (val) => isPlainObject(val) || val === true || val === false
Expand Down Expand Up @@ -174,9 +175,14 @@ function callGroupResolver(keys, resolverName, schemas, mergeSchemas, options, p
}, {})
}).filter(notUndefined), compare)

var related = resolverName === 'properties'
? propertyRelated
: itemsRelated
const map = {
properties: propertyRelated,
items: itemsRelated,
if: conditonalRelated
}

const isIf = resolverName === 'if'
const related = map[resolverName]

var mergers = related.reduce(function(all, key) {
if (contains(schemaGroupProps, key)) {
Expand All @@ -202,7 +208,11 @@ function callGroupResolver(keys, resolverName, schemas, mergeSchemas, options, p
throwIncompatible(compacted, parents.concat(resolverName))
}

return cleanupReturnValue(result)
if (isIf) {
return result
} else {
return cleanupReturnValue(result)
}
}
}

Expand Down Expand Up @@ -244,6 +254,7 @@ function createRequiredMetaArray(arr) {

var propertyRelated = ['properties', 'patternProperties', 'additionalProperties']
var itemsRelated = ['items', 'additionalItems']
var conditonalRelated = ['if', 'then', 'else']
var schemaGroupProps = ['properties', 'patternProperties', 'definitions', 'dependencies']
var schemaArrays = ['anyOf', 'oneOf']
var schemaProps = [
Expand Down Expand Up @@ -402,6 +413,27 @@ var defaultResolvers = {
if (enums.length) {
return sortBy(enums)
}
},
if(values, props, mergers, options) {
const allWithConditional = values.filter(schema =>
conditonalRelated.some(keyword => schema.hasOwnProperty(keyword)))

// merge sub schemas completely
// if,then,else must not be merged to the base schema, but if they contain allOf themselves, that should be merged
function merge(schema) {
const obj = {}
if (schema.hasOwnProperty('if')) obj.if = mergers.if([schema.if])
if (schema.hasOwnProperty('then')) obj.then = mergers.then([schema.then])
if (schema.hasOwnProperty('else')) obj.else = mergers.else([schema.else])
return obj
}

// first schema with any of the 3 keywords is used as base
const first = merge(allWithConditional.shift())
return allWithConditional.reduce((all, schema) => {
all.allOf = (all.allOf || []).concat(merge(schema))
return all
}, first)
}
}

Expand Down Expand Up @@ -475,6 +507,9 @@ function merger(rootSchema, options, totalSchemas) {
var itemKeys = allKeys.filter(isItemsRelated)
pullAll(allKeys, itemKeys)

var conditonalKeys = allKeys.filter(isConditionalRelated)
pullAll(allKeys, conditonalKeys)

allKeys.forEach(function(key) {
var values = getValues(schemas, key)
var compacted = uniqWith(values.filter(notUndefined), compareProp(key))
Expand Down Expand Up @@ -506,10 +541,12 @@ function merger(rootSchema, options, totalSchemas) {
}

var calledWithArray = false
merged[key] = resolver(compacted, parents.concat(key), merger, options, function(unresolvedSchemas) {
const reportUnresolved = unresolvedSchemas => {
calledWithArray = Array.isArray(unresolvedSchemas)
return addToAllOf(unresolvedSchemas)
})
}

merged[key] = resolver(compacted, parents.concat(key), merger, options, reportUnresolved)

if (merged[key] === undefined && !calledWithArray) {
throwIncompatible(compacted, parents.concat(key))
Expand All @@ -521,6 +558,7 @@ function merger(rootSchema, options, totalSchemas) {

Object.assign(merged, callGroupResolver(propertyKeys, 'properties', schemas, mergeSchemas, options, parents))
Object.assign(merged, callGroupResolver(itemKeys, 'items', schemas, mergeSchemas, options, parents))
Object.assign(merged, callGroupResolver(conditonalKeys, 'if', schemas, mergeSchemas, options, parents))

function addToAllOf(unresolvedSchemas) {
merged.allOf = mergeWithArray(merged.allOf, unresolvedSchemas)
Expand Down
291 changes: 291 additions & 0 deletions test/specs/if-then-else.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
var chai = require('chai')
var merger = require('../../src')
var sinon = require('sinon')
var _ = require('lodash')
var expect = chai.expect
var Ajv = require('ajv').default

var ajv = new Ajv()
describe.only('if then else', function() {
it('moves the if then else to the base schema if none there', () => {
const result = merger({
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

expect(result).to.eql({
if: {
required: ['prop1']
},
then: {},
else: {}
})
})

it('does NOT move the if then else to the base schema if something already there', () => {
const result = merger({
if: {
minimum: 5
},
then: {
maximum: 2
},
else: {
maximum: 10
},
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

expect(result).to.eql({
if: {
minimum: 5
},
then: {
maximum: 2
},
else: {
maximum: 10
},
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})
})

it('moves the unaffected keywords to the base schema', () => {
const result = merger({
properties: {
name: {
type: 'string',
minLength: 3
}
},
if: {
minimum: 5
},
then: {
maximum: 2
},
else: {
maximum: 10
},
allOf: [{
properties: {
name: {
type: 'string',
minLength: 5
}
},
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

expect(result).to.eql({
properties: {
name: {
type: 'string',
minLength: 5
}
},
if: {
minimum: 5
},
then: {
maximum: 2
},
else: {
maximum: 10
},
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})
})

it('should not move to base schema if only some keywords are not present', () => {
const result = merger({
else: false,
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

expect(result).to.eql({
else: false,
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

const result2 = merger({
then: false,
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

expect(result2).to.eql({
then: false,
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

const result3 = merger({
if: false,
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

expect(result3).to.eql({
if: false,
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})
})

it('works with undefined value, it is as if not there. NOT the same as empty schema', () => {
const result = merger({
if: undefined,
then: undefined,
else: undefined,
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})

expect(result).to.eql({
if: {
required: ['prop1']
},
then: {},
else: {}
})
})

it('removes empty allOf', () => {
const result = merger({
if: {
required: ['prop1']
},
then: {},
else: {},
allOf: [{
properties: {
name: {
type: 'string'
}
}
}]
})

expect(result).to.eql({
properties: {
name: {
type: 'string'
}
},
if: {
required: ['prop1']
},
then: {},
else: {}
})
})

it('works with resolver that does not manage to resolve it\'s schemas', () => {
const result = merger({
required: ['123'],
if: {},
then: {},
else: {},
allOf: [{
required: ['234'],
if: {
required: ['prop1']
},
then: {},
else: {}
}]

}, {
resolvers: {
foo(values, paths, mergeSchemas, options, reportUnresolved) {
var key = paths.pop()
reportUnresolved(values.map((val) => {
return {
[key]: val
}
}))
}
}
})

expect(result).to.eql({
required: ['123', '234'],
if: {},
then: {},
else: {},
allOf: [{
if: {
required: ['prop1']
},
then: {},
else: {}
}]
})
})
})

0 comments on commit 274bea4

Please sign in to comment.