base.js 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. 'use strict';
  2. const Assert = require('@hapi/hoek/lib/assert');
  3. const Clone = require('@hapi/hoek/lib/clone');
  4. const DeepEqual = require('@hapi/hoek/lib/deepEqual');
  5. const Merge = require('@hapi/hoek/lib/merge');
  6. const Cache = require('./cache');
  7. const Common = require('./common');
  8. const Compile = require('./compile');
  9. const Errors = require('./errors');
  10. const Extend = require('./extend');
  11. const Manifest = require('./manifest');
  12. const Messages = require('./messages');
  13. const Modify = require('./modify');
  14. const Ref = require('./ref');
  15. const Trace = require('./trace');
  16. const Validator = require('./validator');
  17. const Values = require('./values');
  18. const internals = {};
  19. internals.Base = class {
  20. constructor(type) {
  21. // Naming: public, _private, $_extension, $_mutate{action}
  22. this.type = type;
  23. this.$_root = null;
  24. this._definition = {};
  25. this._reset();
  26. }
  27. _reset() {
  28. this._ids = new Modify.Ids();
  29. this._preferences = null;
  30. this._refs = new Ref.Manager();
  31. this._cache = null;
  32. this._valids = null;
  33. this._invalids = null;
  34. this._flags = {};
  35. this._rules = [];
  36. this._singleRules = new Map(); // The rule options passed for non-multi rules
  37. this.$_terms = {}; // Hash of arrays of immutable objects (extended by other types)
  38. this.$_temp = { // Runtime state (not cloned)
  39. ruleset: null, // null: use last, false: error, number: start position
  40. whens: {} // Runtime cache of generated whens
  41. };
  42. }
  43. // Manifest
  44. describe() {
  45. Assert(typeof Manifest.describe === 'function', 'Manifest functionality disabled');
  46. return Manifest.describe(this);
  47. }
  48. // Rules
  49. allow(...values) {
  50. Common.verifyFlat(values, 'allow');
  51. return this._values(values, '_valids');
  52. }
  53. alter(targets) {
  54. Assert(targets && typeof targets === 'object' && !Array.isArray(targets), 'Invalid targets argument');
  55. Assert(!this._inRuleset(), 'Cannot set alterations inside a ruleset');
  56. const obj = this.clone();
  57. obj.$_terms.alterations = obj.$_terms.alterations || [];
  58. for (const target in targets) {
  59. const adjuster = targets[target];
  60. Assert(typeof adjuster === 'function', 'Alteration adjuster for', target, 'must be a function');
  61. obj.$_terms.alterations.push({ target, adjuster });
  62. }
  63. obj.$_temp.ruleset = false;
  64. return obj;
  65. }
  66. artifact(id) {
  67. Assert(id !== undefined, 'Artifact cannot be undefined');
  68. Assert(!this._cache, 'Cannot set an artifact with a rule cache');
  69. return this.$_setFlag('artifact', id);
  70. }
  71. cast(to) {
  72. Assert(to === false || typeof to === 'string', 'Invalid to value');
  73. Assert(to === false || this._definition.cast[to], 'Type', this.type, 'does not support casting to', to);
  74. return this.$_setFlag('cast', to === false ? undefined : to);
  75. }
  76. default(value, options) {
  77. return this._default('default', value, options);
  78. }
  79. description(desc) {
  80. Assert(desc && typeof desc === 'string', 'Description must be a non-empty string');
  81. return this.$_setFlag('description', desc);
  82. }
  83. empty(schema) {
  84. const obj = this.clone();
  85. if (schema !== undefined) {
  86. schema = obj.$_compile(schema, { override: false });
  87. }
  88. return obj.$_setFlag('empty', schema, { clone: false });
  89. }
  90. error(err) {
  91. Assert(err, 'Missing error');
  92. Assert(err instanceof Error || typeof err === 'function', 'Must provide a valid Error object or a function');
  93. return this.$_setFlag('error', err);
  94. }
  95. example(example, options = {}) {
  96. Assert(example !== undefined, 'Missing example');
  97. Common.assertOptions(options, ['override']);
  98. return this._inner('examples', example, { single: true, override: options.override });
  99. }
  100. external(method, description) {
  101. if (typeof method === 'object') {
  102. Assert(!description, 'Cannot combine options with description');
  103. description = method.description;
  104. method = method.method;
  105. }
  106. Assert(typeof method === 'function', 'Method must be a function');
  107. Assert(description === undefined || description && typeof description === 'string', 'Description must be a non-empty string');
  108. return this._inner('externals', { method, description }, { single: true });
  109. }
  110. failover(value, options) {
  111. return this._default('failover', value, options);
  112. }
  113. forbidden() {
  114. return this.presence('forbidden');
  115. }
  116. id(id) {
  117. if (!id) {
  118. return this.$_setFlag('id', undefined);
  119. }
  120. Assert(typeof id === 'string', 'id must be a non-empty string');
  121. Assert(/^[^\.]+$/.test(id), 'id cannot contain period character');
  122. return this.$_setFlag('id', id);
  123. }
  124. invalid(...values) {
  125. return this._values(values, '_invalids');
  126. }
  127. label(name) {
  128. Assert(name && typeof name === 'string', 'Label name must be a non-empty string');
  129. return this.$_setFlag('label', name);
  130. }
  131. meta(meta) {
  132. Assert(meta !== undefined, 'Meta cannot be undefined');
  133. return this._inner('metas', meta, { single: true });
  134. }
  135. note(...notes) {
  136. Assert(notes.length, 'Missing notes');
  137. for (const note of notes) {
  138. Assert(note && typeof note === 'string', 'Notes must be non-empty strings');
  139. }
  140. return this._inner('notes', notes);
  141. }
  142. only(mode = true) {
  143. Assert(typeof mode === 'boolean', 'Invalid mode:', mode);
  144. return this.$_setFlag('only', mode);
  145. }
  146. optional() {
  147. return this.presence('optional');
  148. }
  149. prefs(prefs) {
  150. Assert(prefs, 'Missing preferences');
  151. Assert(prefs.context === undefined, 'Cannot override context');
  152. Assert(prefs.externals === undefined, 'Cannot override externals');
  153. Assert(prefs.warnings === undefined, 'Cannot override warnings');
  154. Assert(prefs.debug === undefined, 'Cannot override debug');
  155. Common.checkPreferences(prefs);
  156. const obj = this.clone();
  157. obj._preferences = Common.preferences(obj._preferences, prefs);
  158. return obj;
  159. }
  160. presence(mode) {
  161. Assert(['optional', 'required', 'forbidden'].includes(mode), 'Unknown presence mode', mode);
  162. return this.$_setFlag('presence', mode);
  163. }
  164. raw(enabled = true) {
  165. return this.$_setFlag('result', enabled ? 'raw' : undefined);
  166. }
  167. result(mode) {
  168. Assert(['raw', 'strip'].includes(mode), 'Unknown result mode', mode);
  169. return this.$_setFlag('result', mode);
  170. }
  171. required() {
  172. return this.presence('required');
  173. }
  174. strict(enabled) {
  175. const obj = this.clone();
  176. const convert = enabled === undefined ? false : !enabled;
  177. obj._preferences = Common.preferences(obj._preferences, { convert });
  178. return obj;
  179. }
  180. strip(enabled = true) {
  181. return this.$_setFlag('result', enabled ? 'strip' : undefined);
  182. }
  183. tag(...tags) {
  184. Assert(tags.length, 'Missing tags');
  185. for (const tag of tags) {
  186. Assert(tag && typeof tag === 'string', 'Tags must be non-empty strings');
  187. }
  188. return this._inner('tags', tags);
  189. }
  190. unit(name) {
  191. Assert(name && typeof name === 'string', 'Unit name must be a non-empty string');
  192. return this.$_setFlag('unit', name);
  193. }
  194. valid(...values) {
  195. Common.verifyFlat(values, 'valid');
  196. const obj = this.allow(...values);
  197. obj.$_setFlag('only', !!obj._valids, { clone: false });
  198. return obj;
  199. }
  200. when(condition, options) {
  201. const obj = this.clone();
  202. if (!obj.$_terms.whens) {
  203. obj.$_terms.whens = [];
  204. }
  205. const when = Compile.when(obj, condition, options);
  206. if (!['any', 'link'].includes(obj.type)) {
  207. const conditions = when.is ? [when] : when.switch;
  208. for (const item of conditions) {
  209. Assert(!item.then || item.then.type === 'any' || item.then.type === obj.type, 'Cannot combine', obj.type, 'with', item.then && item.then.type);
  210. Assert(!item.otherwise || item.otherwise.type === 'any' || item.otherwise.type === obj.type, 'Cannot combine', obj.type, 'with', item.otherwise && item.otherwise.type);
  211. }
  212. }
  213. obj.$_terms.whens.push(when);
  214. return obj.$_mutateRebuild();
  215. }
  216. // Helpers
  217. cache(cache) {
  218. Assert(!this._inRuleset(), 'Cannot set caching inside a ruleset');
  219. Assert(!this._cache, 'Cannot override schema cache');
  220. Assert(this._flags.artifact === undefined, 'Cannot cache a rule with an artifact');
  221. const obj = this.clone();
  222. obj._cache = cache || Cache.provider.provision();
  223. obj.$_temp.ruleset = false;
  224. return obj;
  225. }
  226. clone() {
  227. const obj = Object.create(Object.getPrototypeOf(this));
  228. return this._assign(obj);
  229. }
  230. concat(source) {
  231. Assert(Common.isSchema(source), 'Invalid schema object');
  232. Assert(this.type === 'any' || source.type === 'any' || source.type === this.type, 'Cannot merge type', this.type, 'with another type:', source.type);
  233. Assert(!this._inRuleset(), 'Cannot concatenate onto a schema with open ruleset');
  234. Assert(!source._inRuleset(), 'Cannot concatenate a schema with open ruleset');
  235. let obj = this.clone();
  236. if (this.type === 'any' &&
  237. source.type !== 'any') {
  238. // Change obj to match source type
  239. const tmpObj = source.clone();
  240. for (const key of Object.keys(obj)) {
  241. if (key !== 'type') {
  242. tmpObj[key] = obj[key];
  243. }
  244. }
  245. obj = tmpObj;
  246. }
  247. obj._ids.concat(source._ids);
  248. obj._refs.register(source, Ref.toSibling);
  249. obj._preferences = obj._preferences ? Common.preferences(obj._preferences, source._preferences) : source._preferences;
  250. obj._valids = Values.merge(obj._valids, source._valids, source._invalids);
  251. obj._invalids = Values.merge(obj._invalids, source._invalids, source._valids);
  252. // Remove unique rules present in source
  253. for (const name of source._singleRules.keys()) {
  254. if (obj._singleRules.has(name)) {
  255. obj._rules = obj._rules.filter((target) => target.keep || target.name !== name);
  256. obj._singleRules.delete(name);
  257. }
  258. }
  259. // Rules
  260. for (const test of source._rules) {
  261. if (!source._definition.rules[test.method].multi) {
  262. obj._singleRules.set(test.name, test);
  263. }
  264. obj._rules.push(test);
  265. }
  266. // Flags
  267. if (obj._flags.empty &&
  268. source._flags.empty) {
  269. obj._flags.empty = obj._flags.empty.concat(source._flags.empty);
  270. const flags = Object.assign({}, source._flags);
  271. delete flags.empty;
  272. Merge(obj._flags, flags);
  273. }
  274. else if (source._flags.empty) {
  275. obj._flags.empty = source._flags.empty;
  276. const flags = Object.assign({}, source._flags);
  277. delete flags.empty;
  278. Merge(obj._flags, flags);
  279. }
  280. else {
  281. Merge(obj._flags, source._flags);
  282. }
  283. // Terms
  284. for (const key in source.$_terms) {
  285. const terms = source.$_terms[key];
  286. if (!terms) {
  287. if (!obj.$_terms[key]) {
  288. obj.$_terms[key] = terms;
  289. }
  290. continue;
  291. }
  292. if (!obj.$_terms[key]) {
  293. obj.$_terms[key] = terms.slice();
  294. continue;
  295. }
  296. obj.$_terms[key] = obj.$_terms[key].concat(terms);
  297. }
  298. // Tracing
  299. if (this.$_root._tracer) {
  300. this.$_root._tracer._combine(obj, [this, source]);
  301. }
  302. // Rebuild
  303. return obj.$_mutateRebuild();
  304. }
  305. extend(options) {
  306. Assert(!options.base, 'Cannot extend type with another base');
  307. return Extend.type(this, options);
  308. }
  309. extract(path) {
  310. path = Array.isArray(path) ? path : path.split('.');
  311. return this._ids.reach(path);
  312. }
  313. fork(paths, adjuster) {
  314. Assert(!this._inRuleset(), 'Cannot fork inside a ruleset');
  315. let obj = this; // eslint-disable-line consistent-this
  316. for (let path of [].concat(paths)) {
  317. path = Array.isArray(path) ? path : path.split('.');
  318. obj = obj._ids.fork(path, adjuster, obj);
  319. }
  320. obj.$_temp.ruleset = false;
  321. return obj;
  322. }
  323. rule(options) {
  324. const def = this._definition;
  325. Common.assertOptions(options, Object.keys(def.modifiers));
  326. Assert(this.$_temp.ruleset !== false, 'Cannot apply rules to empty ruleset or the last rule added does not support rule properties');
  327. const start = this.$_temp.ruleset === null ? this._rules.length - 1 : this.$_temp.ruleset;
  328. Assert(start >= 0 && start < this._rules.length, 'Cannot apply rules to empty ruleset');
  329. const obj = this.clone();
  330. for (let i = start; i < obj._rules.length; ++i) {
  331. const original = obj._rules[i];
  332. const rule = Clone(original);
  333. for (const name in options) {
  334. def.modifiers[name](rule, options[name]);
  335. Assert(rule.name === original.name, 'Cannot change rule name');
  336. }
  337. obj._rules[i] = rule;
  338. if (obj._singleRules.get(rule.name) === original) {
  339. obj._singleRules.set(rule.name, rule);
  340. }
  341. }
  342. obj.$_temp.ruleset = false;
  343. return obj.$_mutateRebuild();
  344. }
  345. get ruleset() {
  346. Assert(!this._inRuleset(), 'Cannot start a new ruleset without closing the previous one');
  347. const obj = this.clone();
  348. obj.$_temp.ruleset = obj._rules.length;
  349. return obj;
  350. }
  351. get $() {
  352. return this.ruleset;
  353. }
  354. tailor(targets) {
  355. targets = [].concat(targets);
  356. Assert(!this._inRuleset(), 'Cannot tailor inside a ruleset');
  357. let obj = this; // eslint-disable-line consistent-this
  358. if (this.$_terms.alterations) {
  359. for (const { target, adjuster } of this.$_terms.alterations) {
  360. if (targets.includes(target)) {
  361. obj = adjuster(obj);
  362. Assert(Common.isSchema(obj), 'Alteration adjuster for', target, 'failed to return a schema object');
  363. }
  364. }
  365. }
  366. obj = obj.$_modify({ each: (item) => item.tailor(targets), ref: false });
  367. obj.$_temp.ruleset = false;
  368. return obj.$_mutateRebuild();
  369. }
  370. tracer() {
  371. return Trace.location ? Trace.location(this) : this; // $lab:coverage:ignore$
  372. }
  373. validate(value, options) {
  374. return Validator.entry(value, this, options);
  375. }
  376. validateAsync(value, options) {
  377. return Validator.entryAsync(value, this, options);
  378. }
  379. // Extensions
  380. $_addRule(options) {
  381. // Normalize rule
  382. if (typeof options === 'string') {
  383. options = { name: options };
  384. }
  385. Assert(options && typeof options === 'object', 'Invalid options');
  386. Assert(options.name && typeof options.name === 'string', 'Invalid rule name');
  387. for (const key in options) {
  388. Assert(key[0] !== '_', 'Cannot set private rule properties');
  389. }
  390. const rule = Object.assign({}, options); // Shallow cloned
  391. rule._resolve = [];
  392. rule.method = rule.method || rule.name;
  393. const definition = this._definition.rules[rule.method];
  394. const args = rule.args;
  395. Assert(definition, 'Unknown rule', rule.method);
  396. // Args
  397. const obj = this.clone();
  398. if (args) {
  399. Assert(Object.keys(args).length === 1 || Object.keys(args).length === this._definition.rules[rule.name].args.length, 'Invalid rule definition for', this.type, rule.name);
  400. for (const key in args) {
  401. let arg = args[key];
  402. if (definition.argsByName) {
  403. const resolver = definition.argsByName.get(key);
  404. if (resolver.ref &&
  405. Common.isResolvable(arg)) {
  406. rule._resolve.push(key);
  407. obj.$_mutateRegister(arg);
  408. }
  409. else {
  410. if (resolver.normalize) {
  411. arg = resolver.normalize(arg);
  412. args[key] = arg;
  413. }
  414. if (resolver.assert) {
  415. const error = Common.validateArg(arg, key, resolver);
  416. Assert(!error, error, 'or reference');
  417. }
  418. }
  419. }
  420. if (arg === undefined) {
  421. delete args[key];
  422. continue;
  423. }
  424. args[key] = arg;
  425. }
  426. }
  427. // Unique rules
  428. if (!definition.multi) {
  429. obj._ruleRemove(rule.name, { clone: false });
  430. obj._singleRules.set(rule.name, rule);
  431. }
  432. if (obj.$_temp.ruleset === false) {
  433. obj.$_temp.ruleset = null;
  434. }
  435. if (definition.priority) {
  436. obj._rules.unshift(rule);
  437. }
  438. else {
  439. obj._rules.push(rule);
  440. }
  441. return obj;
  442. }
  443. $_compile(schema, options) {
  444. return Compile.schema(this.$_root, schema, options);
  445. }
  446. $_createError(code, value, local, state, prefs, options = {}) {
  447. const flags = options.flags !== false ? this._flags : {};
  448. const messages = options.messages ? Messages.merge(this._definition.messages, options.messages) : this._definition.messages;
  449. return new Errors.Report(code, value, local, flags, messages, state, prefs);
  450. }
  451. $_getFlag(name) {
  452. return this._flags[name];
  453. }
  454. $_getRule(name) {
  455. return this._singleRules.get(name);
  456. }
  457. $_mapLabels(path) {
  458. path = Array.isArray(path) ? path : path.split('.');
  459. return this._ids.labels(path);
  460. }
  461. $_match(value, state, prefs, overrides) {
  462. prefs = Object.assign({}, prefs); // Shallow cloned
  463. prefs.abortEarly = true;
  464. prefs._externals = false;
  465. state.snapshot();
  466. const result = !Validator.validate(value, this, state, prefs, overrides).errors;
  467. state.restore();
  468. return result;
  469. }
  470. $_modify(options) {
  471. Common.assertOptions(options, ['each', 'once', 'ref', 'schema']);
  472. return Modify.schema(this, options) || this;
  473. }
  474. $_mutateRebuild() {
  475. Assert(!this._inRuleset(), 'Cannot add this rule inside a ruleset');
  476. this._refs.reset();
  477. this._ids.reset();
  478. const each = (item, { source, name, path, key }) => {
  479. const family = this._definition[source][name] && this._definition[source][name].register;
  480. if (family !== false) {
  481. this.$_mutateRegister(item, { family, key });
  482. }
  483. };
  484. this.$_modify({ each });
  485. if (this._definition.rebuild) {
  486. this._definition.rebuild(this);
  487. }
  488. this.$_temp.ruleset = false;
  489. return this;
  490. }
  491. $_mutateRegister(schema, { family, key } = {}) {
  492. this._refs.register(schema, family);
  493. this._ids.register(schema, { key });
  494. }
  495. $_property(name) {
  496. return this._definition.properties[name];
  497. }
  498. $_reach(path) {
  499. return this._ids.reach(path);
  500. }
  501. $_rootReferences() {
  502. return this._refs.roots();
  503. }
  504. $_setFlag(name, value, options = {}) {
  505. Assert(name[0] === '_' || !this._inRuleset(), 'Cannot set flag inside a ruleset');
  506. const flag = this._definition.flags[name] || {};
  507. if (DeepEqual(value, flag.default)) {
  508. value = undefined;
  509. }
  510. if (DeepEqual(value, this._flags[name])) {
  511. return this;
  512. }
  513. const obj = options.clone !== false ? this.clone() : this;
  514. if (value !== undefined) {
  515. obj._flags[name] = value;
  516. obj.$_mutateRegister(value);
  517. }
  518. else {
  519. delete obj._flags[name];
  520. }
  521. if (name[0] !== '_') {
  522. obj.$_temp.ruleset = false;
  523. }
  524. return obj;
  525. }
  526. $_parent(method, ...args) {
  527. return this[method][Common.symbols.parent].call(this, ...args);
  528. }
  529. $_validate(value, state, prefs) {
  530. return Validator.validate(value, this, state, prefs);
  531. }
  532. // Internals
  533. _assign(target) {
  534. target.type = this.type;
  535. target.$_root = this.$_root;
  536. target.$_temp = Object.assign({}, this.$_temp);
  537. target.$_temp.whens = {};
  538. target._ids = this._ids.clone();
  539. target._preferences = this._preferences;
  540. target._valids = this._valids && this._valids.clone();
  541. target._invalids = this._invalids && this._invalids.clone();
  542. target._rules = this._rules.slice();
  543. target._singleRules = Clone(this._singleRules, { shallow: true });
  544. target._refs = this._refs.clone();
  545. target._flags = Object.assign({}, this._flags);
  546. target._cache = null;
  547. target.$_terms = {};
  548. for (const key in this.$_terms) {
  549. target.$_terms[key] = this.$_terms[key] ? this.$_terms[key].slice() : null;
  550. }
  551. // Backwards compatibility
  552. target.$_super = {};
  553. for (const override in this.$_super) {
  554. target.$_super[override] = this._super[override].bind(target);
  555. }
  556. return target;
  557. }
  558. _bare() {
  559. const obj = this.clone();
  560. obj._reset();
  561. const terms = obj._definition.terms;
  562. for (const name in terms) {
  563. const term = terms[name];
  564. obj.$_terms[name] = term.init;
  565. }
  566. return obj.$_mutateRebuild();
  567. }
  568. _default(flag, value, options = {}) {
  569. Common.assertOptions(options, 'literal');
  570. Assert(value !== undefined, 'Missing', flag, 'value');
  571. Assert(typeof value === 'function' || !options.literal, 'Only function value supports literal option');
  572. if (typeof value === 'function' &&
  573. options.literal) {
  574. value = {
  575. [Common.symbols.literal]: true,
  576. literal: value
  577. };
  578. }
  579. const obj = this.$_setFlag(flag, value);
  580. return obj;
  581. }
  582. _generate(value, state, prefs) {
  583. if (!this.$_terms.whens) {
  584. return { schema: this };
  585. }
  586. // Collect matching whens
  587. const whens = [];
  588. const ids = [];
  589. for (let i = 0; i < this.$_terms.whens.length; ++i) {
  590. const when = this.$_terms.whens[i];
  591. if (when.concat) {
  592. whens.push(when.concat);
  593. ids.push(`${i}.concat`);
  594. continue;
  595. }
  596. const input = when.ref ? when.ref.resolve(value, state, prefs) : value;
  597. const tests = when.is ? [when] : when.switch;
  598. const before = ids.length;
  599. for (let j = 0; j < tests.length; ++j) {
  600. const { is, then, otherwise } = tests[j];
  601. const baseId = `${i}${when.switch ? '.' + j : ''}`;
  602. if (is.$_match(input, state.nest(is, `${baseId}.is`), prefs)) {
  603. if (then) {
  604. const localState = state.localize([...state.path, `${baseId}.then`], state.ancestors, state.schemas);
  605. const { schema: generated, id } = then._generate(value, localState, prefs);
  606. whens.push(generated);
  607. ids.push(`${baseId}.then${id ? `(${id})` : ''}`);
  608. break;
  609. }
  610. }
  611. else if (otherwise) {
  612. const localState = state.localize([...state.path, `${baseId}.otherwise`], state.ancestors, state.schemas);
  613. const { schema: generated, id } = otherwise._generate(value, localState, prefs);
  614. whens.push(generated);
  615. ids.push(`${baseId}.otherwise${id ? `(${id})` : ''}`);
  616. break;
  617. }
  618. }
  619. if (when.break &&
  620. ids.length > before) { // Something matched
  621. break;
  622. }
  623. }
  624. // Check cache
  625. const id = ids.join(', ');
  626. state.mainstay.tracer.debug(state, 'rule', 'when', id);
  627. if (!id) {
  628. return { schema: this };
  629. }
  630. if (!state.mainstay.tracer.active &&
  631. this.$_temp.whens[id]) {
  632. return { schema: this.$_temp.whens[id], id };
  633. }
  634. // Generate dynamic schema
  635. let obj = this; // eslint-disable-line consistent-this
  636. if (this._definition.generate) {
  637. obj = this._definition.generate(this, value, state, prefs);
  638. }
  639. // Apply whens
  640. for (const when of whens) {
  641. obj = obj.concat(when);
  642. }
  643. // Tracing
  644. if (this.$_root._tracer) {
  645. this.$_root._tracer._combine(obj, [this, ...whens]);
  646. }
  647. // Cache result
  648. this.$_temp.whens[id] = obj;
  649. return { schema: obj, id };
  650. }
  651. _inner(type, values, options = {}) {
  652. Assert(!this._inRuleset(), `Cannot set ${type} inside a ruleset`);
  653. const obj = this.clone();
  654. if (!obj.$_terms[type] ||
  655. options.override) {
  656. obj.$_terms[type] = [];
  657. }
  658. if (options.single) {
  659. obj.$_terms[type].push(values);
  660. }
  661. else {
  662. obj.$_terms[type].push(...values);
  663. }
  664. obj.$_temp.ruleset = false;
  665. return obj;
  666. }
  667. _inRuleset() {
  668. return this.$_temp.ruleset !== null && this.$_temp.ruleset !== false;
  669. }
  670. _ruleRemove(name, options = {}) {
  671. if (!this._singleRules.has(name)) {
  672. return this;
  673. }
  674. const obj = options.clone !== false ? this.clone() : this;
  675. obj._singleRules.delete(name);
  676. const filtered = [];
  677. for (let i = 0; i < obj._rules.length; ++i) {
  678. const test = obj._rules[i];
  679. if (test.name === name &&
  680. !test.keep) {
  681. if (obj._inRuleset() &&
  682. i < obj.$_temp.ruleset) {
  683. --obj.$_temp.ruleset;
  684. }
  685. continue;
  686. }
  687. filtered.push(test);
  688. }
  689. obj._rules = filtered;
  690. return obj;
  691. }
  692. _values(values, key) {
  693. Common.verifyFlat(values, key.slice(1, -1));
  694. const obj = this.clone();
  695. const override = values[0] === Common.symbols.override;
  696. if (override) {
  697. values = values.slice(1);
  698. }
  699. if (!obj[key] &&
  700. values.length) {
  701. obj[key] = new Values();
  702. }
  703. else if (override) {
  704. obj[key] = values.length ? new Values() : null;
  705. obj.$_mutateRebuild();
  706. }
  707. if (!obj[key]) {
  708. return obj;
  709. }
  710. if (override) {
  711. obj[key].override();
  712. }
  713. for (const value of values) {
  714. Assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');
  715. Assert(value !== Common.symbols.override, 'Override must be the first value');
  716. const other = key === '_invalids' ? '_valids' : '_invalids';
  717. if (obj[other]) {
  718. obj[other].remove(value);
  719. if (!obj[other].length) {
  720. Assert(key === '_valids' || !obj._flags.only, 'Setting invalid value', value, 'leaves schema rejecting all values due to previous valid rule');
  721. obj[other] = null;
  722. }
  723. }
  724. obj[key].add(value, obj._refs);
  725. }
  726. return obj;
  727. }
  728. };
  729. internals.Base.prototype[Common.symbols.any] = {
  730. version: Common.version,
  731. compile: Compile.compile,
  732. root: '$_root'
  733. };
  734. internals.Base.prototype.isImmutable = true; // Prevents Hoek from deep cloning schema objects (must be on prototype)
  735. // Aliases
  736. internals.Base.prototype.deny = internals.Base.prototype.invalid;
  737. internals.Base.prototype.disallow = internals.Base.prototype.invalid;
  738. internals.Base.prototype.equal = internals.Base.prototype.valid;
  739. internals.Base.prototype.exist = internals.Base.prototype.required;
  740. internals.Base.prototype.not = internals.Base.prototype.invalid;
  741. internals.Base.prototype.options = internals.Base.prototype.prefs;
  742. internals.Base.prototype.preferences = internals.Base.prototype.prefs;
  743. module.exports = new internals.Base();