Repository: baianat/vee-validate (js)

Issue:
Validation Provider access to invalid rules
**Versions**

- vee-validate: 2.1.7
- vue: 2.6.8

**Describe the bug**
I want to know which rule is invalid but i have only the error messages

**To reproduce**
Steps to reproduce the behavior:
1. Go to input
2. Fill with a basic string
3. See email error message and errors array

**Expected behavior**
The errors object is a basic array with errors messages but i can't access to the rule error to display custom message for example.

**Demo link**
[Codesandbox](https://codesandbox.io/s/r7yzx610y4?fontsize=14).

**Desktop (please complete the following information):**
 - OS: any
 - Browser any
 - Version any

**Smartphone (please complete the following information):**
 - Device: any
 - OS: any
 - Browser any
 - Version any

**Additional context**
There is a way to access to first or all rule and check if it's valid or invalid or maybe transform the errors array to a ErrorBag?


Pull request description:
Feat verify failed rules
This PR adds `failedRules` to the `Validator.verify` result and to the ValidationProvider slot-scope props, which is very useful if someone wants to do UI customizations based on the failed rules.

```vue
<ValidationProvider rules="required|email" name="field">
  <div slot-scope="{ errors, failedRules }">
    <input type="text" v-model="value">
    <span v-if="failedRules.required">Come on, this is required!</span>
    <span v-if="failedRules.email">Have you ever seen an email like the one you just wrote?</span>
  </div>
</ValidationProvider>
```


closes #1898

Patch:
diff --git a/docs/api/validator.md b/docs/api/validator.md
index 9e64de4c..da22141e 100644
--- a/docs/api/validator.md
+++ b/docs/api/validator.md
@@ -21,7 +21,7 @@ The validator offers an API to add new fields and trigger validations.
 | validateAll(fields?: String or Object) | `Promise<boolean>` | Validates each value against the corresponding field validations. |
 | pause() | `void` | Disables validation. |
 | resume() | `void` | Enables validation. |
-| verify(value: any, rules: string | Object) | { errors: string[], valid: boolean } | [verify method](#verify) |
+| verify(value: any, rules: string | Object) | { errors: string[], valid: boolean, failedRules: { [x: string]: string } } | [verify method](#verify) |
 | detach(name: string, scope?: string) | `void` | Detaches the field that matches the name and the scope of the provided values. |
 | extend(name: string, rule: Rule, options?: ExtendOptions) | `void` | Adds a new validation rule. The provided rule param must be a [valid Rule function or object](/guide/custom-rules.md). |
 | reset(matcher?: Object) | `void` | Resets field flags for all scoped fields. Resets all fields if no scope is provided. |
diff --git a/docs/guide/components/validation-provider.md b/docs/guide/components/validation-provider.md
index ac02afbb..3207d307 100644
--- a/docs/guide/components/validation-provider.md
+++ b/docs/guide/components/validation-provider.md
@@ -42,6 +42,7 @@ The object passed down to the slot scope is called the __validation context__. I
 | Name    | Type                       |  Description |
 |:--------|:--------------------------:|:--------------------------------------------------------------------|
 | errors  | `string[]`                 | The list of error messages.                                         |
+| failedRules | `[x: string]: string`  | A map object of failed rules with (rule, message) as a (key, value) |
 | valid   | `boolean`                  | The current validation state.                                       |
 | flags   | `{ [x: string]: boolean }` | The flags map object state.                                         |
 | aria    | `{ [x: string]: string }`  | Map object of aria attributes for accessibility.                    |
diff --git a/src/components/provider.js b/src/components/provider.js
index 92c50aae..601faae4 100644
--- a/src/components/provider.js
+++ b/src/components/provider.js
@@ -3,7 +3,7 @@ import { getValidator } from '../state';
 import Validator from '../core/validator';
 import RuleContainer from '../core/ruleContainer';
 import { normalizeEvents, isEvent } from '../utils/events';
-import { createFlags, normalizeRules, warn, isCallable, debounce, isNullOrUndefined } from '../utils';
+import { createFlags, normalizeRules, warn, isCallable, debounce, isNullOrUndefined, assign } from '../utils';
 import { findModel, extractVNodes, addVNodeListener, getInputEventName, createRenderless } from '../utils/vnode';
 
 let $validator = null;
@@ -16,6 +16,7 @@ export function createValidationCtx (ctx) {
     flags: ctx.flags,
     classes: ctx.classes,
     valid: ctx.isValid,
+    failedRules: ctx.failedRules,
     reset: () => ctx.reset(),
     validate: (...args) => ctx.validate(...args),
     aria: {
@@ -251,6 +252,7 @@ export const ValidationProvider = {
     initialized: false,
     initialValue: undefined,
     flags: createFlags(),
+    failedRules: {},
     forceRequired: false,
     isDeactivated: false,
     id: null
@@ -388,8 +390,9 @@ export const ValidationProvider = {
         return result;
       });
     },
-    applyResult ({ errors }) {
+    applyResult ({ errors, failedRules }) {
       this.messages = errors;
+      this.failedRules = assign({}, failedRules);
       this.setFlags({
         valid: !errors.length,
         changed: this.value !== this.initialValue,
diff --git a/src/core/validator.js b/src/core/validator.js
index 154df849..b983f7b0 100644
--- a/src/core/validator.js
+++ b/src/core/validator.js
@@ -427,7 +427,18 @@ export default class Validator {
     }
 
     return this._validate(field, value).then(result => {
-      return { valid: result.valid, errors: result.errors.map(e => e.msg) };
+      const errors = [];
+      const ruleMap = {};
+      result.errors.forEach(e => {
+        errors.push(e.msg);
+        ruleMap[e.rule] = e.msg;
+      });
+
+      return {
+        valid: result.valid,
+        errors,
+        failedRules: ruleMap
+      };
     });
   }
 
diff --git a/types/vee-validate.d.ts b/types/vee-validate.d.ts
index 5d02a2f5..23a42e93 100644
--- a/types/vee-validate.d.ts
+++ b/types/vee-validate.d.ts
@@ -124,6 +124,7 @@ export interface FieldMatchOptions {
 export interface VerifyResult {
     valid: boolean;
     errors: string[];
+    failedRules: { [x: string]: string };
 }
 
 export interface VerifyOptions {
@@ -136,6 +137,7 @@ export interface ValidationSlotScopeData {
     errors: string[];
     flags: FieldFlags;
     valid: boolean;
+    failedRules: { [x: string]: string };
     reset (): void;
     validate(value?: any): Promise<VerifyResult>
 }


Test patch:
diff --git a/tests/unit/validator.js b/tests/unit/validator.js
index 9d895bd0..adbe3116 100644
--- a/tests/unit/validator.js
+++ b/tests/unit/validator.js
@@ -1016,14 +1016,14 @@ test('creates regeneratable messages', async () => {
 describe('Verify API', () => {
   test('passing values and results', async () => {
     const v = new Validator();
-    expect(await v.verify('test', 'max:3')).toEqual({ 'errors': ['The {field} field may not be greater than 3 characters.'], 'valid': false });
+    expect(await v.verify('test', 'max:3')).toEqual({ 'errors': ['The {field} field may not be greater than 3 characters.'], 'valid': false, 'failedRules': { 'max': 'The {field} field may not be greater than 3 characters.' } });
     expect(v.errors.count()).toBe(0); // Errors not added.
-    expect(await v.verify('tst', 'max:3')).toEqual({ valid: true, errors: [] });
+    expect(await v.verify('tst', 'max:3')).toEqual({ valid: true, errors: [], failedRules: {} });
     // test required rule
-    expect(await v.verify('', 'required')).toEqual({ 'errors': ['The {field} field is required.'], 'valid': false });
+    expect(await v.verify('', 'required')).toEqual({ 'errors': ['The {field} field is required.'], 'valid': false, failedRules: { required: 'The {field} field is required.' } });
     // test #1353
-    expect(await v.verify('föö@bar.de', { email: { allow_utf8_local_part: true } })).toEqual({ valid: true, errors: [] });
-    expect(await v.verify('föö@bar.de', { email: { allow_utf8_local_part: false } })).toEqual({ valid: false, errors: ['The {field} field must be a valid email.'] });
+    expect(await v.verify('föö@bar.de', { email: { allow_utf8_local_part: true } })).toEqual({ valid: true, errors: [], failedRules: {} });
+    expect(await v.verify('föö@bar.de', { email: { allow_utf8_local_part: false } })).toEqual({ valid: false, errors: ['The {field} field must be a valid email.'], failedRules: { 'email': 'The {field} field must be a valid email.' } });
   });
 
   test('target rules validation using options.values', async () => {