c645de658fe3a4e3f6b8791310acca6d25d081cdda4b1b341f716e9d4d88e9d1f243801c231635b6dcfd2bfe99e5eb1e37bbd48b7d15d496d2a353adb9a24a 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. /*
  2. ## valid(template, data)
  3. 校验真实数据 data 是否与数据模板 template 匹配。
  4. 实现思路:
  5. 1. 解析规则。
  6. 先把数据模板 template 解析为更方便机器解析的 JSON-Schame
  7. name 属性名
  8. type 属性值类型
  9. template 属性值模板
  10. properties 对象属性数组
  11. items 数组元素数组
  12. rule 属性值生成规则
  13. 2. 递归验证规则。
  14. 然后用 JSON-Schema 校验真实数据,校验项包括属性名、值类型、值、值生成规则。
  15. 提示信息
  16. https://github.com/fge/json-schema-validator/blob/master/src/main/resources/com/github/fge/jsonschema/validator/validation.properties
  17. [JSON-Schama validator](http://json-schema-validator.herokuapp.com/)
  18. [Regexp Demo](http://demos.forbeslindesay.co.uk/regexp/)
  19. */
  20. var Constant = require('../constant')
  21. var Util = require('../util')
  22. var toJSONSchema = require('../schema')
  23. function valid(template, data) {
  24. var schema = toJSONSchema(template)
  25. var result = Diff.diff(schema, data)
  26. for (var i = 0; i < result.length; i++) {
  27. // console.log(template, data)
  28. // console.warn(Assert.message(result[i]))
  29. }
  30. return result
  31. }
  32. /*
  33. ## name
  34. 有生成规则:比较解析后的 name
  35. 无生成规则:直接比较
  36. ## type
  37. 无类型转换:直接比较
  38. 有类型转换:先试着解析 template,然后再检查?
  39. ## value vs. template
  40. 基本类型
  41. 无生成规则:直接比较
  42. 有生成规则:
  43. number
  44. min-max.dmin-dmax
  45. min-max.dcount
  46. count.dmin-dmax
  47. count.dcount
  48. +step
  49. 整数部分
  50. 小数部分
  51. boolean
  52. string
  53. min-max
  54. count
  55. ## properties
  56. 对象
  57. 有生成规则:检测期望的属性个数,继续递归
  58. 无生成规则:检测全部的属性个数,继续递归
  59. ## items
  60. 数组
  61. 有生成规则:
  62. `'name|1': [{}, {} ...]` 其中之一,继续递归
  63. `'name|+1': [{}, {} ...]` 顺序检测,继续递归
  64. `'name|min-max': [{}, {} ...]` 检测个数,继续递归
  65. `'name|count': [{}, {} ...]` 检测个数,继续递归
  66. 无生成规则:检测全部的元素个数,继续递归
  67. */
  68. var Diff = {
  69. diff: function diff(schema, data, name /* Internal Use Only */ ) {
  70. var result = []
  71. // 先检测名称 name 和类型 type,如果匹配,才有必要继续检测
  72. if (
  73. this.name(schema, data, name, result) &&
  74. this.type(schema, data, name, result)
  75. ) {
  76. this.value(schema, data, name, result)
  77. this.properties(schema, data, name, result)
  78. this.items(schema, data, name, result)
  79. }
  80. return result
  81. },
  82. /* jshint unused:false */
  83. name: function(schema, data, name, result) {
  84. var length = result.length
  85. Assert.equal('name', schema.path, name + '', schema.name + '', result)
  86. return result.length === length
  87. },
  88. type: function(schema, data, name, result) {
  89. var length = result.length
  90. switch (schema.type) {
  91. case 'string':
  92. // 跳过含有『占位符』的属性值,因为『占位符』返回值的类型可能和模板不一致,例如 '@int' 会返回一个整形值
  93. if (schema.template.match(Constant.RE_PLACEHOLDER)) return true
  94. break
  95. case 'array':
  96. if (schema.rule.parameters) {
  97. // name|count: array
  98. if (schema.rule.min !== undefined && schema.rule.max === undefined) {
  99. // 跳过 name|1: array,因为最终值的类型(很可能)不是数组,也不一定与 `array` 中的类型一致
  100. if (schema.rule.count === 1) return true
  101. }
  102. // 跳过 name|+inc: array
  103. if (schema.rule.parameters[2]) return true
  104. }
  105. break
  106. case 'function':
  107. // 跳过 `'name': function`,因为函数可以返回任何类型的值。
  108. return true
  109. }
  110. Assert.equal('type', schema.path, Util.type(data), schema.type, result)
  111. return result.length === length
  112. },
  113. value: function(schema, data, name, result) {
  114. var length = result.length
  115. var rule = schema.rule
  116. var templateType = schema.type
  117. if (templateType === 'object' || templateType === 'array' || templateType === 'function') return true
  118. // 无生成规则
  119. if (!rule.parameters) {
  120. switch (templateType) {
  121. case 'regexp':
  122. Assert.match('value', schema.path, data, schema.template, result)
  123. return result.length === length
  124. case 'string':
  125. // 同样跳过含有『占位符』的属性值,因为『占位符』的返回值会通常会与模板不一致
  126. if (schema.template.match(Constant.RE_PLACEHOLDER)) return result.length === length
  127. break
  128. }
  129. Assert.equal('value', schema.path, data, schema.template, result)
  130. return result.length === length
  131. }
  132. // 有生成规则
  133. var actualRepeatCount
  134. switch (templateType) {
  135. case 'number':
  136. var parts = (data + '').split('.')
  137. parts[0] = +parts[0]
  138. // 整数部分
  139. // |min-max
  140. if (rule.min !== undefined && rule.max !== undefined) {
  141. Assert.greaterThanOrEqualTo('value', schema.path, parts[0], Math.min(rule.min, rule.max), result)
  142. // , 'numeric instance is lower than the required minimum (minimum: {expected}, found: {actual})')
  143. Assert.lessThanOrEqualTo('value', schema.path, parts[0], Math.max(rule.min, rule.max), result)
  144. }
  145. // |count
  146. if (rule.min !== undefined && rule.max === undefined) {
  147. Assert.equal('value', schema.path, parts[0], rule.min, result, '[value] ' + name)
  148. }
  149. // 小数部分
  150. if (rule.decimal) {
  151. // |dmin-dmax
  152. if (rule.dmin !== undefined && rule.dmax !== undefined) {
  153. Assert.greaterThanOrEqualTo('value', schema.path, parts[1].length, rule.dmin, result)
  154. Assert.lessThanOrEqualTo('value', schema.path, parts[1].length, rule.dmax, result)
  155. }
  156. // |dcount
  157. if (rule.dmin !== undefined && rule.dmax === undefined) {
  158. Assert.equal('value', schema.path, parts[1].length, rule.dmin, result)
  159. }
  160. }
  161. break
  162. case 'boolean':
  163. break
  164. case 'string':
  165. // 'aaa'.match(/a/g)
  166. actualRepeatCount = data.match(new RegExp(schema.template, 'g'))
  167. actualRepeatCount = actualRepeatCount ? actualRepeatCount.length : 0
  168. // |min-max
  169. if (rule.min !== undefined && rule.max !== undefined) {
  170. Assert.greaterThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.min, result)
  171. Assert.lessThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.max, result)
  172. }
  173. // |count
  174. if (rule.min !== undefined && rule.max === undefined) {
  175. Assert.equal('repeat count', schema.path, actualRepeatCount, rule.min, result)
  176. }
  177. break
  178. case 'regexp':
  179. actualRepeatCount = data.match(new RegExp(schema.template.source.replace(/^\^|\$$/g, ''), 'g'))
  180. actualRepeatCount = actualRepeatCount ? actualRepeatCount.length : 0
  181. // |min-max
  182. if (rule.min !== undefined && rule.max !== undefined) {
  183. Assert.greaterThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.min, result)
  184. Assert.lessThanOrEqualTo('repeat count', schema.path, actualRepeatCount, rule.max, result)
  185. }
  186. // |count
  187. if (rule.min !== undefined && rule.max === undefined) {
  188. Assert.equal('repeat count', schema.path, actualRepeatCount, rule.min, result)
  189. }
  190. break
  191. }
  192. return result.length === length
  193. },
  194. properties: function(schema, data, name, result) {
  195. var length = result.length
  196. var rule = schema.rule
  197. var keys = Util.keys(data)
  198. if (!schema.properties) return
  199. // 无生成规则
  200. if (!schema.rule.parameters) {
  201. Assert.equal('properties length', schema.path, keys.length, schema.properties.length, result)
  202. } else {
  203. // 有生成规则
  204. // |min-max
  205. if (rule.min !== undefined && rule.max !== undefined) {
  206. Assert.greaterThanOrEqualTo('properties length', schema.path, keys.length, Math.min(rule.min, rule.max), result)
  207. Assert.lessThanOrEqualTo('properties length', schema.path, keys.length, Math.max(rule.min, rule.max), result)
  208. }
  209. // |count
  210. if (rule.min !== undefined && rule.max === undefined) {
  211. // |1, |>1
  212. if (rule.count !== 1) Assert.equal('properties length', schema.path, keys.length, rule.min, result)
  213. }
  214. }
  215. if (result.length !== length) return false
  216. for (var i = 0; i < keys.length; i++) {
  217. result.push.apply(
  218. result,
  219. this.diff(
  220. function() {
  221. var property
  222. Util.each(schema.properties, function(item /*, index*/ ) {
  223. if (item.name === keys[i]) property = item
  224. })
  225. return property || schema.properties[i]
  226. }(),
  227. data[keys[i]],
  228. keys[i]
  229. )
  230. )
  231. }
  232. return result.length === length
  233. },
  234. items: function(schema, data, name, result) {
  235. var length = result.length
  236. if (!schema.items) return
  237. var rule = schema.rule
  238. // 无生成规则
  239. if (!schema.rule.parameters) {
  240. Assert.equal('items length', schema.path, data.length, schema.items.length, result)
  241. } else {
  242. // 有生成规则
  243. // |min-max
  244. if (rule.min !== undefined && rule.max !== undefined) {
  245. Assert.greaterThanOrEqualTo('items', schema.path, data.length, (Math.min(rule.min, rule.max) * schema.items.length), result,
  246. '[{utype}] array is too short: {path} must have at least {expected} elements but instance has {actual} elements')
  247. Assert.lessThanOrEqualTo('items', schema.path, data.length, (Math.max(rule.min, rule.max) * schema.items.length), result,
  248. '[{utype}] array is too long: {path} must have at most {expected} elements but instance has {actual} elements')
  249. }
  250. // |count
  251. if (rule.min !== undefined && rule.max === undefined) {
  252. // |1, |>1
  253. if (rule.count === 1) return result.length === length
  254. else Assert.equal('items length', schema.path, data.length, (rule.min * schema.items.length), result)
  255. }
  256. // |+inc
  257. if (rule.parameters[2]) return result.length === length
  258. }
  259. if (result.length !== length) return false
  260. for (var i = 0; i < data.length; i++) {
  261. result.push.apply(
  262. result,
  263. this.diff(
  264. schema.items[i % schema.items.length],
  265. data[i],
  266. i % schema.items.length
  267. )
  268. )
  269. }
  270. return result.length === length
  271. }
  272. }
  273. /*
  274. 完善、友好的提示信息
  275. Equal, not equal to, greater than, less than, greater than or equal to, less than or equal to
  276. 路径 验证类型 描述
  277. Expect path.name is less than or equal to expected, but path.name is actual.
  278. Expect path.name is less than or equal to expected, but path.name is actual.
  279. Expect path.name is greater than or equal to expected, but path.name is actual.
  280. */
  281. var Assert = {
  282. message: function(item) {
  283. return (item.message ||
  284. '[{utype}] Expect {path}\'{ltype} {action} {expected}, but is {actual}')
  285. .replace('{utype}', item.type.toUpperCase())
  286. .replace('{ltype}', item.type.toLowerCase())
  287. .replace('{path}', Util.isArray(item.path) && item.path.join('.') || item.path)
  288. .replace('{action}', item.action)
  289. .replace('{expected}', item.expected)
  290. .replace('{actual}', item.actual)
  291. },
  292. equal: function(type, path, actual, expected, result, message) {
  293. if (actual === expected) return true
  294. switch (type) {
  295. case 'type':
  296. // 正则模板 === 字符串最终值
  297. if (expected === 'regexp' && actual === 'string') return true
  298. break
  299. }
  300. var item = {
  301. path: path,
  302. type: type,
  303. actual: actual,
  304. expected: expected,
  305. action: 'is equal to',
  306. message: message
  307. }
  308. item.message = Assert.message(item)
  309. result.push(item)
  310. return false
  311. },
  312. // actual matches expected
  313. match: function(type, path, actual, expected, result, message) {
  314. if (expected.test(actual)) return true
  315. var item = {
  316. path: path,
  317. type: type,
  318. actual: actual,
  319. expected: expected,
  320. action: 'matches',
  321. message: message
  322. }
  323. item.message = Assert.message(item)
  324. result.push(item)
  325. return false
  326. },
  327. notEqual: function(type, path, actual, expected, result, message) {
  328. if (actual !== expected) return true
  329. var item = {
  330. path: path,
  331. type: type,
  332. actual: actual,
  333. expected: expected,
  334. action: 'is not equal to',
  335. message: message
  336. }
  337. item.message = Assert.message(item)
  338. result.push(item)
  339. return false
  340. },
  341. greaterThan: function(type, path, actual, expected, result, message) {
  342. if (actual > expected) return true
  343. var item = {
  344. path: path,
  345. type: type,
  346. actual: actual,
  347. expected: expected,
  348. action: 'is greater than',
  349. message: message
  350. }
  351. item.message = Assert.message(item)
  352. result.push(item)
  353. return false
  354. },
  355. lessThan: function(type, path, actual, expected, result, message) {
  356. if (actual < expected) return true
  357. var item = {
  358. path: path,
  359. type: type,
  360. actual: actual,
  361. expected: expected,
  362. action: 'is less to',
  363. message: message
  364. }
  365. item.message = Assert.message(item)
  366. result.push(item)
  367. return false
  368. },
  369. greaterThanOrEqualTo: function(type, path, actual, expected, result, message) {
  370. if (actual >= expected) return true
  371. var item = {
  372. path: path,
  373. type: type,
  374. actual: actual,
  375. expected: expected,
  376. action: 'is greater than or equal to',
  377. message: message
  378. }
  379. item.message = Assert.message(item)
  380. result.push(item)
  381. return false
  382. },
  383. lessThanOrEqualTo: function(type, path, actual, expected, result, message) {
  384. if (actual <= expected) return true
  385. var item = {
  386. path: path,
  387. type: type,
  388. actual: actual,
  389. expected: expected,
  390. action: 'is less than or equal to',
  391. message: message
  392. }
  393. item.message = Assert.message(item)
  394. result.push(item)
  395. return false
  396. }
  397. }
  398. valid.Diff = Diff
  399. valid.Assert = Assert
  400. module.exports = valid