Repository: TechnologyAdvice/obey
Pull request #70 (2017-10-07 05:32:37)

feat(validators): expand requiredIf(Not) validators to support multiple values
Users can already conditionally require a field based on the specific value of another:
```
{
  foo: { type: 'string', requiredIf: { test: 'bar' } },
  test: { type: 'string' }
}
```

This PR adds support for validating against an array of potential field values:
```
{
  foo: { type: 'string', requiredIf: { test: [ 'bar', 'bizz' ] } },
  test: { type: 'string' }
}
```


Patch:
diff --git a/.snyk b/.snyk
index 2e3a857..2290313 100644
--- a/.snyk
+++ b/.snyk
@@ -1,7 +1,9 @@
+# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
+# ignores vulnerabilities until expiry date; change duration by modifying expiry date
 ignore:
   'npm:marked:20150520':
-    - 'jsdoc > marked':
+    - jsdoc > marked:
         reason: Dev Dependecy
         expires: '2016-05-28T00:25:33.252Z'
 patch: {}
-version: v1
+version: v1.7.1
diff --git a/README.md b/README.md
index e14801f..460d790 100644
--- a/README.md
+++ b/README.md
@@ -160,8 +160,8 @@ When setting definitions for rules or model properties, the following are suppor
 * `min`: The minimum character length for a string, lowest number, or minimum items in array
 * `max`: The maximum character length for a string, highest number, or maximum items in array
 * `required`: Enforces the value cannot be `undefined` during validation (default `false`)
-* `requiredIf`: Enforces the value cannot be `undefined` if a value exists or matches a given value (`{ propertyName: 'requiredValue' }`)
-* `requiredIfNot`: Enforces the value cannot be `undefined` if a value _does not_ exist or match a given value (`{ propertyName: 'requiredValue' }`)
+* `requiredIf`: Enforces the value cannot be `undefined` if a value exists or matches a given value (`{ propertyName: 'requiredValue' }`), or any of a list of values (`{ propertyName: [ 'val1', 'val2' ] }`)
+* `requiredIfNot`: Enforces the value cannot be `undefined` if a value _does not_ exist or match a given value (`{ propertyName: 'requiredValue' }`), or any of a list of values (`{ propertyName: [ 'val1', 'val2' ] }`)
 * `equalTo`: Enforces the value to be the same as the corresponding field
 * `allow`: Object, array or single value representing allowed value(s), see [Allow](#allow)
 * `allowNull`: Accepts a null value or processes specified type
diff --git a/package.json b/package.json
index d529b6c..d6cb297 100644
--- a/package.json
+++ b/package.json
@@ -51,13 +51,13 @@
     "pre-commit": "~1.1.1",
     "sinon": "~1.15.4",
     "sinon-chai": "~2.8.0",
-    "snyk": "^1.11.1"
+    "snyk": "^1.42.2"
   },
   "dependencies": {
-    "bluebird": "^3.3.4",
-    "dot-object": "^1.5.4",
+    "bluebird": "^3.5.1",
+    "dot-object": "^1.7.0",
     "lodash": "^4.6.1",
-    "snyk": "^1.23.3"
+    "snyk": "^1.42.4"
   },
   "pre-commit": [
     "test"
@@ -67,4 +67,4 @@
       "es2015"
     ]
   }
-}
+}
\ No newline at end of file
diff --git a/src/lib/validators.js b/src/lib/validators.js
index 9975778..07e80c8 100644
--- a/src/lib/validators.js
+++ b/src/lib/validators.js
@@ -1,7 +1,7 @@
 /*
  * Copyright (c) 2015 TechnologyAdvice
  */
-/* eslint no-console: 0 */
+/* eslint no-console: 0, consistent-return: 0 */
 import dot from 'dot-object'
 
 const validators = {
@@ -86,9 +86,14 @@ const validators = {
     const sub = def.requiredIf
     if (typeof sub === 'object') {
       const field = Object.keys(sub)[0]
-      if (dot.pick(field, data) === sub[field] && value === undefined) {
-        errors.push({ type, sub, key, value, message: `Value required by existing '${field}' value` })
-      }
+      const fieldArr = Array.isArray(sub[field]) ? sub[field] : [ sub[field] ]
+      fieldArr.some(val => {
+        /* istanbul ignore else */
+        if (dot.pick(field, data) === val && value === undefined) {
+          errors.push({ type, sub, key, value, message: `Value required by existing '${field}' value` })
+          return true
+        }
+      })
     } else if (dot.pick(sub, data) !== undefined && value === undefined) {
       errors.push({ type, sub, key, value, message: `Value required because '${sub}' exists` })
     }
@@ -117,9 +122,14 @@ const validators = {
     const sub = def.requiredIfNot
     if (typeof sub === 'object') {
       const field = Object.keys(sub)[0]
-      if (dot.pick(field, data) !== sub[field] && value === undefined) {
-        errors.push({ type, sub, key, value, message: `Value required because '${field}' value is not one specified` })
-      }
+      const fieldArr = Array.isArray(sub[field]) ? sub[field] : [ sub[field] ]
+      fieldArr.some(val => {
+        /* istanbul ignore else */
+        if (dot.pick(field, data) !== val && value === undefined) {
+          errors.push({ type, sub, key, value, message: `Value required because '${field}' value is not one specified` })
+          return true
+        }
+      })
     } else if (dot.pick(sub, data) === undefined && value === undefined) {
       errors.push({ type, sub, key, value, message: `Value required because '${sub}' is undefined`})
     }


Test patch:
diff --git a/test/src/lib/validators.spec.js b/test/src/lib/validators.spec.js
index f7b634b..9b9b1b6 100644
--- a/test/src/lib/validators.spec.js
+++ b/test/src/lib/validators.spec.js
@@ -182,6 +182,18 @@ describe('validators', () => {
         message: 'Value required by existing \'address.country\' value'
       })
     })
+    it('creates an error object if conditionally required value is undefined when corresponding field has any of the given values', () => {
+      const data = { address: { street: '123 test ave', country: 'US' } }
+      const def = { requiredIf: { 'address.country': [ 'US', 'Canada' ] } }
+      validators.requiredIf(def, undefined, 'address.zip', mockErrors, data)
+      expect(mockErrors[0]).to.deep.equal({
+        type: 'requiredIf',
+        sub: { 'address.country': [ 'US', 'Canada' ] },
+        key: 'address.zip',
+        value: undefined,
+        message: 'Value required by existing \'address.country\' value'
+      })
+    })
   })
   describe('requireIf', () => {
     let stub
@@ -228,6 +240,18 @@ describe('validators', () => {
         message: 'Value required because \'testField\' value is not one specified'
       })
     })
+    it('creates an error object if conditionally required value is undefined when corresponding field does NOT have any of the given value', () => {
+      const data = { testField: 'not what we want' }
+      const def = { requiredIfNot: { testField: [ 'what we want', 'something else we want' ] } }
+      validators.requiredIfNot(def, undefined, 'conditionalField', mockErrors, data)
+      expect(mockErrors[0]).to.deep.equal({
+        type: 'requiredIfNot',
+        sub: { testField: [ 'what we want', 'something else we want' ] },
+        key: 'conditionalField',
+        value: undefined,
+        message: 'Value required because \'testField\' value is not one specified'
+      })
+    })
   })
   describe('requireIfNot', () => {
     let stub