Repository: TechnologyAdvice/obey (js)

Issue:
New feature request: "equalsTo" validator
When I validate registration form, I want to validate password confirmation with schema like this:
```js
let model = obey.model({
	/* Unrelated fields omitted */
	password: {
		type: 'string',
		min: 8
	},
	passwordConfirm: {
		type: 'string',
		equalsTo: 'password'
	}
});
```

Pull request description:
feat(equalTo): Add equalTo validator
Fixes #58 

Patch:
diff --git a/README.md b/README.md
index b431f89..e3c4c9a 100644
--- a/README.md
+++ b/README.md
@@ -66,6 +66,7 @@ const userModel = obey.model({
   id: { type: 'uuid', creator: 'uuid', required: true },
   email: { type: 'email', required: true },
   password: { type: 'string', modifier: 'encryptPassword', required: true },
+  passwordConfirm: { type: 'string', equalTo: 'password' },
   fname: { type: 'string', description: 'First Name' },
   lname: { type: 'string', description: 'Last Name', empty: true },
   suffix: { type: 'string', allowNull: true },
@@ -160,6 +161,7 @@ When setting definitions for rules or model properties, the following are suppor
 * `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
+* `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
 * `strict`: Enable or disable strict checking of an object, see [Strict Mode](#strict-mode)
diff --git a/src/lib/validators.js b/src/lib/validators.js
index ea78ce4..167e668 100644
--- a/src/lib/validators.js
+++ b/src/lib/validators.js
@@ -100,6 +100,23 @@ const validators = {
     if (dot.pick(sub, data) === undefined && value === undefined) {
       errors.push({ type, sub, key, value, message: `Value required because '${sub}' is undefined`})
     }
+  },
+
+  /**
+   * Validator equalTo method, used by model
+   * @param {Object} def The property configuration
+   * @param {*} value The value being validated
+   * @param {string} key The key name of the property
+   * @param {Array<{type: string, sub: string|number, key: string, value: *, message: string}>} errors An error array
+   * to which any additional error objects will be added
+   * @param {Object} data The full initial data object
+   */
+  equalTo: function(def, value, key, errors, data) {
+    const type = 'equalTo'
+    const sub = def.equalTo
+    if (dot.pick(sub, data) !== value) {
+      errors.push({ type, sub, key, value, message: `Value must match ${sub} value`})
+    }
   }
 }
 
diff --git a/src/rules.js b/src/rules.js
index ca5ee64..5f42934 100644
--- a/src/rules.js
+++ b/src/rules.js
@@ -21,7 +21,8 @@ const allProps = {
   max: { name: 'max', fn: validators.max },
   type: { name: 'type', fn: types.validate },
   requireIf: { name: 'requireIf', fn: validators.requireIf },
-  requireIfNot: { name: 'requireIfNot', fn: validators.requireIfNot }
+  requireIfNot: { name: 'requireIfNot', fn: validators.requireIfNot },
+  equalTo: { name: 'equalTo', fn: validators.equalTo }
 }
 
 /**
@@ -45,7 +46,8 @@ const rules = {
       allProps.max,
       allProps.type,
       allProps.requireIf,
-      allProps.requireIfNot
+      allProps.requireIfNot,
+      allProps.equalTo
     ],
     // No value/undefined
     noVal: [
@@ -53,7 +55,8 @@ const rules = {
       allProps.default,
       allProps.modifier,
       allProps.requireIf,
-      allProps.requireIfNot
+      allProps.requireIfNot,
+      allProps.equalTo
     ],
     // No value, partial
     noValPartial: []


Test patch:
diff --git a/test/src/lib/validators.spec.js b/test/src/lib/validators.spec.js
index c379839..d4f30bc 100644
--- a/test/src/lib/validators.spec.js
+++ b/test/src/lib/validators.spec.js
@@ -169,4 +169,24 @@ describe('validators', () => {
       })
     })
   })
+  describe('equalTo', () => {
+    it('creates an error object when fields are not equal', () => {
+      const data = { password: 'Password' }
+      const def = { equalTo: 'password' }
+      validators.equalTo(def, 'Passwrod', 'passwordConfirm', mockErrors, data)
+      expect(mockErrors[0]).to.deep.equal({
+        type: 'equalTo',
+        sub: 'password',
+        key: 'passwordConfirm',
+        value: 'Passwrod',
+        message: 'Value must match password value'
+      })
+    })
+    it('doesn\'t create an error object when fields are equal', () => {
+      const data = { password: 'Password' }
+      const def = { equalTo: 'password' }
+      validators.equalTo(def, 'Password', 'passwordConfirm', mockErrors, data)
+      expect(mockErrors).to.be.empty
+    })
+  })
 })