diff --git a/uni_modules/uni-id-pages/.npmignore b/uni_modules/uni-id-pages/.npmignore new file mode 100644 index 0000000000000000000000000000000000000000..e5cae048b933c6682ed8ee9da863b1a0e6a17b05 --- /dev/null +++ b/uni_modules/uni-id-pages/.npmignore @@ -0,0 +1,2 @@ +*.test.js +**/node_modules/ \ No newline at end of file diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.js index 1b7ff72c984d5989bda26083936b5123e4a5c92d..f7c789934b7a8ff440924a53ebabc044a62ef30c 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.js @@ -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'] = 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 } diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.test.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c305a846b3e66b08524d43b6e5b20d63def33021 --- /dev/null +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/common/validator.test.js @@ -0,0 +1,122 @@ +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`) +} diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js index d004d8c02bcfb5ab9fa6606de64e25983fa815cc..8dc0f467d04d3616bc517ccda26d3ce0ab288748 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/index.obj.js @@ -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' + // }) + // // 甚至更复杂的写法 + // this.validator.valdate({ + // timestamp: [123456789, 123123123123] + // }, { + // timestamp: 'timestamp|array' + // }) + // 挂载uni-captcha到this上,方便后续调用 this.uniCaptcha = uniCaptcha diff --git a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/validate.js b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/validate.js index 4a41085f7f31f121d423b907736fc2276f42442b..52ff04769c0f4498a90111b7cf79313cb4683823 100644 --- a/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/validate.js +++ b/uni_modules/uni-id-pages/uniCloud/cloudfunctions/uni-id-co/middleware/validate.js @@ -1,9 +1,5 @@ -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