index.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. 'use strict';
  2. const Assert = require('@hapi/hoek/lib/assert');
  3. const Clone = require('@hapi/hoek/lib/clone');
  4. const Cache = require('./cache');
  5. const Common = require('./common');
  6. const Compile = require('./compile');
  7. const Errors = require('./errors');
  8. const Extend = require('./extend');
  9. const Manifest = require('./manifest');
  10. const Ref = require('./ref');
  11. const Template = require('./template');
  12. const Trace = require('./trace');
  13. let Schemas;
  14. const internals = {
  15. types: {
  16. alternatives: require('./types/alternatives'),
  17. any: require('./types/any'),
  18. array: require('./types/array'),
  19. boolean: require('./types/boolean'),
  20. date: require('./types/date'),
  21. function: require('./types/function'),
  22. link: require('./types/link'),
  23. number: require('./types/number'),
  24. object: require('./types/object'),
  25. string: require('./types/string'),
  26. symbol: require('./types/symbol')
  27. },
  28. aliases: {
  29. alt: 'alternatives',
  30. bool: 'boolean',
  31. func: 'function'
  32. }
  33. };
  34. if (Buffer) { // $lab:coverage:ignore$
  35. internals.types.binary = require('./types/binary');
  36. }
  37. internals.root = function () {
  38. const root = {
  39. _types: new Set(Object.keys(internals.types))
  40. };
  41. // Types
  42. for (const type of root._types) {
  43. root[type] = function (...args) {
  44. Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');
  45. return internals.generate(this, internals.types[type], args);
  46. };
  47. }
  48. // Shortcuts
  49. for (const method of ['allow', 'custom', 'disallow', 'equal', 'exist', 'forbidden', 'invalid', 'not', 'only', 'optional', 'options', 'prefs', 'preferences', 'required', 'strip', 'valid', 'when']) {
  50. root[method] = function (...args) {
  51. return this.any()[method](...args);
  52. };
  53. }
  54. // Methods
  55. Object.assign(root, internals.methods);
  56. // Aliases
  57. for (const alias in internals.aliases) {
  58. const target = internals.aliases[alias];
  59. root[alias] = root[target];
  60. }
  61. root.x = root.expression;
  62. // Trace
  63. if (Trace.setup) { // $lab:coverage:ignore$
  64. Trace.setup(root);
  65. }
  66. return root;
  67. };
  68. internals.methods = {
  69. ValidationError: Errors.ValidationError,
  70. version: Common.version,
  71. cache: Cache.provider,
  72. assert(value, schema, ...args /* [message], [options] */) {
  73. internals.assert(value, schema, true, args);
  74. },
  75. attempt(value, schema, ...args /* [message], [options] */) {
  76. return internals.assert(value, schema, false, args);
  77. },
  78. build(desc) {
  79. Assert(typeof Manifest.build === 'function', 'Manifest functionality disabled');
  80. return Manifest.build(this, desc);
  81. },
  82. checkPreferences(prefs) {
  83. Common.checkPreferences(prefs);
  84. },
  85. compile(schema, options) {
  86. return Compile.compile(this, schema, options);
  87. },
  88. defaults(modifier) {
  89. Assert(typeof modifier === 'function', 'modifier must be a function');
  90. const joi = Object.assign({}, this);
  91. for (const type of joi._types) {
  92. const schema = modifier(joi[type]());
  93. Assert(Common.isSchema(schema), 'modifier must return a valid schema object');
  94. joi[type] = function (...args) {
  95. return internals.generate(this, schema, args);
  96. };
  97. }
  98. return joi;
  99. },
  100. expression(...args) {
  101. return new Template(...args);
  102. },
  103. extend(...extensions) {
  104. Common.verifyFlat(extensions, 'extend');
  105. Schemas = Schemas || require('./schemas');
  106. Assert(extensions.length, 'You need to provide at least one extension');
  107. this.assert(extensions, Schemas.extensions);
  108. const joi = Object.assign({}, this);
  109. joi._types = new Set(joi._types);
  110. for (let extension of extensions) {
  111. if (typeof extension === 'function') {
  112. extension = extension(joi);
  113. }
  114. this.assert(extension, Schemas.extension);
  115. const expanded = internals.expandExtension(extension, joi);
  116. for (const item of expanded) {
  117. Assert(joi[item.type] === undefined || joi._types.has(item.type), 'Cannot override name', item.type);
  118. const base = item.base || this.any();
  119. const schema = Extend.type(base, item);
  120. joi._types.add(item.type);
  121. joi[item.type] = function (...args) {
  122. return internals.generate(this, schema, args);
  123. };
  124. }
  125. }
  126. return joi;
  127. },
  128. isError: Errors.ValidationError.isError,
  129. isExpression: Template.isTemplate,
  130. isRef: Ref.isRef,
  131. isSchema: Common.isSchema,
  132. in(...args) {
  133. return Ref.in(...args);
  134. },
  135. override: Common.symbols.override,
  136. ref(...args) {
  137. return Ref.create(...args);
  138. },
  139. types() {
  140. const types = {};
  141. for (const type of this._types) {
  142. types[type] = this[type]();
  143. }
  144. for (const target in internals.aliases) {
  145. types[target] = this[target]();
  146. }
  147. return types;
  148. }
  149. };
  150. // Helpers
  151. internals.assert = function (value, schema, annotate, args /* [message], [options] */) {
  152. const message = args[0] instanceof Error || typeof args[0] === 'string' ? args[0] : null;
  153. const options = message !== null ? args[1] : args[0];
  154. const result = schema.validate(value, Common.preferences({ errors: { stack: true } }, options || {}));
  155. let error = result.error;
  156. if (!error) {
  157. return result.value;
  158. }
  159. if (message instanceof Error) {
  160. throw message;
  161. }
  162. const display = annotate && typeof error.annotate === 'function' ? error.annotate() : error.message;
  163. if (error instanceof Errors.ValidationError === false) {
  164. error = Clone(error);
  165. }
  166. error.message = message ? `${message} ${display}` : display;
  167. throw error;
  168. };
  169. internals.generate = function (root, schema, args) {
  170. Assert(root, 'Must be invoked on a Joi instance.');
  171. schema.$_root = root;
  172. if (!schema._definition.args ||
  173. !args.length) {
  174. return schema;
  175. }
  176. return schema._definition.args(schema, ...args);
  177. };
  178. internals.expandExtension = function (extension, joi) {
  179. if (typeof extension.type === 'string') {
  180. return [extension];
  181. }
  182. const extended = [];
  183. for (const type of joi._types) {
  184. if (extension.type.test(type)) {
  185. const item = Object.assign({}, extension);
  186. item.type = type;
  187. item.base = joi[type]();
  188. extended.push(item);
  189. }
  190. }
  191. return extended;
  192. };
  193. module.exports = internals.root();