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

feat: add validator mixin

上级 3cff96e3
*.test.js
**/node_modules/
\ No newline at end of file
...@@ -6,9 +6,9 @@ const { ...@@ -6,9 +6,9 @@ const {
ERROR ERROR
} = require('./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 const errCode = ERROR.INVALID_USERNAME
if (!isValidString(username)) { if (!isValidString(username)) {
return { return {
...@@ -29,7 +29,7 @@ validator.username = function (username) { ...@@ -29,7 +29,7 @@ validator.username = function (username) {
} }
} }
validator.password = function (password) { baseValidator.password = function (password) {
const errCode = ERROR.INVALID_PASSWORD const errCode = ERROR.INVALID_PASSWORD
if (!isValidString(password)) { if (!isValidString(password)) {
return { return {
...@@ -44,7 +44,7 @@ validator.password = function (password) { ...@@ -44,7 +44,7 @@ validator.password = function (password) {
} }
} }
validator.mobile = function (mobile) { baseValidator.mobile = function (mobile) {
const errCode = ERROR.INVALID_MOBILE const errCode = ERROR.INVALID_MOBILE
if (!isValidString(mobile)) { if (!isValidString(mobile)) {
return { return {
...@@ -58,7 +58,7 @@ validator.mobile = function (mobile) { ...@@ -58,7 +58,7 @@ validator.mobile = function (mobile) {
} }
} }
validator.email = function (email) { baseValidator.email = function (email) {
const errCode = ERROR.INVALID_EMAIL const errCode = ERROR.INVALID_EMAIL
if (!isValidString(email)) { if (!isValidString(email)) {
return { return {
...@@ -72,7 +72,7 @@ validator.email = function (email) { ...@@ -72,7 +72,7 @@ validator.email = function (email) {
} }
} }
validator.nickname = function (nickname) { baseValidator.nickname = function (nickname) {
const errCode = ERROR.INVALID_NICKNAME const errCode = ERROR.INVALID_NICKNAME
if (nickname.indexOf('@') !== -1) { if (nickname.indexOf('@') !== -1) {
// 昵称不允许含@ // 昵称不允许含@
...@@ -94,41 +94,194 @@ validator.nickname = function (nickname) { ...@@ -94,41 +94,194 @@ validator.nickname = function (nickname) {
} }
} }
validate['array<string>'] = function (arr) { const baseType = ['string', 'boolean', 'number', 'null'] // undefined不会由客户端提交上来
if (getType(arr) !== 'array') {
baseType.forEach((type) => {
baseValidator[type] = function (val) {
if (getType(val) === type) {
return
}
return { return {
errCode: ERROR.INVALID_PARAM 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
}
/**
* 处理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
}
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
} }
const baseType = ['string', 'boolean', 'number'] 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
}
baseType.forEach((type) => { class Validator {
validator[type] = function (val) { constructor () {
if (getType(val) !== type) { this.baseValidator = baseValidator
return { this.customValidator = Object.create(null)
errCode: ERROR.INVALID_PARAM
} }
mixin (type, handler) {
this.customValidator[type] = handler
} }
getRealBaseValidator (type) {
return this.customValidator[type] || this.baseValidator[type]
} }
})
validator['number|string'] = function (val) { get validator () {
const type = getType(val) return new Proxy({}, {
if (type !== 'number' && type !== 'string') { 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 { return {
errCode: ERROR.INVALID_PARAM errCode: ERROR.INVALID_PARAM
} }
} }
} }
}
})
}
function validate (value = {}, schema = {}) { validate (value = {}, schema = {}) {
for (const schemaKey in schema) { for (const schemaKey in schema) {
let schemaValue = schema[schemaKey] let schemaValue = schema[schemaKey]
if (getType(schemaValue) === 'string') { if (getType(schemaValue) === 'string') {
...@@ -155,7 +308,7 @@ function validate (value = {}, schema = {}) { ...@@ -155,7 +308,7 @@ function validate (value = {}, schema = {}) {
continue continue
} }
} }
const validateMethod = validator[type] const validateMethod = this.validator[type]
if (!validateMethod) { if (!validateMethod) {
throw new Error(`invalid schema type: ${type}`) throw new Error(`invalid schema type: ${type}`)
} }
...@@ -165,6 +318,7 @@ function validate (value = {}, schema = {}) { ...@@ -165,6 +318,7 @@ function validate (value = {}, schema = {}) {
return validateRes return validateRes
} }
} }
}
} }
function checkClientInfo (clientInfo) { function checkClientInfo (clientInfo) {
...@@ -205,7 +359,7 @@ function checkClientInfo (clientInfo) { ...@@ -205,7 +359,7 @@ function checkClientInfo (clientInfo) {
screenWidth: numberNotRequired, screenWidth: numberNotRequired,
screenHeight: numberNotRequired screenHeight: numberNotRequired
} }
const validateRes = validate(clientInfo, schema) const validateRes = new Validator().validate(clientInfo, schema)
if (validateRes) { if (validateRes) {
if (validateRes.errCode === ERROR.PARAM_REQUIRED) { if (validateRes.errCode === ERROR.PARAM_REQUIRED) {
console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId') console.warn('- 如果使用HBuilderX运行本地云函数/云对象功能时出现此提示,请改为使用客户端调用本地云函数方式调试,或更新HBuilderX到3.4.12及以上版本。\n- 如果是缺少clientInfo.appId,请检查项目manifest.json内是否配置了DCloud AppId')
...@@ -217,7 +371,6 @@ function checkClientInfo (clientInfo) { ...@@ -217,7 +371,6 @@ function checkClientInfo (clientInfo) {
} }
module.exports = { module.exports = {
validate, Validator,
validator,
checkClientInfo 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 { ...@@ -4,7 +4,8 @@ const {
getType getType
} = require('./common/utils') } = require('./common/utils')
const { const {
checkClientInfo checkClientInfo,
Validator
} = require('./common/validator') } = require('./common/validator')
const ConfigUtils = require('./lib/utils/config') const ConfigUtils = require('./lib/utils/config')
const { const {
...@@ -106,6 +107,46 @@ module.exports = { ...@@ -106,6 +107,46 @@ module.exports = {
platform: clientPlatform platform: clientPlatform
}).getPlatformConfig() }).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上,方便后续调用 // 挂载uni-captcha到this上,方便后续调用
this.uniCaptcha = uniCaptcha this.uniCaptcha = uniCaptcha
......
const {
validate
} = require('../common/validator')
module.exports = function (value = {}, schema = {}) { module.exports = function (value = {}, schema = {}) {
const validateRes = validate(value, schema) const validateRes = this.validator.validate(value, schema)
if (validateRes) { if (validateRes) {
delete validateRes.schemaKey delete validateRes.schemaKey
throw validateRes throw validateRes
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册