05e0f032b6246f66aff2e3e92a03ee44ee9f73ccf96b73b005c8699ee380007c29030f5c8b9c2e13a1dc7f718c84c3cffa2de38ef920b59530b70c09d26894-exec 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. 'use strict';
  2. const Hoek = require('@hapi/hoek');
  3. const Any = require('../any');
  4. const Cast = require('../../cast');
  5. const Ref = require('../../ref');
  6. const internals = {};
  7. internals.Alternatives = class extends Any {
  8. constructor() {
  9. super();
  10. this._type = 'alternatives';
  11. this._invalids.remove(null);
  12. this._inner.matches = [];
  13. }
  14. _init(...args) {
  15. return args.length ? this.try(...args) : this;
  16. }
  17. _base(value, state, options) {
  18. const errors = [];
  19. const il = this._inner.matches.length;
  20. const baseType = this._baseType;
  21. for (let i = 0; i < il; ++i) {
  22. const item = this._inner.matches[i];
  23. if (!item.schema) {
  24. const schema = item.peek || item.is;
  25. const input = item.is ? item.ref(state.reference || state.parent, options) : value;
  26. const failed = schema._validate(input, null, options, state.parent).errors;
  27. if (failed) {
  28. if (item.otherwise) {
  29. return item.otherwise._validate(value, state, options);
  30. }
  31. }
  32. else if (item.then) {
  33. return item.then._validate(value, state, options);
  34. }
  35. if (i === (il - 1) && baseType) {
  36. return baseType._validate(value, state, options);
  37. }
  38. continue;
  39. }
  40. const result = item.schema._validate(value, state, options);
  41. if (!result.errors) { // Found a valid match
  42. return result;
  43. }
  44. errors.push(...result.errors);
  45. }
  46. if (errors.length) {
  47. return { errors: this.createError('alternatives.child', { reason: errors }, state, options) };
  48. }
  49. return { errors: this.createError('alternatives.base', null, state, options) };
  50. }
  51. try(...schemas) {
  52. schemas = Hoek.flatten(schemas);
  53. Hoek.assert(schemas.length, 'Cannot add other alternatives without at least one schema');
  54. const obj = this.clone();
  55. for (let i = 0; i < schemas.length; ++i) {
  56. const cast = Cast.schema(this._currentJoi, schemas[i]);
  57. if (cast._refs.length) {
  58. obj._refs.push(...cast._refs);
  59. }
  60. obj._inner.matches.push({ schema: cast });
  61. }
  62. return obj;
  63. }
  64. when(condition, options) {
  65. let schemaCondition = false;
  66. Hoek.assert(Ref.isRef(condition) || typeof condition === 'string' || (schemaCondition = condition instanceof Any), 'Invalid condition:', condition);
  67. Hoek.assert(options, 'Missing options');
  68. Hoek.assert(typeof options === 'object', 'Invalid options');
  69. if (schemaCondition) {
  70. Hoek.assert(!options.hasOwnProperty('is'), '"is" can not be used with a schema condition');
  71. }
  72. else {
  73. Hoek.assert(options.hasOwnProperty('is'), 'Missing "is" directive');
  74. }
  75. Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"');
  76. const obj = this.clone();
  77. let is;
  78. if (!schemaCondition) {
  79. is = Cast.schema(this._currentJoi, options.is);
  80. if (options.is === null || !(Ref.isRef(options.is) || options.is instanceof Any)) {
  81. // Only apply required if this wasn't already a schema or a ref, we'll suppose people know what they're doing
  82. is = is.required();
  83. }
  84. }
  85. const item = {
  86. ref: schemaCondition ? null : Cast.ref(condition),
  87. peek: schemaCondition ? condition : null,
  88. is,
  89. then: options.then !== undefined ? Cast.schema(this._currentJoi, options.then) : undefined,
  90. otherwise: options.otherwise !== undefined ? Cast.schema(this._currentJoi, options.otherwise) : undefined
  91. };
  92. if (obj._baseType) {
  93. item.then = item.then && obj._baseType.concat(item.then);
  94. item.otherwise = item.otherwise && obj._baseType.concat(item.otherwise);
  95. }
  96. if (!schemaCondition) {
  97. Ref.push(obj._refs, item.ref);
  98. obj._refs.push(...item.is._refs);
  99. }
  100. if (item.then && item.then._refs.length) {
  101. obj._refs.push(...item.then._refs);
  102. }
  103. if (item.otherwise && item.otherwise._refs.length) {
  104. obj._refs.push(...item.otherwise._refs);
  105. }
  106. obj._inner.matches.push(item);
  107. return obj;
  108. }
  109. label(name) {
  110. const obj = super.label(name);
  111. obj._inner.matches = obj._inner.matches.map((match) => {
  112. if (match.schema) {
  113. return { schema: match.schema.label(name) };
  114. }
  115. match = Object.assign({}, match);
  116. if (match.then) {
  117. match.then = match.then.label(name);
  118. }
  119. if (match.otherwise) {
  120. match.otherwise = match.otherwise.label(name);
  121. }
  122. return match;
  123. });
  124. return obj;
  125. }
  126. describe() {
  127. const description = super.describe();
  128. const alternatives = [];
  129. for (let i = 0; i < this._inner.matches.length; ++i) {
  130. const item = this._inner.matches[i];
  131. if (item.schema) {
  132. // try()
  133. alternatives.push(item.schema.describe());
  134. }
  135. else {
  136. // when()
  137. const when = item.is ? {
  138. ref: item.ref.toString(),
  139. is: item.is.describe()
  140. } : {
  141. peek: item.peek.describe()
  142. };
  143. if (item.then) {
  144. when.then = item.then.describe();
  145. }
  146. if (item.otherwise) {
  147. when.otherwise = item.otherwise.describe();
  148. }
  149. alternatives.push(when);
  150. }
  151. }
  152. description.alternatives = alternatives;
  153. return description;
  154. }
  155. };
  156. module.exports = new internals.Alternatives();