diff --git a/README.md b/README.md index 72111c6..cd8e9a1 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Custom JSON-Schema keywords for [Ajv](https://github.com/epoberezkin/ajv) valida - [uniqueItemProperties](#uniqueitemproperties) - [Keywords for objects](#keywords-for-objects) - [allRequired](allrequired) + - [anyRequired](anyrequired) - [patternRequired](#patternrequired) - [prohibited](#prohibited) - [deepProperties](#deepproperties) @@ -338,6 +339,28 @@ var invalidDataList = [ {}, { foo: 1 }, { bar: 2 } ]; ``` +#### `anyRequired` + +This keyword allows to require the presence of any (at least one) property from the list. + +This keyword applies only to objects. If the data is not an object, the validation succeeds. + +The value of this keyword must be an array of strings, each string being a property name. For data object to be valid at least one of the properties in this array should be present in the object. + +```javascript +var schema = { + anyRequired: ['foo', 'bar'] +}; + +var validData = { foo: 1 }; +var alsoValidData = { foo: 1, bar: 2 }; + +var invalidDataList = [ {}, { baz: 3 } ]; +``` + +__Please note__: By combining `anyRequired` with `maxProperties: 1` you can achieve that exactly one property from the list is required to be present for the data object to pass validation. + + #### `patternRequired` This keyword allows to require the presence of properties that match some pattern(s). @@ -379,6 +402,8 @@ var invalidDataList = [ ]; ``` +__Please note__: `{prohibited: ['foo', 'bar']}` is equivalent to `{not: {anyRequired: ['foo', 'bar']}}` (i.e. it has the same validation result for any data). + #### `deepProperties` diff --git a/keywords/anyRequired.js b/keywords/anyRequired.js new file mode 100644 index 0000000..4cc973b --- /dev/null +++ b/keywords/anyRequired.js @@ -0,0 +1,24 @@ +'use strict'; + +module.exports = function defFunc(ajv) { + defFunc.definition = { + type: 'object', + macro: function (schema) { + if (schema.length == 0) return {}; + if (schema.length == 1) return { required: schema }; + var schemas = schema.map(function (prop) { + return { required: [prop] }; + }); + return { anyOf: schemas }; + }, + metaSchema: { + type: 'array', + items: { + type: 'string' + } + } + }; + + ajv.addKeyword('anyRequired', defFunc.definition); + return ajv; +}; diff --git a/keywords/index.js b/keywords/index.js index 6c22262..900b4ec 100644 --- a/keywords/index.js +++ b/keywords/index.js @@ -7,6 +7,7 @@ module.exports = { 'typeof': require('./typeof'), dynamicDefaults: require('./dynamicDefaults'), allRequired: require('./allRequired'), + anyRequired: require('./anyRequired'), prohibited: require('./prohibited'), uniqueItemProperties: require('./uniqueItemProperties'), deepProperties: require('./deepProperties'), diff --git a/spec/schema-tests.spec.js b/spec/schema-tests.spec.js index da8bd17..d398eaa 100644 --- a/spec/schema-tests.spec.js +++ b/spec/schema-tests.spec.js @@ -7,6 +7,7 @@ var defineKeywords = require('..'); var ajvs = [ defineKeywords(getAjv(), [ 'allRequired', + 'anyRequired', 'deepProperties', 'deepRequired', 'formatMaximum', diff --git a/spec/tests/anyRequired.json b/spec/tests/anyRequired.json new file mode 100644 index 0000000..35671eb --- /dev/null +++ b/spec/tests/anyRequired.json @@ -0,0 +1,86 @@ +[ + { + "description": "anyRequired requires that at least on property in the list is present", + "schema": { + "anyRequired": ["foo"] + }, + "tests": [ + { + "description": "property present is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "property present with an additional property is valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "no property present is invalid", + "data": { "baz": 1 }, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "multiple properties in prohibited", + "schema": { + "anyRequired": ["foo", "bar"] + }, + "tests": [ + { + "description": "property present is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "property present with an additional property is valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "all properties present is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "all properties present with an additional property is valid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": true + }, + { + "description": "no property present is invalid", + "data": {"baz": 3}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "anyRequired: [] is always valid", + "schema": { + "anyRequired": [] + }, + "tests": [ + { + "description": "any object is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + } +]