| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445 |
- /*
- ## valid(template, data)
- 校验真实数据 data 是否与数据模板 template 匹配。
-
- 实现思路:
- 1. 解析规则。
- 先把数据模板 template 解析为更方便机器解析的 JSON-Schame
- 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-Schama validator](http://json-schema-validator.herokuapp.com/)
- [Regexp Demo](http://demos.forbeslindesay.co.uk/regexp/)
- */
- var Constant = require('../constant')
- var Util = require('../util')
- var toJSONSchema = require('../schema')
- function valid(template, data) {
- var schema = toJSONSchema(template)
- var result = Diff.diff(schema, data)
- for (var i = 0; i < result.length; i++) {
- // console.log(template, data)
- // console.warn(Assert.message(result[i]))
- }
- return result
- }
- /*
- ## 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': [{}, {} ...]` 检测个数,继续递归
- 无生成规则:检测全部的元素个数,继续递归
- */
- var Diff = {
- diff: function diff(schema, data, name /* Internal Use Only */ ) {
- var result = []
- // 先检测名称 name 和类型 type,如果匹配,才有必要继续检测
- if (
- this.name(schema, data, name, result) &&
- this.type(schema, data, name, result)
- ) {
- this.value(schema, data, name, result)
- this.properties(schema, data, name, result)
- this.items(schema, data, name, result)
- }
- return result
- },
- /* jshint unused:false */
- name: function(schema, data, name, result) {
- var length = result.length
- Assert.equal('name', schema.path, name + '', schema.name + '', result)
- return result.length === length
- },
- type: function(schema, data, name, result) {
- var length = result.length
- switch (schema.type) {
- case 'string':
- // 跳过含有『占位符』的属性值,因为『占位符』返回值的类型可能和模板不一致,例如 '@int' 会返回一个整形值
- if (schema.template.match(Constant.RE_PLACEHOLDER)) return true
- break
- case 'array':
- 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
- }
- break
- case 'function':
- // 跳过 `'name': function`,因为函数可以返回任何类型的值。
- return true
- }
- Assert.equal('type', schema.path, Util.type(data), schema.type, result)
- return result.length === length
- },
- value: function(schema, data, name, result) {
- var length = result.length
- var rule = schema.rule
- var templateType = schema.type
- if (templateType === 'object' || templateType === 'array' || templateType === 'function') return true
- // 无生成规则
- if (!rule.parameters) {
- switch (templateType) {
- case 'regexp':
- Assert.match('value', schema.path, data, schema.template, result)
- return result.length === length
- case 'string':
- // 同样跳过含有『占位符』的属性值,因为『占位符』的返回值会通常会与模板不一致
- if (schema.template.match(Constant.RE_PLACEHOLDER)) return result.length === length
- break
- }
- Assert.equal('value', schema.path, data, schema.template, result)
- return result.length === length
- }
- // 有生成规则
- var actualRepeatCount
- switch (templateType) {
- case 'number':
- var parts = (data + '').split('.')
- parts[0] = +parts[0]
- // 整数部分
- // |min-max
- if (rule.min !== undefined && rule.max !== undefined) {
- Assert.greaterThanOrEqualTo('value', schema.path, parts[0], Math.min(rule.min, rule.max), result)
- // , 'numeric instance is lower than the required minimum (minimum: {expected}, found: {actual})')
- Assert.lessThanOrEqualTo('value', schema.path, parts[0], Math.max(rule.min, rule.max), result)
- }
- // |count
- if (rule.min !== undefined && rule.max === undefined) {
- Assert.equal('value', schema.path, parts[0], rule.min, result, '[value] ' + name)
- }
- // 小数部分
- if (rule.decimal) {
- // |dmin-dmax
- if (rule.dmin !== undefined && rule.dmax !== undefined) {
- Assert.greaterThanOrEqualTo('value', schema.path, parts[1].length, rule.dmin, result)
- Assert.lessThanOrEqualTo('value', schema.path, parts[1].length, rule.dmax, result)
- }
- // |dcount
- if (rule.dmin !== undefined && rule.dmax === undefined) {
- Assert.equal('value', schema.path, parts[1].length, rule.dmin, result)
- }
- }
- break
- case 'boolean':
- break
- case 'string':
- // '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, rule.min, result)
- Assert.lessThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.max, result)
- }
- // |count
- if (rule.min !== undefined && rule.max === undefined) {
- Assert.equal('repeat count', schema.path, actualRepeatCount, rule.min, result)
- }
- break
- case 'regexp':
- 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, rule.min, result)
- Assert.lessThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.max, result)
- }
- // |count
- if (rule.min !== undefined && rule.max === undefined) {
- Assert.equal('repeat count', schema.path, actualRepeatCount, rule.min, result)
- }
- break
- }
- return result.length === length
- },
- properties: function(schema, data, name, result) {
- var length = result.length
- var rule = schema.rule
- var keys = Util.keys(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(rule.min, rule.max), result)
- Assert.lessThanOrEqualTo('properties length', schema.path, keys.length, Math.max(rule.min, 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, rule.min, result)
- }
- }
- if (result.length !== length) return false
- for (var i = 0; i < keys.length; i++) {
- result.push.apply(
- result,
- this.diff(
- function() {
- var property
- Util.each(schema.properties, function(item /*, index*/ ) {
- if (item.name === keys[i]) property = item
- })
- return property || schema.properties[i]
- }(),
- data[keys[i]],
- keys[i]
- )
- )
- }
- return result.length === length
- },
- items: function(schema, data, name, result) {
- var length = result.length
- if (!schema.items) return
- var 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(rule.min, 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(rule.min, 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, (rule.min * schema.items.length), result)
- }
- // |+inc
- if (rule.parameters[2]) return result.length === length
- }
- if (result.length !== length) return false
- for (var i = 0; i < data.length; i++) {
- result.push.apply(
- result,
- this.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.
- */
- var Assert = {
- message: function(item) {
- return (item.message ||
- '[{utype}] Expect {path}\'{ltype} {action} {expected}, but is {actual}')
- .replace('{utype}', item.type.toUpperCase())
- .replace('{ltype}', item.type.toLowerCase())
- .replace('{path}', Util.isArray(item.path) && item.path.join('.') || item.path)
- .replace('{action}', item.action)
- .replace('{expected}', item.expected)
- .replace('{actual}', item.actual)
- },
- equal: function(type, path, actual, expected, result, message) {
- if (actual === expected) return true
- switch (type) {
- case 'type':
- // 正则模板 === 字符串最终值
- if (expected === 'regexp' && actual === 'string') return true
- break
- }
- var item = {
- path: path,
- type: type,
- actual: actual,
- expected: expected,
- action: 'is equal to',
- message: message
- }
- item.message = Assert.message(item)
- result.push(item)
- return false
- },
- // actual matches expected
- match: function(type, path, actual, expected, result, message) {
- if (expected.test(actual)) return true
- var item = {
- path: path,
- type: type,
- actual: actual,
- expected: expected,
- action: 'matches',
- message: message
- }
- item.message = Assert.message(item)
- result.push(item)
- return false
- },
- notEqual: function(type, path, actual, expected, result, message) {
- if (actual !== expected) return true
- var item = {
- path: path,
- type: type,
- actual: actual,
- expected: expected,
- action: 'is not equal to',
- message: message
- }
- item.message = Assert.message(item)
- result.push(item)
- return false
- },
- greaterThan: function(type, path, actual, expected, result, message) {
- if (actual > expected) return true
- var item = {
- path: path,
- type: type,
- actual: actual,
- expected: expected,
- action: 'is greater than',
- message: message
- }
- item.message = Assert.message(item)
- result.push(item)
- return false
- },
- lessThan: function(type, path, actual, expected, result, message) {
- if (actual < expected) return true
- var item = {
- path: path,
- type: type,
- actual: actual,
- expected: expected,
- action: 'is less to',
- message: message
- }
- item.message = Assert.message(item)
- result.push(item)
- return false
- },
- greaterThanOrEqualTo: function(type, path, actual, expected, result, message) {
- if (actual >= expected) return true
- var item = {
- path: path,
- type: type,
- actual: actual,
- expected: expected,
- action: 'is greater than or equal to',
- message: message
- }
- item.message = Assert.message(item)
- result.push(item)
- return false
- },
- lessThanOrEqualTo: function(type, path, actual, expected, result, message) {
- if (actual <= expected) return true
- var item = {
- path: path,
- type: type,
- actual: actual,
- expected: expected,
- action: 'is less than or equal to',
- message: message
- }
- item.message = Assert.message(item)
- result.push(item)
- return false
- }
- }
- valid.Diff = Diff
- valid.Assert = Assert
- module.exports = valid
|