index.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import { format, complementError, asyncMap, warning, deepMerge } from './util.js';
  2. import validators from './validator/index.js';
  3. import { messages as defaultMessages, newMessages } from './messages.js';
  4. /**
  5. * Encapsulates a validation schema.
  6. *
  7. * @param descriptor An object declaring validation rules
  8. * for this schema.
  9. */
  10. function Schema(descriptor) {
  11. this.rules = null;
  12. this._messages = defaultMessages;
  13. this.define(descriptor);
  14. }
  15. Schema.prototype = {
  16. messages(messages) {
  17. if (messages) {
  18. this._messages = deepMerge(newMessages(), messages);
  19. }
  20. return this._messages;
  21. },
  22. define(rules) {
  23. if (!rules) {
  24. throw new Error(
  25. 'Cannot configure a schema with no rules');
  26. }
  27. if (typeof rules !== 'object' || Array.isArray(rules)) {
  28. throw new Error('Rules must be an object');
  29. }
  30. this.rules = {};
  31. let z;
  32. let item;
  33. for (z in rules) {
  34. if (rules.hasOwnProperty(z)) {
  35. item = rules[z];
  36. this.rules[z] = Array.isArray(item) ? item : [item];
  37. }
  38. }
  39. },
  40. validate(source_, o = {}, oc) {
  41. let source = source_;
  42. let options = o;
  43. let callback = oc;
  44. if (typeof options === 'function') {
  45. callback = options;
  46. options = {};
  47. }
  48. if (!this.rules || Object.keys(this.rules).length === 0) {
  49. if (callback) {
  50. callback();
  51. }
  52. return;
  53. }
  54. function complete(results) {
  55. let i;
  56. let field;
  57. let errors = [];
  58. let fields = {};
  59. function add(e) {
  60. if (Array.isArray(e)) {
  61. errors = errors.concat.apply(errors, e);
  62. } else {
  63. errors.push(e);
  64. }
  65. }
  66. for (i = 0; i < results.length; i++) {
  67. add(results[i]);
  68. }
  69. if (!errors.length) {
  70. errors = null;
  71. fields = null;
  72. } else {
  73. for (i = 0; i < errors.length; i++) {
  74. field = errors[i].field;
  75. fields[field] = fields[field] || [];
  76. fields[field].push(errors[i]);
  77. }
  78. }
  79. callback(errors, fields);
  80. }
  81. if (options.messages) {
  82. let messages = this.messages();
  83. if (messages === defaultMessages) {
  84. messages = newMessages();
  85. }
  86. deepMerge(messages, options.messages);
  87. options.messages = messages;
  88. } else {
  89. options.messages = this.messages();
  90. }
  91. let arr;
  92. let value;
  93. const series = {};
  94. const keys = options.keys || Object.keys(this.rules);
  95. keys.forEach((z) => {
  96. arr = this.rules[z];
  97. value = source[z];
  98. arr.forEach((r) => {
  99. let rule = r;
  100. if (typeof (rule.transform) === 'function') {
  101. if (source === source_) {
  102. source = { ...source };
  103. }
  104. value = source[z] = rule.transform(value);
  105. }
  106. if (typeof (rule) === 'function') {
  107. rule = {
  108. validator: rule,
  109. };
  110. } else {
  111. rule = { ...rule };
  112. }
  113. rule.validator = this.getValidationMethod(rule);
  114. rule.field = z;
  115. rule.fullField = rule.fullField || z;
  116. rule.type = this.getType(rule);
  117. if (!rule.validator) {
  118. return;
  119. }
  120. series[z] = series[z] || [];
  121. series[z].push({
  122. rule,
  123. value,
  124. source,
  125. field: z,
  126. });
  127. });
  128. });
  129. const errorFields = {};
  130. asyncMap(series, options, (data, doIt) => {
  131. const rule = data.rule;
  132. let deep = (rule.type === 'object' || rule.type === 'array') &&
  133. (typeof (rule.fields) === 'object' || typeof (rule.defaultField) === 'object');
  134. deep = deep && (rule.required || (!rule.required && data.value));
  135. rule.field = data.field;
  136. function addFullfield(key, schema) {
  137. return {
  138. ...schema,
  139. fullField: `${rule.fullField}.${key}`,
  140. };
  141. }
  142. function cb(e = []) {
  143. let errors = e;
  144. if (!Array.isArray(errors)) {
  145. errors = [errors];
  146. }
  147. if (errors.length) {
  148. warning('async-validator:', errors);
  149. }
  150. if (errors.length && rule.message) {
  151. errors = [].concat(rule.message);
  152. }
  153. errors = errors.map(complementError(rule));
  154. if (options.first && errors.length) {
  155. errorFields[rule.field] = 1;
  156. return doIt(errors);
  157. }
  158. if (!deep) {
  159. doIt(errors);
  160. } else {
  161. // if rule is required but the target object
  162. // does not exist fail at the rule level and don't
  163. // go deeper
  164. if (rule.required && !data.value) {
  165. if (rule.message) {
  166. errors = [].concat(rule.message).map(complementError(rule));
  167. } else if (options.error) {
  168. errors = [options.error(rule, format(options.messages.required, rule.field))];
  169. } else {
  170. errors = [];
  171. }
  172. return doIt(errors);
  173. }
  174. let fieldsSchema = {};
  175. if (rule.defaultField) {
  176. for (const k in data.value) {
  177. if (data.value.hasOwnProperty(k)) {
  178. fieldsSchema[k] = rule.defaultField;
  179. }
  180. }
  181. }
  182. fieldsSchema = {
  183. ...fieldsSchema,
  184. ...data.rule.fields,
  185. };
  186. for (const f in fieldsSchema) {
  187. if (fieldsSchema.hasOwnProperty(f)) {
  188. const fieldSchema = Array.isArray(fieldsSchema[f]) ?
  189. fieldsSchema[f] : [fieldsSchema[f]];
  190. fieldsSchema[f] = fieldSchema.map(addFullfield.bind(null, f));
  191. }
  192. }
  193. const schema = new Schema(fieldsSchema);
  194. schema.messages(options.messages);
  195. if (data.rule.options) {
  196. data.rule.options.messages = options.messages;
  197. data.rule.options.error = options.error;
  198. }
  199. schema.validate(data.value, data.rule.options || options, (errs) => {
  200. doIt(errs && errs.length ? errors.concat(errs) : errs);
  201. });
  202. }
  203. }
  204. const res = rule.validator(
  205. rule, data.value, cb, data.source, options);
  206. if (res && res.then) {
  207. res.then(() => cb(), e => cb(e));
  208. }
  209. }, (results) => {
  210. complete(results);
  211. });
  212. },
  213. getType(rule) {
  214. if (rule.type === undefined && (rule.pattern instanceof RegExp)) {
  215. rule.type = 'pattern';
  216. }
  217. if (typeof (rule.validator) !== 'function' &&
  218. (rule.type && !validators.hasOwnProperty(rule.type))) {
  219. throw new Error(format('Unknown rule type %s', rule.type));
  220. }
  221. return rule.type || 'string';
  222. },
  223. getValidationMethod(rule) {
  224. if (typeof rule.validator === 'function') {
  225. return rule.validator;
  226. }
  227. const keys = Object.keys(rule);
  228. const messageIndex = keys.indexOf('message');
  229. if (messageIndex !== -1) {
  230. keys.splice(messageIndex, 1);
  231. }
  232. if (keys.length === 1 && keys[0] === 'required') {
  233. return validators.required;
  234. }
  235. return validators[this.getType(rule)] || false;
  236. },
  237. };
  238. Schema.register = function register(type, validator) {
  239. if (typeof validator !== 'function') {
  240. throw new Error('Cannot register a validator by type, validator is not a function');
  241. }
  242. validators[type] = validator;
  243. };
  244. Schema.messages = defaultMessages;
  245. export default Schema;