对于任何 api 来说,输入参数的校验,是非常重要的一个步骤;很多框架和工具也都提供了输入参数验证功能;今天,我们来学习其中一种,并尝试从零开始,创建一个 Javascript 版本的 Laravel 风格参数验证器。

关于 Laravel

laravel/laravel​github.com

是一个基于 PHP 实现的 web 框架,它提供了api参数验证功能,其中对于验证规则的组织很简洁:

public function store(Request $request)
{$validatedData = $request->validate(['title' => 'required|max:255','body' => 'required',]);// ...
}

通过上面的 php 代码我们看到,对于参数 title,有两个验证规则,它们分别是:

  1. required 这代表 title 参数必传,如果没有传递 title 参数,或 title 参数的值为:null、空字符串、空数组、空对象,则验证不会通过。
  2. max:255 这代表参数作为字符串、数字或数组,上限必须小于或等于255(对于字符串和数组来说,则判定其 length 的值)

以上验证规则,使用符号 | 分割,看起来很紧凑。

我们参照上面的这些内容,设计一个 JavaScript 版本的验证器。

需求

首先,我们列出对于验证器的需求:

  • 输入:验证器接收至少两个参数:输入参数列表针对每个输入参数的验证规则定义列表
  • 输出:验证器返回一个数组,其中包含了验证参数失败的信息,默认只返回第一个验证失败的参数,支持返回所有验证失败的参数;如验证全部通过,则返回空数组。
  • 验证规则:验证器除支持基本类型验证外,还需支持以下验证规则 requiredmaxmin
  • 扩展性:验证器支持扩展自定义验证规则和验证失败信息
  • 语言支持:验证器支持多语言验证失败信息,至少支持:中文英文, 默认返回 中文 错误信息

验证规则详情:

  • required,参数值为:null、undefined、NaN、空字符串、空数组、空对象,则验证不通过
  • max,参数值类型如果是:数字,则值不能大于(可以等于) max: 后面指定的值;参数值类型如果是:字符串、数组,则长度不能大于(可以等于)max: 后面指定的值
  • min,参数值类型如果是:数字,则值不能小于(可以等于) min: 后面指定的值;参数值类型如果是:字符串、数组,则长度不能小于(可以等于)min: 后面指定的值

实现

接下来,我们创建工程,并根据需求,设计好工程文件目录结构:

第一步:创建工程,搭建单元测试环境,并设计工程目录结构与文件

step1:创建工程

mkdir validator && cd validator && yarn init

然后安装我们需要的代码检查工具(standard)、单元测试工具(jest)和 git hook 工具(husky):

yarn add -D standard jest husky

安装完毕后,package.json 的内容如下:

{"name": "validator","version": "1.0.0","main": "index.js","license": "MIT","devDependencies": {"husky": "^3.0.5","jest": "^24.9.0","standard": "^14.3.0"}
}

我们添加两个命令:

  • lint 用于启动代码检查
  • test 用于启动单元测试:
{..."scripts": {"lint": "standard","test": "jest"},...
}

并设定每次执行 git commit 前,自动运行 yarn lintyarn test

{..."husky": {"hooks": {"pre-commit": "yarn lint && yarn test"}}
}

step2:配置单元测试环境

我们新建 jest.config.js 文件,并在其中指定单元测试覆盖率:

module.exports = {'collectCoverage': true,'coverageThreshold': {'global': {'branches': 100,'functions': 100,'lines': 100,'statements': 100}}
}

另外,因为在使用 jest 撰写单元测试时,会使用到两个全局变量:testexpect

所以,需要在 package.json 中将其添加到 standard 白名单:

{..."standard": {"globals": ["test","expect"]}...
}

最终,package.json 的内容如下:

{"name": "validator","version": "1.0.0","main": "index.js","license": "MIT","scripts": {"lint": "standard","test": "jest"},"devDependencies": {"husky": "^3.0.5","jest": "^24.9.0","standard": "^14.3.0"},"husky": {"hooks": {"pre-commit": "yarn lint && yarn test"}},"standard": {"globals": ["test","expect"]}
}

step3:设计目录结构与文件

回顾一下需求,我们确定有如下几个功能文件:

  • index.js 功能入口文件,提供 validator 最终的 api 接口
  • rules.js 负责实现验证规则的细节
  • language.js 负责实现所有语言版本的验证失败信息
  • type.js 负责提供基本的类型校验功能
  • index.test.js 负责实现单元测试

index.js 外(创建工程时已经创建了此文件),我们建好上述其他文件。

接下来,我们新建两个文件夹:

  • lib 存放 rules.jstype.jslanguage.js
  • test存放 index.test.js

最终,目录如下:

├── jest.config.js
├── lib
│   ├── language.js
│   ├── rules.js
│   └── type.js
├── package.json
├── test
│   └── index.test.js
└── yarn.lock

到此,我们已经有了初步的环境和目录结构,接下来,我们撰写单元测试。

第二步:实现单元测试

单元测试本质上是站在用户(使用者)的角度去验证功能行为,因此,在开始撰写单元测试之前,我们先要确定 validator 的 api:

step1:确定 Api

我们预期 validator 像如下这般使用:

const V = require('validator')const params = { name:'hello world', age: 18 }
const schema = { name: 'string|required|max:10', age: 'number' }
const options = { language: 'en', deep: true }const invalidList = V(params, schema, options)// check invalidList .../* the invalidlist will be
[{paramName: 'name',actualValue: 'hello world',invalidMessage: 'name can not gt 10. hello world given.'}
]
*/

上述代码表达了如下内容:

  1. params 是输入参数对象,其中包含两个参数:nameage,值分别为 hello world18
  2. schema 是针对输入参数对象所描述的具体验证规则,这里实际上要求 name 参数为字符串类型,且必须必传,且最大长度不能超过 10(可以等于 10),而 age 参数为数字类型
  3. options 作为 validator 的配置参数,决定验证失败信息使用中文还是英文(默认为中文 zh),以及是否返回所有验证失败的参数信息(默认只返回第一个验证失败的参数信息)
  4. invalidList 是一个数组,如果内容不为空,则其中包含了验证失败参数的信息,包括:参数名称(paramName)、失败描述(invalidMessage)、实际值(actualValue)

step2:设计测试用例

确定了 api 之后,我们来确认撰写测试用例的注意事项:

  • 测试用例需要覆盖所有的验证规则,且每个规则需要覆盖 两个 case,其中, 的 case 代表验证通过; 的 case 代表验证失败
  • 测试用例需要覆盖 中文英文 两个 case
  • 测试用例需要覆盖必传参数没有传递的 case
  • 测试用例需要覆盖返回所有验证失败的参数的 case

接下来我们设计测试用例,最终代码如下:

const V = require('../index.js')test('invalid value of params or schema or both', () => {expect(V({ name: 'jarone' })).toEqual([])expect(V({ name: 'jarone' }, 0)).toEqual([])expect(V({ name: 'jarone' }, false)).toEqual([])expect(V({ name: 'jarone' }, '')).toEqual([])expect(V({ name: 'jarone' }, 123)).toEqual([])expect(V({ name: 'jarone' }, 'abc')).toEqual([])expect(V({ name: 'jarone' }, [])).toEqual([])expect(V({ name: 'jarone' }, {})).toEqual([])expect(V({ name: 'jarone' }, () => {})).toEqual([])expect(V({ name: 'jarone' }, Promise.resolve())).toEqual([])expect(V({ name: 'jarone' }, new Error())).toEqual([])expect(V({ name: 'jarone' }, new Date())).toEqual([])expect(V(undefined, { name: 'max:10' })).toEqual([])expect(V(0, { name: 'max:10' })).toEqual([])expect(V(false, { name: 'max:10' })).toEqual([])expect(V('', { name: 'max:10' })).toEqual([])expect(V(123, { name: 'max:10' })).toEqual([])expect(V('abc', { name: 'max:10' })).toEqual([])expect(V([], { name: 'max:10' })).toEqual([])expect(V({}, { name: 'max:10' })).toEqual([])expect(V(() => {}, { name: 'max:10' })).toEqual([])expect(V(Promise.resolve(), { name: 'max:10' })).toEqual([])expect(V(new Error(), { name: 'max:10' })).toEqual([])expect(V(new Date(), { name: 'max:10' })).toEqual([])expect(V()).toEqual([])expect(V(0, 0)).toEqual([])expect(V(false, false)).toEqual([])expect(V('', '')).toEqual([])expect(V(123, 123)).toEqual([])expect(V('abc', 'abc')).toEqual([])expect(V([], [])).toEqual([])expect(V({}, {})).toEqual([])expect(V(() => {}, () => {})).toEqual([])expect(V(Promise.resolve(), Promise.resolve())).toEqual([])expect(V(new Error(), new Error())).toEqual([])
})test('RULE: string', () => {expect(V({ name: 'jarone' }, { name: 'string' })).toEqual([])expect(V({ name: 1 }, { name: 'string' })).toEqual([{paramName: 'name',actualValue: 1,invalidMessage: 'name 必须为字符串类型, 实际值为:1'}])expect(V({ name: 1 }, { name: 'string' }, { language: 'en' })).toEqual([{paramName: 'name',actualValue: 1,invalidMessage: 'name is not string, 1 given.'}])
})test('RULE: numericString', () => {expect(V({ age: '1' }, { age: 'numericString' })).toEqual([])expect(V({ age: 'one' }, { age: 'numericString' })).toEqual([{paramName: 'age',actualValue: 'one',invalidMessage: 'age 必须为数字, 实际值为:one'}])expect(V({ age: 'one' }, { age: 'numericString' }, { language: 'en' })).toEqual([{paramName: 'age',actualValue: 'one',invalidMessage: 'age is not numeric string, one given.'}])
})test('RULE: boolean', () => {expect(V({ ok: false }, { ok: 'boolean' })).toEqual([])expect(V({ ok: 1 }, { ok: 'boolean' })).toEqual([{paramName: 'ok',actualValue: 1,invalidMessage: 'ok 必须为布尔类型, 实际值为:1'}])expect(V({ ok: 1 }, { ok: 'boolean' }, { language: 'en' })).toEqual([{paramName: 'ok',actualValue: 1,invalidMessage: 'ok is not boolean, 1 given.'}])
})test('RULE: array', () => {expect(V({ records: [1, 2] }, { records: 'array' })).toEqual([])expect(V({ records: 1 }, { records: 'array' })).toEqual([{paramName: 'records',actualValue: 1,invalidMessage: 'records 必须为数组, 实际值为:1'}])expect(V({ records: 1 }, { records: 'array' }, { language: 'en' })).toEqual([{paramName: 'records',actualValue: 1,invalidMessage: 'records is not array, 1 given.'}])
})test('RULE: required', () => {expect(V({ name: 'jarone' }, { name: 'required' })).toEqual([])expect(V({}, { name: 'required' })).toEqual([{paramName: 'name',actualValue: undefined,invalidMessage: '必须传递 name, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象'}])expect(V({ name: null }, { name: 'required' })).toEqual([{paramName: 'name',actualValue: null,invalidMessage: '必须传递 name, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象'}])expect(V({ name: '' }, { name: 'required' })).toEqual([{paramName: 'name',actualValue: '',invalidMessage: '必须传递 name, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象'}])expect(V({ name: [] }, { name: 'required' })).toEqual([{paramName: 'name',actualValue: [],invalidMessage: '必须传递 name, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象'}])expect(V({ name: {} }, { name: 'required' })).toEqual([{paramName: 'name',actualValue: {},invalidMessage: '必须传递 name, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象'}])expect(V({ name: {} }, { name: 'required' }, { language: 'en' })).toEqual([{paramName: 'name',actualValue: {},invalidMessage: 'Must pass name, and the value cannot be: null, undefined, NaN, empty string, empty array, empty object'}])
})test('RULE: max', () => {expect(V({ name: 'jarone' }, { name: 'max:10' })).toEqual([])expect(V({ name: 'hello world' }, { name: 'max:10' })).toEqual([{paramName: 'name',actualValue: 'hello world',invalidMessage: 'name 的长度或大小不能大于 10. 实际值为:hello world'}])expect(V({ name: 'hello world' }, { name: 'max:10' }, { language: 'en' })).toEqual([{paramName: 'name',actualValue: 'hello world',invalidMessage: 'name length or size cannot be greater than 10. actual value is: hello world'}])
})test('RULE: min', () => {expect(V({ name: 'hello world' }, { name: 'min:10' })).toEqual([])expect(V({ name: 'jarone' }, { name: 'min:10' })).toEqual([{paramName: 'name',actualValue: 'jarone',invalidMessage: 'name 的长度或大小不能小于 10. 实际值为:jarone'}])expect(V({ name: 'jarone' }, { name: 'min:10' }, { language: 'en' })).toEqual([{paramName: 'name',actualValue: 'jarone',invalidMessage: 'name length or size cannot be less than 10. actual value is: jarone'}])
})test('OPTIONS: deep', () => {expect(V({ name: 'hello world', age: 18 }, { name: 'min:10', age: 'max:18' }, { deep: true })).toEqual([])expect(V({ name: 'jarone', age: 28 }, { name: 'min:10', age: 'max:18' }, { deep: true })).toEqual([{paramName: 'name',actualValue: 'jarone',invalidMessage: 'name 的长度或大小不能小于 10. 实际值为:jarone'},{paramName: 'age',actualValue: 28,invalidMessage: 'age 的长度或大小不能大于 18. 实际值为:28'}])expect(V({ name: 'jarone', age: 28 }, { name: 'min:10', age: 'max:18' }, { deep: true, language: 'en' })).toEqual([{paramName: 'name',actualValue: 'jarone',invalidMessage: 'name length or size cannot be less than 10. actual value is: jarone'},{paramName: 'age',actualValue: 28,invalidMessage: 'age length or size cannot be greater than 18. actual value is: 28'}])
})test('extend rules', () => {expect(V({ name: 'jarone' },{ name: 'isJarone' },{language: 'en',extRules: { isJarone: (val) => val === 'jarone' },extInvalidMessages: { isJarone: (paramName, val) => `${paramName} is not jarone, ${val} given.` }})).toEqual([])expect(V({ name: 'luy' },{ name: 'isJarone' },{language: 'en',extRules: { isJarone: (val) => val === 'jarone' },extInvalidMessages: { isJarone: (paramName, val) => `${paramName} is not jarone, ${val} given.` }})).toEqual([{paramName: 'name',actualValue: 'luy',invalidMessage: 'name is not jarone, luy given.'}])
})

第三步:实现功能

step1:实现 lib/type.js

我们需要一组函数来提供对于基本类型的判断,一个比较好的方式是使用那些经过时间考验的工具库

本文中我们使用的类型判断功能并不太多,所以选择自己实现这些函数:

function _isType (arg, type) {return Object.prototype.toString.call(arg) === `[object ${type}]`
}module.exports = {isString: arg => _isType(arg, 'String'),isBoolean: arg => _isType(arg, 'Boolean'),isArray: arg => _isType(arg, 'Array'),isObject: arg => _isType(arg, 'Object'),isNaN: arg => Number.isNaN(arg),isNull: arg => _isType(arg, 'Null'),isUndefined: arg => _isType(arg, 'Undefined'),isNumericString: arg => _isType(+arg, 'Number') && !Number.isNaN(+arg)
}

step2:实现 lib/language.js

按照需求,我们需要支持 中文英文 两种语言的验证失败信息

对于基础类型和 required 验证规则而言,我们只需要传递参数名称和实际值,就能得到验证失败信息;对于 maxmin 这两个规则,还需要传递边界值:

const invalidMsgEn = {string: (paramName, actualValue) => `${paramName} is not string, ${actualValue} given.`,numericString: (paramName, actualValue) => `${paramName} is not numeric string, ${actualValue} given.`,boolean: (paramName, actualValue) => `${paramName} is not boolean, ${actualValue} given.`,array: (paramName, actualValue) => `${paramName} is not array, ${actualValue} given.`,required: (paramName, actualValue) => `Must pass ${paramName}, and the value cannot be: null, undefined, NaN, empty string, empty array, empty object`,max: (paramName, actualValue, boundary) => `${paramName} length or size cannot be greater than ${boundary}. actual value is: ${actualValue}`,min: (paramName, actualValue, boundary) => `${paramName} length or size cannot be less than ${boundary}. actual value is: ${actualValue}`
}const invalidMsgZh = {string: (paramName, actualValue) => `${paramName} 必须为字符串类型, 实际值为:${actualValue}`,numericString: (paramName, actualValue) => `${paramName} 必须为数字, 实际值为:${actualValue}`,boolean: (paramName, actualValue) => `${paramName} 必须为布尔类型, 实际值为:${actualValue}`,array: (paramName, actualValue) => `${paramName} 必须为数组, 实际值为:${actualValue}`,required: (paramName, actualValue) => `必须传递 ${paramName}, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象`,max: (paramName, actualValue, boundary) => `${paramName} 的长度或大小不能大于 ${boundary}. 实际值为:${actualValue}`,min: (paramName, actualValue, boundary) => `${paramName} 的长度或大小不能小于 ${boundary}. 实际值为:${actualValue}`
}module.exports = {zh: invalidMsgZh,en: invalidMsgEn
}

step3:实现 lib/rules.js

1):实现基本类型验证规则

我们约定:规则函数的返回值类型为布尔类型,true 代表验证通过,false 代表验证失败

接下来,我们借助于前文已经实现的 lib/type.js,创建以下4种类型验证规则:

  • 字符串
  • 数字 (包含可以转为数字的字符串)
  • 布尔
  • 数组
const T = require('./type.js')module.exports = {string: T.isString,numericString: T.isNumericString,boolean: T.isBoolean,array: T.isArray
}

1):实现 required 规则

接下来,我们实现 required 规则,回顾一下前文中关于 required 的详情描述

参数值为 null、undefined、NaN、空字符串、空数组、空对象,则验证不通过;否则,验证通过:

const T = require('./type.js')const _isPassedRequired = val => {if (T.isNaN(val) || T.isUndefined(val) || T.isNull(val)) return falseif ((T.isArray(val) || T.isObject(val) || T.isString(val)) && !Object.keys(val).length) return falsereturn true
}module.exports = {string: T.isString,numericString: T.isNumericString,bool: T.isBoolean,array: T.isArray,required: val => _isPassedRequired(val)
}

2):实现 maxmin 规则

对于 max 规则

参数值类型如果是:数字,则值不能大于(可以等于)max: 后面指定的值;

参数值类型如果是:字符串、数组,则长度不能大于(可以等于)max: 后面指定的值。

min 规则正好与 max 相反。

我们对于类似 maxmin 这种对比逻辑,简单做一下抽象,将对比操作符和对类型的处理,分别定义出来:

...const operatorMapping = {'>=': (val, boundary) => val >= boundary,'<=': (val, boundary) => val <= boundary
}// compare: Array、String、Number and Numeric String
const _compare = (val, boundary, operator) => (T.isString(val) || T.isArray(val))? !operatorMapping[operator](val && val.length, boundary): !operatorMapping[operator](+val, boundary)...module.exports = {...max: (val, boundary) => _compare(val, boundary, '>='),min: (val, boundary) => _compare(val, boundary, '<=')
}

step4:实现 index.js

最后,我们来实现入口文件 index.js,它负责:

  • 解析输入参数和验证规则,同时,对输出参数和验证规则进行校验
  • 对每个输入参数逐个应用验证规则。这里需要明确一点:和输入参数一样,单个参数的验证规则可能也有多个
  • 根据配置项 deep 的值,决定只返回第一个验证失败的参数信息还是返回全部
  • 根据配置项 language 的值,决定在组装验证失败信息时,使用 中文英文
  • 根据配置项 extRulesextLanguages 的值,决定是否扩展验证规则和对应的信息文案

入口文件 index.js 最终的代码如下:

const T = require('./lib/type.js')
const InvalidMessages = require('./lib/language.js')
const Rules = require('./lib/rules.js')function validateSingleParamByMultipleRules (name, val, rulesString, allRules, allInvalidMsg, allParams) {let result = ''const rules = rulesString.split('|')for (let i = 0, len = rules.length; i < len; i++) {const rule = rules[i]const idxOfSeparator = rule.indexOf(':')let ruleName = rulelet ruleValue = ''if (~idxOfSeparator) {ruleValue = rule.substr(idxOfSeparator + 1)ruleName = rule.substr(0, idxOfSeparator)}const fn = allInvalidMsg[ruleName + '']if (!allRules[ruleName](val, ruleValue, allParams)) {result = {paramName: name,actualValue: val,invalidMessage: fn(name, val, ruleValue)}break}}return result
}function main (params, schema, options = {}) {const invalidParams = []if (!T.isObject(schema)) return invalidParamsif (!T.isObject(params)) params = {}const needValidateParamNameList = Object.keys(schema)if (!needValidateParamNameList.length) return invalidParamsconst { language = 'zh', deep = false, extRules = {}, extInvalidMessages = {} } = optionsconst allRules = Object.assign({}, Rules, extRules)const allInvalidMessages = Object.assign({}, InvalidMessages[language], extInvalidMessages)for (let i = 0, len = needValidateParamNameList.length; i < len; i++) {const name = needValidateParamNameList[i]const val = params[name]const rulesString = schema[name]if (!name || !rulesString || (T.isUndefined(val) && !rulesString.includes('required'))) continueconst invalidInfo = validateSingleParamByMultipleRules(name, val, rulesString, allRules, allInvalidMessages, params)if (invalidInfo) {invalidParams.push(invalidInfo)if (!deep) break}}return invalidParams
}module.exports = main

第四步:验证单元测试以及覆盖率

最后,我们再次运行单元测试 yarn test, 结果如下:

yarn run v1.13.0
$ jestPASS  test/index.test.js✓ invalid value of params or schema or both (9ms)✓ RULE: string (1ms)✓ RULE: numericString (1ms)✓ RULE: boolean✓ RULE: array (1ms)✓ RULE: required (1ms)✓ RULE: max (1ms)✓ RULE: min (1ms)✓ OPTIONS: deep✓ extend rules (1ms)---------------|----------|----------|----------|----------|-------------------|
File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
---------------|----------|----------|----------|----------|-------------------|
All files      |      100 |      100 |      100 |      100 |                   |validator     |      100 |      100 |      100 |      100 |                   |index.js     |      100 |      100 |      100 |      100 |                   |validator/lib |      100 |      100 |      100 |      100 |                   |language.js  |      100 |      100 |      100 |      100 |                   |rules.js     |      100 |      100 |      100 |      100 |                   |type.js      |      100 |      100 |      100 |      100 |                   |
---------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       10 passed, 10 total
Snapshots:   0 total
Time:        1.104s
Ran all test suites.
✨  Done in 1.97s.

至此,我们已经完成了 validator 的创建与研发工作

想查看完整代码的读者,可以访问在 GitHub 的代码仓库

jarone/validator​github.com

想在自己的工程中使用的读者,可以使用 npm 安装

validator-simple​www.npmjs.com

接下来,我们在 NodeJS 工程中,实践一下 validator

实践

新建一个工程,并安装 koakoa-bodyparservalidator-simple

mkdir demo && cd demo && yarn inityarn add koa koa-bodyparser validator-simple

新建 validator.js 文件,并输入如下内容:

const V = require('validator-simple')const main = (params, schema) => {const invalidMsg = V(params, schema)if (invalidMsg && invalidMsg.length) {let err = new Error('参数错误:' + invalidMsg[0].invalidMessage +' 参数名称:' + invalidMsg[0].paramName +' 参数值:' + invalidMsg[0].actualValue)err.code = 400throw err}
}module.exports = main

新建 app.js 文件,并输入如下内容:

const Koa = require('koa')
const app = new Koa()
const V = require('./validator.js')app.use(require('koa-bodyparser')())app.use(async (ctx, next) => {try {await next()} catch (error) {ctx.status = error.code || 500ctx.body = error.message}
})app.use(async ctx => {const params = ctx.request.bodyconst schema = {name: 'required|string|min:3|max:10'}V(params, schema)ctx.body = 'done'
})app.listen({ port: 3000 }, () =>console.log('  Server ready at http://localhost:3000')
)

启动服务,我们在命令行请求这个服务,分别传递正确的 name,和错误的 name,返回如下:

➜  ~ curl -X POST http://localhost:3000 -H 'content-type: application/json' -d '{"name":"jarone"}'
done➜  ~ curl -X POST http://localhost:3000 -H 'content-type: application/json' -d '{}'
参数错误:必须传递 name, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象 参数名称:name 参数值:undefined                           ➜  ~ curl -X POST http://localhost:3000 -H 'content-type: application/json' -d '{"name":1}'
参数错误:name 必须为字符串类型, 实际值为:1 参数名称:name 参数值:1➜  ~ curl -X POST http://localhost:3000 -H 'content-type: application/json' -d '{"name":"a"}'
参数错误:name 的长度或大小不能小于 3. 实际值为:a 参数名称:name 参数值:a➜  ~ curl -X POST http://localhost:3000 -H 'content-type: application/json' -d '{"name":"abcedfghijk"}'
参数错误:name 的长度或大小不能大于 10. 实际值为:abcedfghijk 参数名称:name 参数值:abcedfghijk➜  ~ curl -X POST http://localhost:3000 -H 'content-type: application/json' -d '{"name":[]}'
参数错误:必须传递 name, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象 参数名称:name 参数值:➜  ~ curl -X POST http://localhost:3000 -H 'content-type: application/json' -d '{"name":{}}'
参数错误:必须传递 name, 且值不能为: null, undefined, NaN, 空字符串, 空数组, 空对象 参数名称:name 参数值:[object Object]

结束语

本文中,我们只实现了几个基本的验证规则,在我们实际的工作中,还会有更多的场景需要使用验证器。例如:

分页

基于游标分页的参数中,一般会传递如下参数:

  • pageSize 代表每页展示的记录数
  • next 代表当前页面最后一条记录的游标
  • prev 代表当前页面第一条记录的游标

通常,参数 nextprev 是互斥的,我们完全可以根据场景需求让验证器支持如下规则:

  • 如果没有传递 next 参数,则要求必须传递 prev 参数;反之亦然
  • 如果同时传递了 next 参数和 prev 参数,则验证不通过或默认只识别其中一个参数;否则验证通过

日期

在校验用户生日等日期表单值时,我们希望验证器支持校验日期参数,且能限制日期值的上限和下限:

日期参数值类似: 2019-01-01 13:30

限制日期类参数值的规则类似: date|gte:1900-01-01|lte:2020-12-31

...

最后,希望这篇文章能帮助到您。


水滴前端团队招募伙伴,欢迎投递简历到邮箱:fed@shuidihuzhu.com

laravel input值必须不等于0_【第十一期】实现 Javascript 版本的 Laravel 风格参数验证器...相关推荐

  1. 原生js监听input值发生变化

    原生JS中可以使用oninput,onpropertychange,onchange oninput,onpropertychange,onchange的用法 1) onchange 触发事件必须满足 ...

  2. python输入一组数字存到列表_Python如何使用输入传递多个值并将它们存储在一个列表中(简单版本),python,怎么,input,传入,储存,到...

    Python如何使用输入传递多个值并将它们存储在一个列表中(简单版本),python,怎么,input,传入,储存,到 发表时间:2020-08-20 当传入多个值时,需要使用split()函数来切割 ...

  3. bigdecimal js 判断等于0_为啥阿里禁用BigDecimal的equals方法做等值比较

    BigDecimal,相信对于很多人来说都不陌生,很多人都知道它的用法,这是一种java.math包中提供的一种可以用来进行精确运算的类型. 很多人都知道,在进行金额表示.金额计算等场景,不能使用do ...

  4. onchange监听input值变化及input隐藏后change事件不触发的原因与解决方法(设置readonly后onchange不起作用的解决方案)

    onchange监听input值变化及input隐藏后change事件不触发的原因与解决方法(设置readonly后onchange不起作用的解决方案) 参考文章: (1)onchange监听inpu ...

  5. 清除file类型的input值

    问题背景: 非现场开户, 需要上传身份证照片, 见证视频等信息. 当客户上传文件后,由于文件不符合要求等问题, 需要重新上传. 但重新上传后发现,不能触发input的onchange方法. 问题:   ...

  6. HTML form表单添加enctype属性后获取不到input值

    HTML form表单添加enctype属性后获取不到input值 问题如图所示, 在servlet中输出获取的属性值,发现全部为null 最后结论 enctype修改了form表单提交时的格式,不再 ...

  7. 使用JS获取input值

    获取input值,设置input值 可以使用 $(".class") $("#id") $("input[name='name']") re ...

  8. jquery input值发生变化时,时时触发事件 input propertychange 、 cheng

    propertychange事件,是input值发生变化时,时时触发的事件 $(".dizhi").bind("input propertychange",fu ...

  9. onchange监听input值变化及input隐藏后change事件不触发的原因与解决方法

    1. onchange事件监听input值变化的使用方法: <input id="test"></input>$("input").ch ...

  10. onchange监听input值变化及input隐藏后change事件不触发的原因与解决方法(设置readonly后onchange不起作用的解决方案)...

    转自:https://www.cnblogs.com/white0710/p/7338456.html 1. onchange事件监听input值变化的使用方法: <input id=" ...

最新文章

  1. 三维点云语义分割总览
  2. Spark Steaming 点滴
  3. ubuntu 18.04 添加快快捷方式
  4. java map 结构体_业务代码的救星——Java 对象转换框架 MapStruct 妙用
  5. Matlab GUI 如何自动缩放
  6. 移动端开发, 常用CSS单位
  7. Docker容器的生命周期管理
  8. 17.容器的成员函数优先于同名的算法
  9. 工作积累(五)——使用spring@Value注解实现常量功能
  10. ABP .Net Core Entity Framework迁移使用MySql数据库
  11. 开发者的利器:Docker 理解与使用
  12. 六年级下册百分数计算题_六年级数学上册期末试卷(附答案)
  13. AD如何清理过期电脑
  14. github 出现无法连接成功问题终极详解
  15. 上拉查看详情和下拉隐藏详情
  16. 《逆袭大学——传给IT学子正能量》一审稿目录
  17. 使用ActiveX实现的Web自定义查询-万能查询
  18. git合并分支相关操作
  19. java计算机毕业设计医院住院部信息管理系统源程序+mysql+系统+lw文档+远程调试
  20. python莫比乌斯内接矩形_用莫比乌斯带巧解内接矩形问题:拓扑学的用处

热门文章

  1. (转)姚期智:呼之欲出的量子计算机,和它漫长的最后一英里(全文)
  2. 中国存储系统的先行者郑纬民
  3. 【路径规划】基于matlab改进的人工势场算法机器人避障路径规划【含Matlab源码 1151期】
  4. 【人脸识别】基于matlab GUI Haar分类器五官定位【含Matlab源码 686期】
  5. 【光学】基于matlab涡旋光与球面波的干涉【含Matlab源码 597期】
  6. mysql连接池满了_《MySql体系结构与存储引擎》面试腾讯前总结
  7. python svm向量_支持向量机(SVM)及其Python实现
  8. Eclipse 格式化代码且不影响注释
  9. mysql least 参数_MySQL中MIN()和LEAST()的区别
  10. 记一个函数定义中,形参是空列表时要注意的问题