Skip to content

Commit

Permalink
Fix requireValidation for oneOf/anyOf/allOf
Browse files Browse the repository at this point in the history
Fixes: #64
  • Loading branch information
ChALkeR committed Jun 24, 2020
1 parent 875c749 commit f53c2bd
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 7 deletions.
20 changes: 13 additions & 7 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,12 +354,15 @@ const compile = (schema, root, opts, scope, basePathRoot) => {

/* Preparation and methods, post-$ref validation will begin at the end of the function */

const hasSubValidation =
node.$ref || ['allOf', 'anyOf', 'oneOf'].some((key) => Array.isArray(node[key]))

const typeArray =
node.type === undefined ? null : Array.isArray(node.type) ? node.type : [node.type]
for (const t of typeArray || [])
enforce(typeof t === 'string' && types.has(t), 'Unknown type:', t)
// typeArray === null means no type validation, which is required if we don't have const or enum
if (typeArray === null && node.const === undefined && !node.enum)
if (typeArray === null && node.const === undefined && !node.enum && !hasSubValidation)
enforceValidation('type is required')

const typeApplicable = (...possibleTypes) =>
Expand Down Expand Up @@ -466,7 +469,10 @@ const compile = (schema, root, opts, scope, basePathRoot) => {
errorIf('!%s.test(%s)', [p, name], 'pattern mismatch')
}
consume('pattern', 'string')
} else if (typeApplicable('string') && requireStringValidation && !node.format) {
}

const stringValidated = node.format || node.pattern || hasSubValidation
if (typeApplicable('string') && requireStringValidation && !stringValidated) {
fail('pattern or format must be specified for strings, use pattern: ^[\\s\\S]*$ to opt-out')
}
}
Expand Down Expand Up @@ -498,7 +504,7 @@ const compile = (schema, root, opts, scope, basePathRoot) => {
})
}
consume('items', 'object', 'array', 'boolean')
} else if (typeApplicable('array')) {
} else if (typeApplicable('array') && !hasSubValidation) {
enforceValidation('items rule must be specified')
}

Expand Down Expand Up @@ -679,7 +685,7 @@ const compile = (schema, root, opts, scope, basePathRoot) => {
})
})
consume('additionalProperties', 'object', 'boolean')
} else if (typeApplicable('object')) {
} else if (typeApplicable('object') && !hasSubValidation) {
enforceValidation('additionalProperties rule must be specified')
}
}
Expand Down Expand Up @@ -733,15 +739,15 @@ const compile = (schema, root, opts, scope, basePathRoot) => {
consume('if', 'object', 'boolean')
}

if (node.allOf) {
if (node.allOf !== undefined) {
enforce(Array.isArray(node.allOf), 'Invalid allOf')
node.allOf.forEach((sch, key) => {
rule(current, sch, subPath('allOf', key))
})
consume('allOf', 'array')
}

if (node.anyOf && node.anyOf.length) {
if (node.anyOf !== undefined) {
enforce(Array.isArray(node.anyOf), 'Invalid anyOf')
const prev = gensym('prev')

Expand All @@ -764,7 +770,7 @@ const compile = (schema, root, opts, scope, basePathRoot) => {
consume('anyOf', 'array')
}

if (node.oneOf && node.oneOf.length) {
if (node.oneOf !== undefined) {
enforce(Array.isArray(node.oneOf), 'Invalid oneOf')
const prev = gensym('prev')
const passes = gensym('passes')
Expand Down
69 changes: 69 additions & 0 deletions test/regressions/requireValidation-allOf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use strict'

const tape = require('tape')
const { validator } = require('../../')

const fail = (t, schema, msg) => t.throws(() => validator(schema, { mode: 'strong' }), msg)
const pass = (t, schema, msg) => t.doesNotThrow(() => validator(schema, { mode: 'strong' }), msg)

tape('requireValidation works with allOf/anyOf/oneOf, objects', (t) => {
fail(t, {}, 'no validation done fails')
pass(t, { type: 'boolean' }, 'non-object type passes')
fail(t, { type: 'object' }, 'object with no additionalProperties fails')

pass(t, { type: 'object', additionalProperties: false }, 'object with additionalProperties')

pass(t, { type: 'object', allOf: [{ type: 'object', additionalProperties: false }] }, 'allOf')
pass(t, { allOf: [{ type: 'object', additionalProperties: false }] }, 'allOf with no type')
fail(t, { allOf: [{ additionalProperties: false }] }, 'oneOf with no type in allOf')

pass(t, { type: 'object', oneOf: [{ type: 'object', additionalProperties: false }] }, 'oneOf')
pass(t, { oneOf: [{ type: 'object', additionalProperties: false }] }, 'oneOf with no type')
fail(t, { oneOf: [{ additionalProperties: false }] }, 'oneOf with no type in oneOf')
fail(t, { oneOf: [{ type: 'object', additionalProperties: false }, {}] }, 'oneOf with one empty')

pass(t, { type: 'object', anyOf: [{ type: 'object', additionalProperties: false }] }, 'anyOf')
pass(t, { anyOf: [{ type: 'object', additionalProperties: false }] }, 'anyOf with no type')
fail(t, { anyOf: [{ additionalProperties: false }] }, 'oneOf with no type in anyOf')
fail(t, { anyOf: [{ type: 'object', additionalProperties: false }, {}] }, 'anyOf with one empty')

t.end()
})

tape('requireValidation works with allOf/anyOf/oneOf, arrays', (t) => {
fail(t, {}, 'no validation done fails')
pass(t, { type: 'boolean' }, 'non-array type passes')
fail(t, { type: 'array' }, 'array with no additionalItems fails')

pass(t, { type: 'array', items: [], additionalItems: false }, 'array with additionalItems')

pass(t, { type: 'array', allOf: [{ type: 'array', items: [], additionalItems: false }] }, 'allOf')
pass(t, { allOf: [{ type: 'array', items: [], additionalItems: false }] }, 'allOf with no type')
fail(t, { allOf: [{ items: [], additionalItems: false }] }, 'oneOf with no type in allOf')

pass(t, { type: 'array', oneOf: [{ type: 'array', items: [], additionalItems: false }] }, 'oneOf')
pass(t, { oneOf: [{ type: 'array', items: [], additionalItems: false }] }, 'oneOf with no type')
fail(t, { oneOf: [{ items: [], additionalItems: false }] }, 'oneOf with no type in oneOf')

pass(t, { type: 'array', anyOf: [{ type: 'array', items: [], additionalItems: false }] }, 'anyOf')
pass(t, { anyOf: [{ type: 'array', items: [], additionalItems: false }] }, 'anyOf with no type')
fail(t, { anyOf: [{ items: [], additionalItems: false }] }, 'oneOf with no type in anyOf')

t.end()
})

tape('requireValidation works with $ref', (t) => {
const good = { type: 'object', additionalProperties: false }
const bad0 = {}
const bad1 = { type: 'object' }
const bad2 = { additionalProperties: false }
const $defs = { good, bad0, bad1, bad2 }

fail(t, { $defs }, 'no validation done fails')
fail(t, { $defs, $ref: '#/$defs/bad0' }, 'ref to empty fails')
fail(t, { $defs, $ref: '#/$defs/bad1' }, 'ref to only type fails')
fail(t, { $defs, $ref: '#/$defs/bad2' }, 'ref to only additionalProperties fails')
pass(t, { $defs, $ref: '#/$defs/good' }, 'ref to type + additionalProperties passes')

t.end()
})

0 comments on commit f53c2bd

Please sign in to comment.