schema-validator.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. var pattern = {
  2. email: /^\S+?@\S+?\.\S+?$/,
  3. url: new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", 'i')
  4. };
  5. const FORMAT_MAPPING = {
  6. "int": 'number',
  7. "bool": 'boolean',
  8. "double": 'number',
  9. "long": 'number'
  10. }
  11. function formatMessage(args, resources) {
  12. var defaultMessage = ['label']
  13. defaultMessage.forEach((item) => {
  14. if (args[item] === undefined) {
  15. args[item] = ''
  16. }
  17. })
  18. let str = resources
  19. for (let key in args) {
  20. let reg = new RegExp('{' + key + '}')
  21. str = str.replace(reg, args[key])
  22. }
  23. return str
  24. }
  25. function isEmptyValue(value, type) {
  26. if (value === undefined || value === null) {
  27. return true;
  28. }
  29. if (typeof value === 'string' && !value) {
  30. return true;
  31. }
  32. if (type === 'array' && Array.isArray(value) && !value.length) {
  33. return true;
  34. }
  35. return false;
  36. }
  37. class RuleValidator {
  38. constructor(message) {
  39. this._message = message
  40. }
  41. validateRule(key, value, data) {
  42. var result = null
  43. let rules = key.rules
  44. let hasRequired = rules.findIndex((item) => { return item.required })
  45. if (value === undefined && hasRequired < 0) {
  46. return result
  47. }
  48. var message = this._message
  49. if (rules === undefined) {
  50. return message['default']
  51. }
  52. for (var i = 0; i < rules.length; i++) {
  53. let rule = rules[i]
  54. let vt = this._getValidateType(rule)
  55. if (key.label !== undefined) {
  56. Object.assign(rule, { label: key.label })
  57. }
  58. if (RuleValidatorHelper[vt]) {
  59. var v = RuleValidatorHelper[vt](rule, value, message)
  60. if (v != null) {
  61. result = v
  62. break
  63. }
  64. }
  65. if (rule.validator) {
  66. var res = rule.validator(rule, value, data)
  67. if (!res) {
  68. result = formatMessage(rule, rule.errorMessage || message[vt] || message['default'])
  69. break
  70. }
  71. }
  72. }
  73. return result
  74. }
  75. _getValidateType(rule) {
  76. // TODO
  77. var result = ''
  78. if (rule.required) {
  79. result = 'required'
  80. } else if (rule.enum || rule.maximum || rule.minimum || rule.maxLength || rule.minLength) {
  81. result = 'range'
  82. } else if (rule.format) {
  83. result = 'format'
  84. } else if (rule.pattern) {
  85. result = 'pattern'
  86. } else if (rule.validate) {
  87. result = 'validate'
  88. }
  89. return result
  90. }
  91. }
  92. const RuleValidatorHelper = {
  93. required(rule, value, message) {
  94. if (rule.required && isEmptyValue(value, rule.format)) {
  95. return formatMessage(rule, rule.errorMessage || message.required);
  96. }
  97. return null
  98. },
  99. range(rule, value, message) {
  100. var type = (rule.maximum || rule.minimum) ? 1 : 0
  101. var min = type ? rule.minimum : rule.minLength;
  102. var max = type ? rule.maximum : rule.maxLength;
  103. var key = ['string', 'number'][type];
  104. var val = type ? value : value.length;
  105. var enumValue = rule.enum;
  106. if (enumValue) {
  107. if (enumValue.indexOf(value) < 0) {
  108. return formatMessage(rule, message[key].len);
  109. } else {
  110. return null;
  111. }
  112. } else if (min && !max && val < min) {
  113. return formatMessage(rule, rule.errorMessage || message[key].min)
  114. } else if (max && !min && val > max) {
  115. return formatMessage(rule, rule.errorMessage || message[key].max)
  116. } else if (min && max && (val < min || val > max)) {
  117. return formatMessage(rule, rule.errorMessage || message[key].range)
  118. }
  119. return null
  120. },
  121. pattern(rule, value, message) {
  122. if (!types['pattern'](rule.pattern, value)) {
  123. return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
  124. }
  125. return null
  126. },
  127. format(rule, value, message) {
  128. var customTypes = Object.keys(types);
  129. var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : rule.format;
  130. if (customTypes.indexOf(format) > -1) {
  131. if (!types[format](value)) {
  132. return formatMessage(rule, rule.errorMessage || message.types[format]);
  133. }
  134. }
  135. return null
  136. }
  137. }
  138. const types = {
  139. integer(value) {
  140. return types.number(value) && parseInt(value, 10) === value;
  141. },
  142. string(value) {
  143. return typeof value === 'string';
  144. },
  145. number(value) {
  146. if (isNaN(value)) {
  147. return false;
  148. }
  149. return typeof value === 'number';
  150. },
  151. "boolean": function (value) {
  152. return typeof value === 'boolean';
  153. },
  154. "float": function (value) {
  155. return types.number(value) && !types.integer(value);
  156. },
  157. array(value) {
  158. return Array.isArray(value);
  159. },
  160. object(value) {
  161. return typeof value === 'object' && !types.array(value);
  162. },
  163. date(value) {
  164. var v
  165. if (value instanceof Date) {
  166. v = value;
  167. } else {
  168. v = new Date(value);
  169. }
  170. return typeof v.getTime === 'function' && typeof v.getMonth === 'function' && typeof v.getYear === 'function' && !isNaN(v.getTime());
  171. },
  172. timestamp(value) {
  173. if (!this.integer(value) || Math.abs(value).toString().length > 16) {
  174. return false
  175. }
  176. return this.date(value);
  177. },
  178. email(value) {
  179. return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255;
  180. },
  181. url(value) {
  182. return typeof value === 'string' && !!value.match(pattern.url);
  183. },
  184. pattern(reg, value) {
  185. try {
  186. return new RegExp(reg).test(value);
  187. } catch (e) {
  188. return false;
  189. }
  190. },
  191. method(value) {
  192. return typeof value === 'function';
  193. }
  194. }
  195. class SchemaValidator extends RuleValidator {
  196. constructor(schema, options) {
  197. super(SchemaValidator.message);
  198. this._schema = schema
  199. this._options = options || null
  200. }
  201. updateSchema(schema) {
  202. this._schema = schema
  203. }
  204. validate(data) {
  205. var checkResult = this._checkField(data)
  206. if (checkResult) {
  207. return checkResult
  208. }
  209. var result = this.invokeValidate(data, false)
  210. return result.length ? result[0] : null
  211. }
  212. validateAll(data) {
  213. var checkResult = this._checkField(data)
  214. if (checkResult) {
  215. return checkResult
  216. }
  217. return this.invokeValidate(data, true)
  218. }
  219. validateUpdate(data) {
  220. var checkResult = this._checkField(data)
  221. if (checkResult) {
  222. return checkResult
  223. }
  224. var result = this.invokeValidateUpdate(data, false)
  225. return result.length ? result[0] : null
  226. }
  227. invokeValidate(data, all) {
  228. let result = []
  229. let schema = this._schema
  230. for (let key in schema) {
  231. let value = schema[key]
  232. let vir = this.validateRule(value, data[key], data)
  233. if (vir != null) {
  234. result.push({
  235. key: key,
  236. errorMessage: vir
  237. })
  238. if (!all) break
  239. }
  240. }
  241. return result
  242. }
  243. invokeValidateUpdate(data, all) {
  244. let result = []
  245. for (let key in data) {
  246. let vir = this.validateRule(this._schema[key], data[key], data)
  247. if (vir != null) {
  248. result.push({
  249. key: key,
  250. errorMessage: vir
  251. })
  252. if (!all) break
  253. }
  254. }
  255. return result
  256. }
  257. _checkField(data) {
  258. var keys = Object.keys(data)
  259. var keys2 = Object.keys(this._schema)
  260. if (new Set(keys.concat(keys2)).size === keys2.length) {
  261. return ''
  262. }
  263. return [{ key: 'invalid', errorMessage: SchemaValidator.message['defaultInvalid'] }]
  264. }
  265. }
  266. function Message() {
  267. return {
  268. default: '验证错误',
  269. defaultInvalid: '非法字段',
  270. required: '{label}必填',
  271. 'enum': '{label}不合法',
  272. whitespace: '{label}不能为空',
  273. date: {
  274. format: '{label}日期{value}格式无效',
  275. parse: '{label}日期无法解析,{value}无效',
  276. invalid: '{label}日期{value}无效'
  277. },
  278. types: {
  279. string: '{label}类型无效',
  280. array: '{label}类型无效',
  281. object: '{label}类型无效',
  282. number: '{label}类型无效',
  283. date: '{label}类型无效',
  284. boolean: '{label}类型无效',
  285. integer: '{label}类型无效',
  286. float: '{label}类型无效',
  287. regexp: '{label}无效',
  288. email: '{label}类型无效',
  289. url: '{label}类型无效'
  290. },
  291. string: {
  292. len: '{label}必须为{length}个字符',
  293. min: '{label}不能少于 {minLength}个字符',
  294. max: '{label}不能超过 {maxLength}个字符',
  295. range: '{label}必须介于 {minLength}和{maxLength}个字符之间'
  296. },
  297. number: {
  298. len: '{label}必须等于{length}',
  299. min: '{label}不能小于{minimum}',
  300. max: '{label}不能大于{maximum}',
  301. range: '{label}必须介于{minimum}and{maximum}之间'
  302. },
  303. pattern: {
  304. mismatch: '{label} 格式不匹配'
  305. }
  306. };
  307. }
  308. SchemaValidator.message = new Message();
  309. export default SchemaValidator