Repository: TechnologyAdvice/obey
Pull request #64 (2017-01-07 06:00:44)

Conditionally require fields based on specific corresponding value
Related to https://github.com/TechnologyAdvice/deliveryhub/issues/59.

(Not sure if this is something we actually _want_ support for, but it was easy enough to implement.)

With this, `requireIf` and `requireIfNot` accept an object mapping to another field, and validate based on the given value.


Patch:
diff --git a/README.md b/README.md
index e3c4c9a..854b477 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,7 @@ const userModel = obey.model({
   suffix: { type: 'string', allowNull: true },
   phone: { type: 'phone:numeric', min: 7, max: 10 },
   phoneType: { type: 'string', requireIf: 'phone' },
+  carrier: { type: 'string', requireIf: { phoneType: 'mobile' }},
   // Array
   labels: { type: 'array', values: {
     type: 'object', keys: {
@@ -159,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`)
-* `requireIf`: Enforces the value cannot be `undefined` if a value exists for the corresponding field
-* `requireIfNot`: Enforces the value cannot be `undefined` if a value _does not_ exist for the corresponding field
+* `requireIf`: Enforces the value cannot be `undefined` if a value exists or matches a given value (`{ propertyName: 'requiredValue' }`)
+* `requireIfNot`: Enforces the value cannot be `undefined` if a value _does not_ exist or match a given value (`{ propertyName: 'requiredValue' }`)
 * `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/src/lib/validators.js b/src/lib/validators.js
index 167e668..3a26c73 100644
--- a/src/lib/validators.js
+++ b/src/lib/validators.js
@@ -80,8 +80,13 @@ const validators = {
   requireIf: function(def, value, key, errors, data) {
     const type = 'requireIf'
     const sub = def.requireIf
-    if (dot.pick(sub, data) !== undefined && value === undefined) {
-      errors.push({ type, sub, key, value, message: `Value required because '${sub}' exists`})
+    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` })
+      }
+    } else if (dot.pick(sub, data) !== undefined && value === undefined) {
+      errors.push({ type, sub, key, value, message: `Value required because '${sub}' exists` })
     }
   },
 
@@ -97,7 +102,12 @@ const validators = {
   requireIfNot: function(def, value, key, errors, data) {
     const type = 'requireIfNot'
     const sub = def.requireIfNot
-    if (dot.pick(sub, data) === undefined && value === undefined) {
+    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` })
+      }
+    } 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 d4f30bc..eaf213f 100644
--- a/test/src/lib/validators.spec.js
+++ b/test/src/lib/validators.spec.js
@@ -154,6 +154,18 @@ describe('validators', () => {
         message: 'Value required because \'address.street\' exists'
       })
     })
+    it('creates an error object if conditionally required value is undefined when corresponding field HAS the given value', () => {
+      const data = { address: { street: '123 test ave', country: 'US' } }
+      const def = { requireIf: { 'address.country': 'US' } }
+      validators.requireIf(def, undefined, 'address.zip', mockErrors, data)
+      expect(mockErrors[0]).to.deep.equal({
+        type: 'requireIf',
+        sub: { 'address.country': 'US' },
+        key: 'address.zip',
+        value: undefined,
+        message: 'Value required by existing \'address.country\' value'
+      })
+    })
   })
   describe('requireIfNot', () => {
     it('creates an error object if conditionally required value is undefined', () => {
@@ -168,6 +180,18 @@ describe('validators', () => {
         message: 'Value required because \'address.state\' is undefined'
       })
     })
+    it('creates an error object if conditionally required value is undefined when corresponding field does NOT have the given value', () => {
+      const data = { testField: 'not what we want' }
+      const def = { requireIfNot: { testField: 'what we want' } }
+      validators.requireIfNot(def, undefined, 'conditionalField', mockErrors, data)
+      expect(mockErrors[0]).to.deep.equal({
+        type: 'requireIfNot',
+        sub: { testField: 'what we want' },
+        key: 'conditionalField',
+        value: undefined,
+        message: 'Value required because \'testField\' value is not one specified'
+      })
+    })
   })
   describe('equalTo', () => {
     it('creates an error object when fields are not equal', () => {