UNPKG

better-mock

Version:

Forked from Mockjs. Generate random data & Intercept ajax request. Support miniprogram.

415 lines (379 loc) 14.7 kB
// ## valid(template, data) // // 校验真实数据 data 是否与数据模板 template 匹配。 // // 实现思路: // 1. 解析规则。 // 先把数据模板 template 解析为更方便机器解析的 JSON-Schema // name 属性名 // type 属性值类型 // template 属性值模板 // properties 对象属性数组 // items 数组元素数组 // rule 属性值生成规则 // 2. 递归验证规则。 // 然后用 JSON-Schema 校验真实数据,校验项包括属性名、值类型、值、值生成规则。 // // 提示信息 // https://github.com/fge/json-schema-validator/blob/master/src/main/resources/com/github/fge/jsonschema/validator/validation.properties // [JSON-Schema validator](http://json-schema-validator.herokuapp.com/) // [Regexp Demo](http://demos.forbeslindesay.co.uk/regexp/) // ## name // 有生成规则:比较解析后的 name // 无生成规则:直接比较 // ## type // 无类型转换:直接比较 // 有类型转换:先试着解析 template,然后再检查? // ## value vs. template // 基本类型 // 无生成规则:直接比较 // 有生成规则: // number // min-max.dmin-dmax // min-max.dcount // count.dmin-dmax // count.dcount // +step // 整数部分 // 小数部分 // boolean // string // min-max // count // ## properties // 对象 // 有生成规则:检测期望的属性个数,继续递归 // 无生成规则:检测全部的属性个数,继续递归 // ## items // 数组 // 有生成规则: // `'name|1': [{}, {} ...]` 其中之一,继续递归 // `'name|+1': [{}, {} ...]` 顺序检测,继续递归 // `'name|min-max': [{}, {} ...]` 检测个数,继续递归 // `'name|count': [{}, {} ...]` 检测个数,继续递归 // 无生成规则:检测全部的元素个数,继续递归 import constant from '../utils/constant' import { type, keys as objectKeys, isArray, isString, isFunction, isRegExp, isNumber } from '../utils' import toJSONSchema from './schema' import { SchemaResult, DiffResult } from '../types' import handler from './handler' const Diff = { diff: function (schema: SchemaResult, data: string | object, name?: string | number) { const result: DiffResult[] = [] // 先检测名称 name 和类型 type,如果匹配,才有必要继续检测 if (Diff.name(schema, data, name, result) && Diff.type(schema, data, name, result)) { Diff.value(schema, data, name, result) Diff.properties(schema, data, name, result) Diff.items(schema, data, name, result) } return result }, /* jshint unused:false */ name: function (schema: SchemaResult, _data, name: string | number | undefined, result: DiffResult[]) { const length = result.length Assert.equal('name', schema.path, name + '', schema.name + '', result) return result.length === length }, type: function (schema: SchemaResult, data, _name, result: DiffResult[]) { const length = result.length if (isString(schema.template)) { // 占位符类型处理 if (schema.template.match(constant.RE_PLACEHOLDER)) { const actualValue = handler.gen(schema.template) Assert.equal('type', schema.path, type(data), type(actualValue), result) return result.length === length } } else if (isArray(schema.template)) { if (schema.rule.parameters) { // name|count: array if (schema.rule.min !== undefined && schema.rule.max === undefined) { // 跳过 name|1: array,因为最终值的类型(很可能)不是数组,也不一定与 `array` 中的类型一致 if (schema.rule.count === 1) { return true } } // 跳过 name|+inc: array if (schema.rule.parameters[2]) { return true } } } else if (isFunction(schema.template)) { // 跳过 `'name': function`,因为函数可以返回任何类型的值。 return true } Assert.equal('type', schema.path, type(data), schema.type, result) return result.length === length }, value: function (schema: SchemaResult, data, name, result: DiffResult[]) { const length = result.length const rule = schema.rule const templateType = schema.type if (templateType === 'object' || templateType === 'array' || templateType === 'function') { return true } // 无生成规则 if (!rule.parameters) { if (isRegExp(schema.template)) { Assert.match('value', schema.path, data, schema.template, result) return result.length === length } if (isString(schema.template)) { // 同样跳过含有『占位符』的属性值,因为『占位符』的返回值会通常会与模板不一致 if (schema.template.match(constant.RE_PLACEHOLDER)) { return result.length === length } } Assert.equal('value', schema.path, data, schema.template, result) return result.length === length } // 有生成规则 let actualRepeatCount if (isNumber(schema.template)) { const parts: string[] = (data + '').split('.') const intPart = Number(parts[0]) const floatPart = parts[1] // 整数部分 // |min-max if (rule.min !== undefined && rule.max !== undefined) { Assert.greaterThanOrEqualTo('value', schema.path, intPart, Math.min(Number(rule.min), Number(rule.max)), result) // , 'numeric instance is lower than the required minimum (minimum: {expected}, found: {actual})') Assert.lessThanOrEqualTo('value', schema.path, intPart, Math.max(Number(rule.min), Number(rule.max)), result) } // |count if (rule.min !== undefined && rule.max === undefined) { Assert.equal('value', schema.path, intPart, Number(rule.min), result, '[value] ' + name) } // 小数部分 if (rule.decimal) { // |dmin-dmax if (rule.dmin !== undefined && rule.dmax !== undefined) { Assert.greaterThanOrEqualTo('value', schema.path, floatPart.length, Number(rule.dmin), result) Assert.lessThanOrEqualTo('value', schema.path, floatPart.length, Number(rule.dmax), result) } // |dcount if (rule.dmin !== undefined && rule.dmax === undefined) { Assert.equal('value', schema.path, floatPart.length, Number(rule.dmin), result) } } } else if (isString(schema.template)) { // 'aaa'.match(/a/g) actualRepeatCount = data.match(new RegExp(schema.template, 'g')) actualRepeatCount = actualRepeatCount ? actualRepeatCount.length : 0 // |min-max if (rule.min !== undefined && rule.max !== undefined) { Assert.greaterThanOrEqualTo('repeat count', schema.path, actualRepeatCount, Number(rule.min), result) Assert.lessThanOrEqualTo('repeat count', schema.path, actualRepeatCount, Number(rule.max), result) } // |count if (rule.min !== undefined && rule.max === undefined) { Assert.equal('repeat count', schema.path, actualRepeatCount, rule.min, result) } } else if (isRegExp(schema.template)) { actualRepeatCount = data.match(new RegExp(schema.template.source.replace(/^\^|\$$/g, ''), 'g')) actualRepeatCount = actualRepeatCount ? actualRepeatCount.length : 0 // |min-max if (rule.min !== undefined && rule.max !== undefined) { Assert.greaterThanOrEqualTo('repeat count', schema.path, actualRepeatCount, Number(rule.min), result) Assert.lessThanOrEqualTo('repeat count', schema.path, actualRepeatCount, Number(rule.max), result) } // |count if (rule.min !== undefined && rule.max === undefined) { Assert.equal('repeat count', schema.path, actualRepeatCount, rule.min, result) } } return result.length === length }, properties: function (schema: SchemaResult, data, _name, result: DiffResult[]) { const length = result.length const rule = schema.rule const keys = objectKeys(data) if (!schema.properties) { return } // 无生成规则 if (!schema.rule.parameters) { Assert.equal('properties length', schema.path, keys.length, schema.properties.length, result) } else { // 有生成规则 // |min-max if (rule.min !== undefined && rule.max !== undefined) { Assert.greaterThanOrEqualTo( 'properties length', schema.path, keys.length, Math.min(Number(rule.min), Number(rule.max)), result ) Assert.lessThanOrEqualTo( 'properties length', schema.path, keys.length, Math.max(Number(rule.min), Number(rule.max)), result ) } // |count if (rule.min !== undefined && rule.max === undefined) { // |1, |>1 if (rule.count !== 1) { Assert.equal('properties length', schema.path, keys.length, Number(rule.min), result) } } } if (result.length !== length) { return false } for (let i = 0; i < keys.length; i++) { let property: SchemaResult | undefined schema.properties.forEach((item) => { if (item.name === keys[i]) { property = item } }) property = property || schema.properties[i] result.push(...Diff.diff(property, data[keys[i]], keys[i])) } return result.length === length }, items: function (schema: SchemaResult, data, _name, result: DiffResult[]) { const length = result.length if (!schema.items) { return } const rule = schema.rule // 无生成规则 if (!schema.rule.parameters) { Assert.equal('items length', schema.path, data.length, schema.items.length, result) } else { // 有生成规则 // |min-max if (rule.min !== undefined && rule.max !== undefined) { Assert.greaterThanOrEqualTo( 'items', schema.path, data.length, Math.min(Number(rule.min), Number(rule.max)) * schema.items.length, result, '[{utype}] array is too short: {path} must have at least {expected} elements but instance has {actual} elements' ) Assert.lessThanOrEqualTo( 'items', schema.path, data.length, Math.max(Number(rule.min), Number(rule.max)) * schema.items.length, result, '[{utype}] array is too long: {path} must have at most {expected} elements but instance has {actual} elements' ) } // |count if (rule.min !== undefined && rule.max === undefined) { // |1, |>1 if (rule.count === 1) { return result.length === length } else { Assert.equal('items length', schema.path, data.length, (Number(rule.min) * schema.items.length), result) } } // |+inc if (rule.parameters && rule.parameters[2]) { return result.length === length } } if (result.length !== length) { return false } for (let i = 0; i < data.length; i++) { result.push( ...Diff.diff( schema.items[i % schema.items.length], data[i], i % schema.items.length ) ) } return result.length === length } } // 完善、友好的提示信息 // // Equal, not equal to, greater than, less than, greater than or equal to, less than or equal to // 路径 验证类型 描述 // // Expect path.name is less than or equal to expected, but path.name is actual. // // Expect path.name is less than or equal to expected, but path.name is actual. // Expect path.name is greater than or equal to expected, but path.name is actual. const Assert = { message: function (item: DiffResult) { if (item.message) { return item.message } const upperType = item.type.toUpperCase() const lowerType = item.type.toLowerCase() const path = isArray(item.path) && item.path.join('.') || item.path const action = item.action const expected = item.expected const actual = item.actual return `[${upperType}] Expect ${path}\'${lowerType} ${action} ${expected}, but is ${actual}` }, equal: function<T extends string | number> (type: string, path: string[], actual: T, expected: T, result: DiffResult[], message?: string) { if (actual === expected) { return true } // 正则模板 === 字符串最终值 if (type === 'type' && expected === 'regexp' && actual === 'string') { return true } result.push(Assert.createDiffResult( type, path, actual, expected, message, 'is equal to' )) return false }, // actual matches expected match: function (type: string, path: string[], actual: any, expected: RegExp, result: DiffResult[], message?: string) { if (expected.test(actual)) { return true } result.push(Assert.createDiffResult( type, path, actual, expected, message, 'matches' )) return false }, greaterThanOrEqualTo: function (type: string, path: string[], actual: number, expected: number, result: DiffResult[], message?: string) { if (actual >= expected) { return true } result.push(Assert.createDiffResult( type, path, actual, expected, message, 'is greater than or equal to' )) return false }, lessThanOrEqualTo: function (type: string, path: string[], actual: number, expected: number, result: DiffResult[], message?: string) { if (actual <= expected) { return true } result.push(Assert.createDiffResult( type, path, actual, expected, message, 'is less than or equal to' )) return false }, createDiffResult: function (type: string, path: string[], actual: any, expected: any, message: string | undefined, action: string) { const item = { path: path, type: type, actual: actual, expected: expected, action: action, message: message } item.message = Assert.message(item) return item } } const valid = function (template: string | object, data: string | object) { const schema = toJSONSchema(template) return Diff.diff(schema, data) } valid.Diff = Diff valid.Assert = Assert export default valid