Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

435 строки
13 KiB

  1. 'use strict';
  2. // Load modules
  3. const Hoek = require('hoek');
  4. const Any = require('./types/any');
  5. const Cast = require('./cast');
  6. const Errors = require('./errors');
  7. const Lazy = require('./types/lazy');
  8. const Ref = require('./ref');
  9. // Declare internals
  10. const internals = {
  11. alternatives: require('./types/alternatives'),
  12. array: require('./types/array'),
  13. boolean: require('./types/boolean'),
  14. binary: require('./types/binary'),
  15. date: require('./types/date'),
  16. func: require('./types/func'),
  17. number: require('./types/number'),
  18. object: require('./types/object'),
  19. string: require('./types/string')
  20. };
  21. internals.applyDefaults = function (schema) {
  22. Hoek.assert(this, 'Must be invoked on a Joi instance.');
  23. if (this._defaults) {
  24. schema = this._defaults(schema);
  25. }
  26. schema._currentJoi = this;
  27. return schema;
  28. };
  29. internals.root = function () {
  30. const any = new Any();
  31. const root = any.clone();
  32. Any.prototype._currentJoi = root;
  33. root._currentJoi = root;
  34. root.any = function () {
  35. Hoek.assert(arguments.length === 0, 'Joi.any() does not allow arguments.');
  36. return internals.applyDefaults.call(this, any);
  37. };
  38. root.alternatives = root.alt = function () {
  39. const alternatives = internals.applyDefaults.call(this, internals.alternatives);
  40. return arguments.length ? alternatives.try.apply(alternatives, arguments) : alternatives;
  41. };
  42. root.array = function () {
  43. Hoek.assert(arguments.length === 0, 'Joi.array() does not allow arguments.');
  44. return internals.applyDefaults.call(this, internals.array);
  45. };
  46. root.boolean = root.bool = function () {
  47. Hoek.assert(arguments.length === 0, 'Joi.boolean() does not allow arguments.');
  48. return internals.applyDefaults.call(this, internals.boolean);
  49. };
  50. root.binary = function () {
  51. Hoek.assert(arguments.length === 0, 'Joi.binary() does not allow arguments.');
  52. return internals.applyDefaults.call(this, internals.binary);
  53. };
  54. root.date = function () {
  55. Hoek.assert(arguments.length === 0, 'Joi.date() does not allow arguments.');
  56. return internals.applyDefaults.call(this, internals.date);
  57. };
  58. root.func = function () {
  59. Hoek.assert(arguments.length === 0, 'Joi.func() does not allow arguments.');
  60. return internals.applyDefaults.call(this, internals.func);
  61. };
  62. root.number = function () {
  63. Hoek.assert(arguments.length === 0, 'Joi.number() does not allow arguments.');
  64. return internals.applyDefaults.call(this, internals.number);
  65. };
  66. root.object = function () {
  67. const object = internals.applyDefaults.call(this, internals.object);
  68. return arguments.length ? object.keys.apply(object, arguments) : object;
  69. };
  70. root.string = function () {
  71. Hoek.assert(arguments.length === 0, 'Joi.string() does not allow arguments.');
  72. return internals.applyDefaults.call(this, internals.string);
  73. };
  74. root.ref = function () {
  75. return Ref.create.apply(null, arguments);
  76. };
  77. root.isRef = function (ref) {
  78. return Ref.isRef(ref);
  79. };
  80. root.validate = function (value /*, [schema], [options], callback */) {
  81. const last = arguments[arguments.length - 1];
  82. const callback = typeof last === 'function' ? last : null;
  83. const count = arguments.length - (callback ? 1 : 0);
  84. if (count === 1) {
  85. return any.validate(value, callback);
  86. }
  87. const options = count === 3 ? arguments[2] : {};
  88. const schema = root.compile(arguments[1]);
  89. return schema._validateWithOptions(value, options, callback);
  90. };
  91. root.describe = function () {
  92. const schema = arguments.length ? root.compile(arguments[0]) : any;
  93. return schema.describe();
  94. };
  95. root.compile = function (schema) {
  96. try {
  97. return Cast.schema(this, schema);
  98. }
  99. catch (err) {
  100. if (err.hasOwnProperty('path')) {
  101. err.message = err.message + '(' + err.path + ')';
  102. }
  103. throw err;
  104. }
  105. };
  106. root.assert = function (value, schema, message) {
  107. root.attempt(value, schema, message);
  108. };
  109. root.attempt = function (value, schema, message) {
  110. const result = root.validate(value, schema);
  111. const error = result.error;
  112. if (error) {
  113. if (!message) {
  114. if (typeof error.annotate === 'function') {
  115. error.message = error.annotate();
  116. }
  117. throw error;
  118. }
  119. if (!(message instanceof Error)) {
  120. if (typeof error.annotate === 'function') {
  121. error.message = `${message} ${error.annotate()}`;
  122. }
  123. throw error;
  124. }
  125. throw message;
  126. }
  127. return result.value;
  128. };
  129. root.reach = function (schema, path) {
  130. Hoek.assert(schema && schema instanceof Any, 'you must provide a joi schema');
  131. Hoek.assert(typeof path === 'string', 'path must be a string');
  132. if (path === '') {
  133. return schema;
  134. }
  135. const parts = path.split('.');
  136. const children = schema._inner.children;
  137. if (!children) {
  138. return;
  139. }
  140. const key = parts[0];
  141. for (let i = 0; i < children.length; ++i) {
  142. const child = children[i];
  143. if (child.key === key) {
  144. return this.reach(child.schema, path.substr(key.length + 1));
  145. }
  146. }
  147. };
  148. root.lazy = function (fn) {
  149. return Lazy.set(fn);
  150. };
  151. root.defaults = function (fn) {
  152. Hoek.assert(typeof fn === 'function', 'Defaults must be a function');
  153. let joi = Object.create(this.any());
  154. joi = fn(joi);
  155. Hoek.assert(joi && joi instanceof this.constructor, 'defaults() must return a schema');
  156. Object.assign(joi, this, joi.clone()); // Re-add the types from `this` but also keep the settings from joi's potential new defaults
  157. joi._defaults = (schema) => {
  158. if (this._defaults) {
  159. schema = this._defaults(schema);
  160. Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');
  161. }
  162. schema = fn(schema);
  163. Hoek.assert(schema instanceof this.constructor, 'defaults() must return a schema');
  164. return schema;
  165. };
  166. return joi;
  167. };
  168. root.extend = function () {
  169. const extensions = Hoek.flatten(Array.prototype.slice.call(arguments));
  170. Hoek.assert(extensions.length > 0, 'You need to provide at least one extension');
  171. this.assert(extensions, root.extensionsSchema);
  172. const joi = Object.create(this.any());
  173. Object.assign(joi, this);
  174. for (let i = 0; i < extensions.length; ++i) {
  175. let extension = extensions[i];
  176. if (typeof extension === 'function') {
  177. extension = extension(joi);
  178. }
  179. this.assert(extension, root.extensionSchema);
  180. const base = (extension.base || this.any()).clone(); // Cloning because we're going to override language afterwards
  181. const ctor = base.constructor;
  182. const type = class extends ctor { // eslint-disable-line no-loop-func
  183. constructor() {
  184. super();
  185. if (extension.base) {
  186. Object.assign(this, base);
  187. }
  188. this._type = extension.name;
  189. if (extension.language) {
  190. this._settings = this._settings || { language: {} };
  191. this._settings.language = Hoek.applyToDefaults(this._settings.language, {
  192. [extension.name]: extension.language
  193. });
  194. }
  195. }
  196. };
  197. if (extension.coerce) {
  198. type.prototype._coerce = function (value, state, options) {
  199. if (ctor.prototype._coerce) {
  200. const baseRet = ctor.prototype._coerce.call(this, value, state, options);
  201. if (baseRet.errors) {
  202. return baseRet;
  203. }
  204. value = baseRet.value;
  205. }
  206. const ret = extension.coerce.call(this, value, state, options);
  207. if (ret instanceof Errors.Err) {
  208. return { value, errors: ret };
  209. }
  210. return { value: ret };
  211. };
  212. }
  213. if (extension.pre) {
  214. type.prototype._base = function (value, state, options) {
  215. if (ctor.prototype._base) {
  216. const baseRet = ctor.prototype._base.call(this, value, state, options);
  217. if (baseRet.errors) {
  218. return baseRet;
  219. }
  220. value = baseRet.value;
  221. }
  222. const ret = extension.pre.call(this, value, state, options);
  223. if (ret instanceof Errors.Err) {
  224. return { value, errors: ret };
  225. }
  226. return { value: ret };
  227. };
  228. }
  229. if (extension.rules) {
  230. for (let j = 0; j < extension.rules.length; ++j) {
  231. const rule = extension.rules[j];
  232. const ruleArgs = rule.params ?
  233. (rule.params instanceof Any ? rule.params._inner.children.map((k) => k.key) : Object.keys(rule.params)) :
  234. [];
  235. const validateArgs = rule.params ? Cast.schema(this, rule.params) : null;
  236. type.prototype[rule.name] = function () { // eslint-disable-line no-loop-func
  237. if (arguments.length > ruleArgs.length) {
  238. throw new Error('Unexpected number of arguments');
  239. }
  240. const args = Array.prototype.slice.call(arguments);
  241. let hasRef = false;
  242. let arg = {};
  243. for (let k = 0; k < ruleArgs.length; ++k) {
  244. arg[ruleArgs[k]] = args[k];
  245. if (!hasRef && Ref.isRef(args[k])) {
  246. hasRef = true;
  247. }
  248. }
  249. if (validateArgs) {
  250. arg = joi.attempt(arg, validateArgs);
  251. }
  252. let schema;
  253. if (rule.validate) {
  254. const validate = function (value, state, options) {
  255. return rule.validate.call(this, arg, value, state, options);
  256. };
  257. schema = this._test(rule.name, arg, validate, {
  258. description: rule.description,
  259. hasRef
  260. });
  261. }
  262. else {
  263. schema = this.clone();
  264. }
  265. if (rule.setup) {
  266. const newSchema = rule.setup.call(schema, arg);
  267. if (newSchema !== undefined) {
  268. Hoek.assert(newSchema instanceof Any, `Setup of extension Joi.${this._type}().${rule.name}() must return undefined or a Joi object`);
  269. schema = newSchema;
  270. }
  271. }
  272. return schema;
  273. };
  274. }
  275. }
  276. if (extension.describe) {
  277. type.prototype.describe = function () {
  278. const description = ctor.prototype.describe.call(this);
  279. return extension.describe.call(this, description);
  280. };
  281. }
  282. const instance = new type();
  283. joi[extension.name] = function () {
  284. return internals.applyDefaults.call(this, instance);
  285. };
  286. }
  287. return joi;
  288. };
  289. root.extensionSchema = internals.object.keys({
  290. base: internals.object.type(Any, 'Joi object'),
  291. name: internals.string.required(),
  292. coerce: internals.func.arity(3),
  293. pre: internals.func.arity(3),
  294. language: internals.object,
  295. describe: internals.func.arity(1),
  296. rules: internals.array.items(internals.object.keys({
  297. name: internals.string.required(),
  298. setup: internals.func.arity(1),
  299. validate: internals.func.arity(4),
  300. params: [
  301. internals.object.pattern(/.*/, internals.object.type(Any, 'Joi object')),
  302. internals.object.type(internals.object.constructor, 'Joi object')
  303. ],
  304. description: [internals.string, internals.func.arity(1)]
  305. }).or('setup', 'validate'))
  306. }).strict();
  307. root.extensionsSchema = internals.array.items([internals.object, internals.func.arity(1)]).strict();
  308. root.version = require('../package.json').version;
  309. return root;
  310. };
  311. module.exports = internals.root();