提交 2faaec33 编写于 作者: 雪洛's avatar 雪洛

feat: add validator mixin

上级 3cff96e3
*.test.js
**/node_modules/
\ No newline at end of file
......@@ -6,9 +6,9 @@ const {
ERROR
} = require('./error')
const validator = Object.create(null)
const baseValidator = Object.create(null)
validator.username = function (username) {
baseValidator.username = function (username) {
const errCode = ERROR.INVALID_USERNAME
if (!isValidString(username)) {
return {
......@@ -29,7 +29,7 @@ validator.username = function (username) {
}
}
validator.password = function (password) {
baseValidator.password = function (password) {
const errCode = ERROR.INVALID_PASSWORD
if (!isValidString(password)) {
return {
......@@ -44,7 +44,7 @@ validator.password = function (password) {
}
}
validator.mobile = function (mobile) {
baseValidator.mobile = function (mobile) {
const errCode = ERROR.INVALID_MOBILE
if (!isValidString(mobile)) {
return {
......@@ -58,7 +58,7 @@ validator.mobile = function (mobile) {
}
}
validator.email = function (email) {
baseValidator.email = function (email) {
const errCode = ERROR.INVALID_EMAIL
if (!isValidString(email)) {
return {
......@@ -72,7 +72,7 @@ validator.email = function (email) {
}
}
validator.nickname = function (nickname) {
baseValidator.nickname = function (nickname) {
const errCode = ERROR.INVALID_NICKNAME
if (nickname.indexOf('@') !== -1) {
// 昵称不允许含@
......@@ -94,75 +94,229 @@ validator.nickname = function (nickname) {
}
}
validate['array<string>'] = function (arr) {
if (getType(arr) !== 'array') {
const baseType = ['string', 'boolean', 'number', 'null'] // undefined不会由客户端提交上来
baseType.forEach((type) => {
baseValidator[type] = function (val) {
if (getType(val) === type) {
return
}
return {
errCode: ERROR.INVALID_PARAM
}
}
if (arr.some(item => !isValidString(item))) {
return {
errCode: ERROR.INVALID_PARAM
})
function tokenize (name) {
let i = 0
const result = []
let token = ''
while (i < name.length) {
const char = name[i]
switch (char) {
case '|':
case '<':
case '>':
token && result.push(token)
result.push(char)
token = ''
break
default:
token += char
break
}
i++
if (i === name.length && token) {
result.push(token)
}
}
return result
}
const baseType = ['string', 'boolean', 'number']
baseType.forEach((type) => {
validator[type] = function (val) {
if (getType(val) !== type) {
return {
errCode: ERROR.INVALID_PARAM
/**
* 处理validator名
* @param {string} name
*/
function parseValidatorName (name) {
const tokenList = tokenize(name)
let i = 0
let currentToken = tokenList[i]
const result = {
type: 'root',
children: [],
parent: null
}
let lastRealm = result
while (currentToken) {
switch (currentToken) {
case 'array': {
const currentRealm = {
type: 'array',
children: [],
parent: lastRealm
}
lastRealm.children.push(currentRealm)
lastRealm = currentRealm
break
}
case '<':
if (lastRealm.type !== 'array') {
throw new Error('Invalid validator token "<"')
}
break
case '>':
if (lastRealm.type !== 'array') {
throw new Error('Invalid validator token ">"')
}
lastRealm = lastRealm.parent
break
case '|':
if (lastRealm.type !== 'array' && lastRealm.type !== 'root') {
throw new Error('Invalid validator token "|"')
}
break
default:
lastRealm.children.push({
type: currentToken
})
break
}
i++
currentToken = tokenList[i]
}
})
return result
}
validator['number|string'] = function (val) {
const type = getType(val)
if (type !== 'number' && type !== 'string') {
return {
errCode: ERROR.INVALID_PARAM
function getRuleCategory (rule) {
switch (rule.type) {
case 'array':
return 'array'
case 'root':
return 'root'
default:
return 'base'
}
}
function isMatchUnionType (val, rule) {
if (!rule.children || rule.children.length === 0) {
return true
}
const children = rule.children
for (let i = 0; i < children.length; i++) {
const child = children[i]
const category = getRuleCategory(child)
let pass = false
switch (category) {
case 'base':
pass = isMatchBaseType(val, child)
break
case 'array':
pass = isMatchArrayType(val, child)
break
default:
break
}
if (pass) {
return true
}
}
return false
}
function isMatchBaseType (val, rule) {
if (typeof baseValidator[rule.type] !== 'function') {
throw new Error(`invalid schema type: ${rule.type}`)
}
const validateRes = baseValidator[rule.type](val)
if (validateRes && validateRes.errCode) {
return false
}
return true
}
function validate (value = {}, schema = {}) {
for (const schemaKey in schema) {
let schemaValue = schema[schemaKey]
if (getType(schemaValue) === 'string') {
schemaValue = {
required: true,
type: schemaValue
function isMatchArrayType (arr, rule) {
if (getType(arr) !== 'array') {
return false
}
if (rule.children && rule.children.length && arr.some(item => !isMatchUnionType(item, rule))) {
return false
}
return true
}
class Validator {
constructor () {
this.baseValidator = baseValidator
this.customValidator = Object.create(null)
}
mixin (type, handler) {
this.customValidator[type] = handler
}
getRealBaseValidator (type) {
return this.customValidator[type] || this.baseValidator[type]
}
get validator () {
return new Proxy({}, {
get: (_, prop) => {
if (typeof prop !== 'string') {
return
}
const realBaseValidator = this.getRealBaseValidator(prop)
if (realBaseValidator) {
return realBaseValidator
}
const rule = parseValidatorName(prop)
return function (val) {
if (!isMatchUnionType(val, rule)) {
return {
errCode: ERROR.INVALID_PARAM
}
}
}
}
}
const {
required,
type
} = schemaValue
// value内未传入了schemaKey或对应值为undefined
if (value[schemaKey] === undefined) {
if (required) {
return {
errCode: ERROR.PARAM_REQUIRED,
errMsgValue: {
param: schemaKey
},
schemaKey
})
}
validate (value = {}, schema = {}) {
for (const schemaKey in schema) {
let schemaValue = schema[schemaKey]
if (getType(schemaValue) === 'string') {
schemaValue = {
required: true,
type: schemaValue
}
} else {
continue
}
}
const validateMethod = validator[type]
if (!validateMethod) {
throw new Error(`invalid schema type: ${type}`)
}
const validateRes = validateMethod(value[schemaKey])
if (validateRes) {
validateRes.schemaKey = schemaKey
return validateRes
const {
required,
type
} = schemaValue
// value内未传入了schemaKey或对应值为undefined
if (value[schemaKey] === undefined) {
if (required) {
return {
errCode: ERROR.PARAM_REQUIRED,
errMsgValue: {
param: schemaKey
},
schemaKey
}
} else {
continue
}
}
const validateMethod = this.validator[type]
if (!validateMethod) {
throw new Error(`invalid schema type: ${type}`)
}
const validateRes = validateMethod(value[schemaKey])
if (validateRes) {
validateRes.schemaKey = schemaKey
return validateRes
}
}
}
}
......@@ -205,7 +359,7 @@ function checkClientInfo (clientInfo) {
screenWidth: numberNotRequired,
screenHeight: numberNotRequired
}
const validateRes = validate(clientInfo, schema)
const validateRes = new Validator().validate(clientInfo, schema)
if (validateRes) {
if (validateRes.errCode === ERROR.PARAM_REQUIRED) {
console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId')
......@@ -217,7 +371,6 @@ function checkClientInfo (clientInfo) {
}
module.exports = {
validate,
validator,
Validator,
checkClientInfo
}
const assert = require('assert')
const {
Validator
} = require('./validator')
const {
ERROR
} = require('./error')
const {
getType
} = require('./utils')
const testCaseList = [{
value: {
username: 'uname'
},
schema: {
username: 'username'
},
expected: undefined
}, {
value: {
username: '123456'
},
schema: {
username: 'username'
},
expected: {
errCode: ERROR.INVALID_USERNAME
}
}, {
value: {
username: '数字天堂'
},
schema: {
username: 'username'
},
expected: {
errCode: ERROR.INVALID_USERNAME
}
}, {
value: {
password: '123456'
},
schema: {
password: 'password'
}
}, {
value: {
password: '123456'
},
schema: {
password: 'password'
},
mixin: [{
type: 'password',
handler: function (password) {
if (typeof password !== 'string' || password.length < 10) {
return {
errCode: ERROR.INVALID_PASSWORD
}
}
}
}],
expected: {
errCode: ERROR.INVALID_PASSWORD
}
}]
function execTestCase ({
value = {},
schema = {},
mixin = [],
expected = undefined,
error = ''
} = {}) {
const validator = new Validator()
for (let i = 0; i < mixin.length; i++) {
const {
type,
handler
} = mixin[i]
validator.mixin(type, handler)
}
let validateResult,
validateError
try {
validateResult = validator.validate(value, schema)
} catch (err) {
validateError = err
}
const tag = JSON.stringify({ value, schema })
if (error) {
if (typeof error === 'string') {
assert.strictEqual(validateError.message.indexOf(error) > -1, true, `[${tag}] error message error`)
} else if (getType(error) === 'regexp') {
assert.strictEqual(error.test(validateError), true, `[${tag}] error message error`)
} else {
throw new Error(`[${tag}] invalid test case`)
}
return
}
if (expected === undefined) {
assert.strictEqual(validateResult, undefined, `[${tag}] validate result error`)
return
}
const expectedKeys = Object.keys(expected)
let passResultCheck = true
for (let i = 0; i < expectedKeys.length; i++) {
const key = expectedKeys[i]
if (expected[key] !== validateResult[key]) {
passResultCheck = false
break
}
}
assert.strictEqual(passResultCheck, true, `[${tag}] validate result error`)
}
for (let i = 0; i < testCaseList.length; i++) {
console.log(`test case: ${i}`)
execTestCase(testCaseList[i])
console.log(`test case: ${i}, pass`)
}
......@@ -4,7 +4,8 @@ const {
getType
} = require('./common/utils')
const {
checkClientInfo
checkClientInfo,
Validator
} = require('./common/validator')
const ConfigUtils = require('./lib/utils/config')
const {
......@@ -106,6 +107,46 @@ module.exports = {
platform: clientPlatform
}).getPlatformConfig()
this.validator = new Validator()
/**
* 示例:覆盖密码验证规则
*/
// this.validator.mixin('password', function (password) {
// if (typeof password !== 'string' || password.length < 10) {
// // 调整为密码长度不能小于10
// return {
// errCode: ERROR.INVALID_PASSWORD
// }
// }
// })
/**
* 示例:新增验证规则
*/
// this.validator.mixin('timestamp', function (timestamp) {
// if (typeof timestamp !== 'number' || timestamp > Date.now()) {
// return {
// errCode: ERROR.INVALID_PARAM
// }
// }
// })
// // 新增规则同样可以在数组验证规则中使用
// this.validator.valdate({
// timestamp: 123456789
// }, {
// timestamp: 'timestamp'
// })
// this.validator.valdate({
// timestampList: [123456789, 123123123123]
// }, {
// timestampList: 'array<timestamp>'
// })
// // 甚至更复杂的写法
// this.validator.valdate({
// timestamp: [123456789, 123123123123]
// }, {
// timestamp: 'timestamp|array<timestamp>'
// })
// 挂载uni-captcha到this上,方便后续调用
this.uniCaptcha = uniCaptcha
......
const {
validate
} = require('../common/validator')
module.exports = function (value = {}, schema = {}) {
const validateRes = validate(value, schema)
const validateRes = this.validator.validate(value, schema)
if (validateRes) {
delete validateRes.schemaKey
throw validateRes
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册