# ValidatorSchema

表单通用校验器

## 基本使用

```js
import { ValidatorSchema } from 'amos-framework';

const descriptor = {
  name: { type: "string", required: true }
}

const validator = new ValidatorSchema(descriptor);

validator.validate({name: "xiaoji"}, (errors, fields) => {
  if(errors) {
    // 校验失败，errors 是校验项失败集合
    return handleErrors(errors, fields);
  }
  // 校验通过
});

// 采用 promise 方式
validator.validate({ name: 'xiaoji' }).then(() => {
  // 校验通过，或无错误信息
}).catch(({ errors, fields }) => {
  return handleErrors(errors, fields);
});
```

### validate 方法说明

```js
validator.validate(source, [options], callback)
```

* `source`: 待校验的对象 (必填).
* `options`: 验证对象配置项 (可选).
* `callback`: 验证完成时要调用的回调函数 (必填).

use promise

* `then()`，验证通过
* `catch({ errors, fields })`，验证失败, errors： 错误数组, fields：`{[fieldName]: *}[]`

#### validate options 参数说明

| param | type | descr |
| --- | --- | --- |
| first | Boolean | Invoke`callback` 当第一个验证规则生成错误时，不会处理更多的验证规则。如果验证涉及多个异步调用（例如，数据库查询），并且只需要第一个错误，请使用此选项。 |
| firstFields | `Boolean or String[]` | 设置为 false 时，Invoke `callback` 时，当指定字段的第一个验证规则生成错误时，将不再处理同一字段的验证规则， `true` 表示所有字段均触发。 |
| disabledLog | Boolean | 是否禁用初始化警告log，默认不禁用 |

#### validate callback 参数说明

* `errors`: 该参数存在，则验证失败，值为：所有错误信息组成的数组， 数据格式为：`{ field, message }[]`
* `fields`: 该参数是一个错误集合对象，key 为 field，值为该field对应的错误数组， 数据格式为：`{ [field]: { field, message }[] }`

通常情况下，只需要使用 `errors` 即可。

## Rules

rules 可以是执行验证的函数

```js
function(rule, value, callback, source, options)
```

* `rule`: 源描述符中与要验证的字段名相对应的验证规则。它总是被分配一个 `field` 属性，其中包含要验证的字段的名称.
* `value`: 正在验证的源对象属性的值
* `callback`: 验证完成后调用的回调函数。它需要传递一个 `Error` 实例数组，以指示验证失败。
* `source`: 传递给 `validate` 方法的源对象。
* `options`: 其它配置项
* `options.messages`: 包含验证错误的消息对象，与 `defaultMessages` 深度合并

The options passed to `validate` are passed on to the validation functions so that you may reference transient data (such as model references) in validation functions.

However, some option names are reserved; if you use these properties of the options object they are overwritten.

The reserved properties are `messages`, `exception` and `error`.


```js
import { ValidatorSchema } from 'amos-framework';

const descriptor = {
  name(rule, value, callback, source, options) {
    const errors = [];
    if(!/^[a-z0-9]+$/.test(value)) {
      errors.push(
        new Error(
          util.format("%s must be lowercase alphanumeric characters",
            rule.field)));
    }
    callback(errors);
  }
}
const validator = new ValidatorSchema(descriptor);
validator.validate({name: "Firstname"}, (errors, fields) => {
  if(errors) {
    return handleErrors(errors, fields);
  }
  // validation passed
});
```

通常情况，需要针对单个字段的多个验证规则进行测试，此时，需要将规则设置为对象数组，例如：

```js
const regEmail = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
const descriptor = {
  email: [
    { type: "string", required: true, pattern: regEmail },
    { validator(rule, value, callback, source, options) {
      const errors = [];
      // test if email address already exists in a database
      // and add a validation error to the errors array if it does
      callback(errors);
    }}
  ]
}
```

#### 支持的 Type

可使用的校验类型， 具体类型如下:

* `string`: 数据类型必须是 `string`. `默认类型.`
* `number`: 数据类型必须是 `number`.
* `boolean`: 数据类型必须是 `boolean`.
* `method`: 数据类型必须是 `function`.
* `regexp`: 必须是 `RegExp` 对象，或者是一个可生成 `RegExp` 对象的字符串.
* `integer`: 数据类型必须是 `number` 且为 `integer`.
* `float`: 数据类型必须是 `number` 且为 `float`.
* `array`: 必须是由 `array.isArray` 确定的数组.
* `object`: 数据类型必须是 `object` 且不为 `Array.isArray`.
* `enum`: 值必须存在于 `enum`.
* `date`: 值必须是由 `Date` 确定的有效值
* `url`: 数据类型必须是 `url`.
* `hex`: 数据类型必须是 `hex`.
* `email`: 数据类型必须是 `email`.
* `any`: 可以是任意类型.

#### Required 必填校验

`required` 表示该字段必须存在于正在验证的源对象上。

#### Pattern 正则校验

`pattern` 表示值必须匹配该正则表达式才能通过验证。

#### Range 范围校验

范围是使用 `main` 和 `max` 属性定义的。

对于 `string` 和 `array` 类型，将根据 `length` 进行比较；

对于 `number` 类型，数字不得小于 `min` 或大于 `max`。

#### Length 长度校验

To validate an exact length of a field specify the `len` property.

For `string` and `array` types comparison is performed on the `length` property, for the `number` type this property indicates an exact match for the `number`, ie, it may only be strictly equal to `len`.

If the `len` property is combined with the `min` and `max` range properties, `len` takes precedence.

#### Enumerable 枚举校验

To validate a value from a list of possible values use the `enum` type with a `enum` property listing the valid values for the field, for example:

```js
const descriptor = {
  role: {type: "enum", enum: ['admin', 'user', 'guest']}
}
```

### Messages 设置提示信息

Depending upon your application requirements, you may need i18n support or you may prefer different validation error messages.

The easiest way to achieve this is to assign a `message` to a rule:

```js
{ name:{type: "string", required: true, message: "Name is required"} }
```

Message can be any type, such as jsx format.

```js
{name:{type: "string", required: true, message: <b>Name is required</b>}}
```

Potentially you may require the same schema validation rules for different languages, in which case duplicating the schema rules for each language does not make sense.

In this scenario you could just provide your own messages for the language and assign it to the schema:

```js
import { ValidatorSchema } from 'amos-framework';

const cn = {
  required: '%s 必填',
};
const descriptor = {name:{type: "string", required: true}};
const validator = new ValidatorSchema(descriptor);
// deep merge with defaultMessages
validator.messages(cn);
...
```

如果要定义自己的验证函数，最好将 `message strings` 指定给 `messages object`，然后通过验证函数中的 `options.messages` 属性访问消息。

### validator

you can custom validate function for specified field:

```js
const fields = {
  asyncField:{
    validator(rule,value,callback){
      fetch({
        url:'xx',
        value:value
      }).then(function(data){
        callback();
      },function(error){
        callback(new Error(error))
      });
    }
  },

  promiseField:{
      validator(rule, value){
        return fetch({
          url:'xx',
          value:value
        });
      }
    }
};
```

#### 使用自定义 asyncValidator

默认情况下，validator 也支持返回值为 `Promise` 的场景，新版本，新增 `asyncValidator` 方式，专门处理返回值为 `Promise` 的情景。

用途： 使用于后端校验的场景。

如果有多个 `ValidatorSchema` 校验，此时可以采用 `Propmise.all([p1, p2, p3, ...])` 的方式进行处理。

```js
const fields = {
  asyncField: {
    asyncValidator(rule, value, callback) {
      ajax({
        url: 'xx',
        value: value,
      }).then(function(data) {
        callback();
      }, function(error) {
        callback(new Error(error));
      });
    },
  },

  promiseField: {
    asyncValidator(rule, value) {
      return ajax({
        url: 'xx',
        value: value,
      });
    },
  },
};
```

## 完整案例

```js
import { ValidatorSchema } from 'amos-framework';

const vs = new ValidatorSchema({
  name: {
    type: 'string',
    required: true,
    min: 5,
    max: 10
  },
  address: {
    type: 'object',
    required: true,
    fields: {
      province: {
        type: 'string',
        required: true,
        min: 4
      },
      city: {
        type: 'string',
        message: 'custom message',
        min: 5
      },
      async: {
        validator(rule, value, callback) {
          setTimeout(() => {
            callback(rule.message);
          }, 100);
        },
        message: 'address async fails'
      }
    }
  },
  async: {
    validator(rule, value, callback) {
      setTimeout(() => {
        callback([new Error(rule.message)]);
      }, 100);
    },
    message: 'async fails'
  }
});

// 待校验的数据对象
const obj = {
  name: 2,
  address: {
    city: 2
  },
  async: '2'
};

vs.validate(obj, (errors, fields) => {
  console.log('errors');
  console.log(errors);
  console.log('fields');
  console.log(fields);
});

console.log('end');
```
