import { isFn, hasOwn, toRawType, isPlainObject } from 'uni-shared' export default function validateParam (key, paramTypes, paramsData) { const paramOptions = paramTypes[key] const absent = !hasOwn(paramsData, key) let value = paramsData[key] const booleanIndex = getTypeIndex(Boolean, paramOptions.type) if (booleanIndex > -1) { if (absent && !hasOwn(paramOptions, 'default')) { value = false } } if (value === undefined) { if (hasOwn(paramOptions, 'default')) { const paramDefault = paramOptions.default value = isFn(paramDefault) ? paramDefault() : paramDefault paramsData[key] = value // 默认值 } } return assertParam(paramOptions, key, value, absent, paramsData) } function assertParam ( paramOptions, name, value, absent, paramsData ) { if (paramOptions.required && absent) { return `Missing required parameter \`${name}\`` } if (value == null && !paramOptions.required) { const validator = paramOptions.validator if (validator) { return validator(value, paramsData) } return } let type = paramOptions.type let valid = !type || type === true const expectedTypes = [] if (type) { if (!Array.isArray(type)) { type = [type] } for (let i = 0; i < type.length && !valid; i++) { const assertedType = assertType(value, type[i]) expectedTypes.push(assertedType.expectedType || '') valid = assertedType.valid } } if (!valid) { return getInvalidTypeMessage(name, value, expectedTypes) } const validator = paramOptions.validator if (validator) { return validator(value, paramsData) } } const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/ function assertType (value, type) { let valid const expectedType = getType(type) if (simpleCheckRE.test(expectedType)) { const t = typeof value valid = t === expectedType.toLowerCase() if (!valid && t === 'object') { valid = value instanceof type } } else if (value.byteLength >= 0) { valid = true } else if (expectedType === 'Object') { valid = isPlainObject(value) } else if (expectedType === 'Array') { valid = Array.isArray(value) } else { // TODO 页面传入的ArrayBuffer使用instanceof ArrayBuffer返回false,暂做此修改 valid = value instanceof type || toRawType(value) === getType(type) } return { valid, expectedType } } function getType (fn) { const match = fn && fn.toString().match(/^\s*function (\w+)/) return match ? match[1] : '' } function isSameType (a, b) { return getType(a) === getType(b) } function getTypeIndex (type, expectedTypes) { if (!Array.isArray(expectedTypes)) { return isSameType(expectedTypes, type) ? 0 : -1 } for (let i = 0, len = expectedTypes.length; i < len; i++) { if (isSameType(expectedTypes[i], type)) { return i } } return -1 } function getInvalidTypeMessage (name, value, expectedTypes) { let message = `parameter \`${name}\`.` + ` Expected ${expectedTypes.join(', ')}` const expectedType = expectedTypes[0] const receivedType = toRawType(value) const expectedValue = styleValue(value, expectedType) const receivedValue = styleValue(value, receivedType) if (expectedTypes.length === 1 && isExplicable(expectedType) && !isBoolean(expectedType, receivedType)) { message += ` with value ${expectedValue}` } message += `, got ${receivedType} ` if (isExplicable(receivedType)) { message += `with value ${receivedValue}.` } return message } function styleValue (value, type) { if (type === 'String') { return `"${value}"` } else if (type === 'Number') { return `${Number(value)}` } else { return `${value}` } } const explicitTypes = ['string', 'number', 'boolean'] function isExplicable (value) { return explicitTypes.some(elem => value.toLowerCase() === elem) } function isBoolean (...args) { return args.some(elem => elem.toLowerCase() === 'boolean') }