any.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911
  1. 'use strict';
  2. // Load modules
  3. const Hoek = require('hoek');
  4. const Ref = require('./ref');
  5. const Errors = require('./errors');
  6. let Alternatives = null; // Delay-loaded to prevent circular dependencies
  7. let Cast = null;
  8. // Declare internals
  9. const internals = {};
  10. internals.defaults = {
  11. abortEarly: true,
  12. convert: true,
  13. allowUnknown: false,
  14. skipFunctions: false,
  15. stripUnknown: false,
  16. language: {},
  17. presence: 'optional',
  18. strip: false,
  19. noDefaults: false
  20. // context: null
  21. };
  22. module.exports = internals.Any = class {
  23. constructor() {
  24. Cast = Cast || require('./cast');
  25. this.isJoi = true;
  26. this._type = 'any';
  27. this._settings = null;
  28. this._valids = new internals.Set();
  29. this._invalids = new internals.Set();
  30. this._tests = [];
  31. this._refs = [];
  32. this._flags = {
  33. /*
  34. presence: 'optional', // optional, required, forbidden, ignore
  35. allowOnly: false,
  36. allowUnknown: undefined,
  37. default: undefined,
  38. forbidden: false,
  39. encoding: undefined,
  40. insensitive: false,
  41. trim: false,
  42. case: undefined, // upper, lower
  43. empty: undefined,
  44. func: false,
  45. raw: false
  46. */
  47. };
  48. this._description = null;
  49. this._unit = null;
  50. this._notes = [];
  51. this._tags = [];
  52. this._examples = [];
  53. this._meta = [];
  54. this._inner = {}; // Hash of arrays of immutable objects
  55. }
  56. createError(type, context, state, options) {
  57. return Errors.create(type, context, state, options, this._flags);
  58. }
  59. checkOptions(options) {
  60. const Schemas = require('./schemas');
  61. const result = Schemas.options.validate(options);
  62. if (result.error) {
  63. throw new Error(result.error.details[0].message);
  64. }
  65. }
  66. clone() {
  67. const obj = Object.create(Object.getPrototypeOf(this));
  68. obj.isJoi = true;
  69. obj._type = this._type;
  70. obj._settings = internals.concatSettings(this._settings);
  71. obj._valids = Hoek.clone(this._valids);
  72. obj._invalids = Hoek.clone(this._invalids);
  73. obj._tests = this._tests.slice();
  74. obj._refs = this._refs.slice();
  75. obj._flags = Hoek.clone(this._flags);
  76. obj._description = this._description;
  77. obj._unit = this._unit;
  78. obj._notes = this._notes.slice();
  79. obj._tags = this._tags.slice();
  80. obj._examples = this._examples.slice();
  81. obj._meta = this._meta.slice();
  82. obj._inner = {};
  83. const inners = Object.keys(this._inner);
  84. for (let i = 0; i < inners.length; ++i) {
  85. const key = inners[i];
  86. obj._inner[key] = this._inner[key] ? this._inner[key].slice() : null;
  87. }
  88. return obj;
  89. }
  90. concat(schema) {
  91. Hoek.assert(schema instanceof internals.Any, 'Invalid schema object');
  92. Hoek.assert(this._type === 'any' || schema._type === 'any' || schema._type === this._type, 'Cannot merge type', this._type, 'with another type:', schema._type);
  93. let obj = this.clone();
  94. if (this._type === 'any' && schema._type !== 'any') {
  95. // Reset values as if we were "this"
  96. const tmpObj = schema.clone();
  97. const keysToRestore = ['_settings', '_valids', '_invalids', '_tests', '_refs', '_flags', '_description', '_unit',
  98. '_notes', '_tags', '_examples', '_meta', '_inner'];
  99. for (let i = 0; i < keysToRestore.length; ++i) {
  100. tmpObj[keysToRestore[i]] = obj[keysToRestore[i]];
  101. }
  102. obj = tmpObj;
  103. }
  104. obj._settings = obj._settings ? internals.concatSettings(obj._settings, schema._settings) : schema._settings;
  105. obj._valids.merge(schema._valids, schema._invalids);
  106. obj._invalids.merge(schema._invalids, schema._valids);
  107. obj._tests = obj._tests.concat(schema._tests);
  108. obj._refs = obj._refs.concat(schema._refs);
  109. Hoek.merge(obj._flags, schema._flags);
  110. obj._description = schema._description || obj._description;
  111. obj._unit = schema._unit || obj._unit;
  112. obj._notes = obj._notes.concat(schema._notes);
  113. obj._tags = obj._tags.concat(schema._tags);
  114. obj._examples = obj._examples.concat(schema._examples);
  115. obj._meta = obj._meta.concat(schema._meta);
  116. const inners = Object.keys(schema._inner);
  117. const isObject = obj._type === 'object';
  118. for (let i = 0; i < inners.length; ++i) {
  119. const key = inners[i];
  120. const source = schema._inner[key];
  121. if (source) {
  122. const target = obj._inner[key];
  123. if (target) {
  124. if (isObject && key === 'children') {
  125. const keys = {};
  126. for (let j = 0; j < target.length; ++j) {
  127. keys[target[j].key] = j;
  128. }
  129. for (let j = 0; j < source.length; ++j) {
  130. const sourceKey = source[j].key;
  131. if (keys[sourceKey] >= 0) {
  132. target[keys[sourceKey]] = {
  133. key: sourceKey,
  134. schema: target[keys[sourceKey]].schema.concat(source[j].schema)
  135. };
  136. }
  137. else {
  138. target.push(source[j]);
  139. }
  140. }
  141. }
  142. else {
  143. obj._inner[key] = obj._inner[key].concat(source);
  144. }
  145. }
  146. else {
  147. obj._inner[key] = source.slice();
  148. }
  149. }
  150. }
  151. return obj;
  152. }
  153. _test(name, arg, func, options) {
  154. const obj = this.clone();
  155. obj._tests.push({ func, name, arg, options });
  156. return obj;
  157. }
  158. options(options) {
  159. Hoek.assert(!options.context, 'Cannot override context');
  160. this.checkOptions(options);
  161. const obj = this.clone();
  162. obj._settings = internals.concatSettings(obj._settings, options);
  163. return obj;
  164. }
  165. strict(isStrict) {
  166. const obj = this.clone();
  167. obj._settings = obj._settings || {};
  168. obj._settings.convert = isStrict === undefined ? false : !isStrict;
  169. return obj;
  170. }
  171. raw(isRaw) {
  172. const obj = this.clone();
  173. obj._flags.raw = isRaw === undefined ? true : isRaw;
  174. return obj;
  175. }
  176. error(err) {
  177. Hoek.assert(err && err instanceof Error, 'Must provide a valid Error object');
  178. const obj = this.clone();
  179. obj._flags.error = err;
  180. return obj;
  181. }
  182. _allow() {
  183. const values = Hoek.flatten(Array.prototype.slice.call(arguments));
  184. for (let i = 0; i < values.length; ++i) {
  185. const value = values[i];
  186. Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');
  187. this._invalids.remove(value);
  188. this._valids.add(value, this._refs);
  189. }
  190. }
  191. allow() {
  192. const obj = this.clone();
  193. obj._allow.apply(obj, arguments);
  194. return obj;
  195. }
  196. valid() {
  197. const obj = this.allow.apply(this, arguments);
  198. obj._flags.allowOnly = true;
  199. return obj;
  200. }
  201. invalid(value) {
  202. const obj = this.clone();
  203. const values = Hoek.flatten(Array.prototype.slice.call(arguments));
  204. for (let i = 0; i < values.length; ++i) {
  205. value = values[i];
  206. Hoek.assert(value !== undefined, 'Cannot call allow/valid/invalid with undefined');
  207. obj._valids.remove(value);
  208. obj._invalids.add(value, this._refs);
  209. }
  210. return obj;
  211. }
  212. required() {
  213. const obj = this.clone();
  214. obj._flags.presence = 'required';
  215. return obj;
  216. }
  217. optional() {
  218. const obj = this.clone();
  219. obj._flags.presence = 'optional';
  220. return obj;
  221. }
  222. forbidden() {
  223. const obj = this.clone();
  224. obj._flags.presence = 'forbidden';
  225. return obj;
  226. }
  227. strip() {
  228. const obj = this.clone();
  229. obj._flags.strip = true;
  230. return obj;
  231. }
  232. applyFunctionToChildren(children, fn, args, root) {
  233. children = [].concat(children);
  234. if (children.length !== 1 || children[0] !== '') {
  235. root = root ? (root + '.') : '';
  236. const extraChildren = (children[0] === '' ? children.slice(1) : children).map((child) => {
  237. return root + child;
  238. });
  239. throw new Error('unknown key(s) ' + extraChildren.join(', '));
  240. }
  241. return this[fn].apply(this, args);
  242. }
  243. default(value, description) {
  244. if (typeof value === 'function' &&
  245. !Ref.isRef(value)) {
  246. if (!value.description &&
  247. description) {
  248. value.description = description;
  249. }
  250. if (!this._flags.func) {
  251. Hoek.assert(typeof value.description === 'string' && value.description.length > 0, 'description must be provided when default value is a function');
  252. }
  253. }
  254. const obj = this.clone();
  255. obj._flags.default = value;
  256. Ref.push(obj._refs, value);
  257. return obj;
  258. }
  259. empty(schema) {
  260. const obj = this.clone();
  261. obj._flags.empty = schema === undefined ? undefined : Cast.schema(schema);
  262. return obj;
  263. }
  264. when(ref, options) {
  265. Hoek.assert(options && typeof options === 'object', 'Invalid options');
  266. Hoek.assert(options.then !== undefined || options.otherwise !== undefined, 'options must have at least one of "then" or "otherwise"');
  267. const then = options.hasOwnProperty('then') ? this.concat(Cast.schema(options.then)) : undefined;
  268. const otherwise = options.hasOwnProperty('otherwise') ? this.concat(Cast.schema(options.otherwise)) : undefined;
  269. Alternatives = Alternatives || require('./alternatives');
  270. const obj = Alternatives.when(ref, { is: options.is, then, otherwise });
  271. obj._flags.presence = 'ignore';
  272. obj._settings = internals.concatSettings(obj._settings, { baseType: this });
  273. return obj;
  274. }
  275. description(desc) {
  276. Hoek.assert(desc && typeof desc === 'string', 'Description must be a non-empty string');
  277. const obj = this.clone();
  278. obj._description = desc;
  279. return obj;
  280. }
  281. notes(notes) {
  282. Hoek.assert(notes && (typeof notes === 'string' || Array.isArray(notes)), 'Notes must be a non-empty string or array');
  283. const obj = this.clone();
  284. obj._notes = obj._notes.concat(notes);
  285. return obj;
  286. }
  287. tags(tags) {
  288. Hoek.assert(tags && (typeof tags === 'string' || Array.isArray(tags)), 'Tags must be a non-empty string or array');
  289. const obj = this.clone();
  290. obj._tags = obj._tags.concat(tags);
  291. return obj;
  292. }
  293. meta(meta) {
  294. Hoek.assert(meta !== undefined, 'Meta cannot be undefined');
  295. const obj = this.clone();
  296. obj._meta = obj._meta.concat(meta);
  297. return obj;
  298. }
  299. example(value) {
  300. Hoek.assert(arguments.length, 'Missing example');
  301. const result = this._validate(value, null, internals.defaults);
  302. Hoek.assert(!result.errors, 'Bad example:', result.errors && Errors.process(result.errors, value));
  303. const obj = this.clone();
  304. obj._examples.push(value);
  305. return obj;
  306. }
  307. unit(name) {
  308. Hoek.assert(name && typeof name === 'string', 'Unit name must be a non-empty string');
  309. const obj = this.clone();
  310. obj._unit = name;
  311. return obj;
  312. }
  313. _validate(value, state, options, reference) {
  314. const originalValue = value;
  315. // Setup state and settings
  316. state = state || { key: '', path: '', parent: null, reference };
  317. if (this._settings) {
  318. options = internals.concatSettings(options, this._settings);
  319. }
  320. let errors = [];
  321. const finish = () => {
  322. let finalValue;
  323. if (!this._flags.strip) {
  324. if (value !== undefined) {
  325. finalValue = this._flags.raw ? originalValue : value;
  326. }
  327. else if (options.noDefaults) {
  328. finalValue = originalValue;
  329. }
  330. else if (Ref.isRef(this._flags.default)) {
  331. finalValue = this._flags.default(state.parent, options);
  332. }
  333. else if (typeof this._flags.default === 'function' &&
  334. !(this._flags.func && !this._flags.default.description)) {
  335. let args;
  336. if (state.parent !== null &&
  337. this._flags.default.length > 0) {
  338. args = [Hoek.clone(state.parent), options];
  339. }
  340. const defaultValue = internals._try(this._flags.default, args);
  341. finalValue = defaultValue.value;
  342. if (defaultValue.error) {
  343. errors.push(this.createError('any.default', defaultValue.error, state, options));
  344. }
  345. }
  346. else {
  347. finalValue = Hoek.clone(this._flags.default);
  348. }
  349. }
  350. return {
  351. value: finalValue,
  352. errors: errors.length ? errors : null
  353. };
  354. };
  355. if (this._coerce) {
  356. const coerced = this._coerce.call(this, value, state, options);
  357. if (coerced.errors) {
  358. value = coerced.value;
  359. errors = errors.concat(coerced.errors);
  360. return finish(); // Coerced error always aborts early
  361. }
  362. value = coerced.value;
  363. }
  364. if (this._flags.empty && !this._flags.empty._validate(value, null, internals.defaults).errors) {
  365. value = undefined;
  366. }
  367. // Check presence requirements
  368. const presence = this._flags.presence || options.presence;
  369. if (presence === 'optional') {
  370. if (value === undefined) {
  371. const isDeepDefault = this._flags.hasOwnProperty('default') && this._flags.default === undefined;
  372. if (isDeepDefault && this._type === 'object') {
  373. value = {};
  374. }
  375. else {
  376. return finish();
  377. }
  378. }
  379. }
  380. else if (presence === 'required' &&
  381. value === undefined) {
  382. errors.push(this.createError('any.required', null, state, options));
  383. return finish();
  384. }
  385. else if (presence === 'forbidden') {
  386. if (value === undefined) {
  387. return finish();
  388. }
  389. errors.push(this.createError('any.unknown', null, state, options));
  390. return finish();
  391. }
  392. // Check allowed and denied values using the original value
  393. if (this._valids.has(value, state, options, this._flags.insensitive)) {
  394. return finish();
  395. }
  396. if (this._invalids.has(value, state, options, this._flags.insensitive)) {
  397. errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', null, state, options));
  398. if (options.abortEarly ||
  399. value === undefined) { // No reason to keep validating missing value
  400. return finish();
  401. }
  402. }
  403. // Convert value and validate type
  404. if (this._base) {
  405. const base = this._base.call(this, value, state, options);
  406. if (base.errors) {
  407. value = base.value;
  408. errors = errors.concat(base.errors);
  409. return finish(); // Base error always aborts early
  410. }
  411. if (base.value !== value) {
  412. value = base.value;
  413. // Check allowed and denied values using the converted value
  414. if (this._valids.has(value, state, options, this._flags.insensitive)) {
  415. return finish();
  416. }
  417. if (this._invalids.has(value, state, options, this._flags.insensitive)) {
  418. errors.push(this.createError(value === '' ? 'any.empty' : 'any.invalid', null, state, options));
  419. if (options.abortEarly) {
  420. return finish();
  421. }
  422. }
  423. }
  424. }
  425. // Required values did not match
  426. if (this._flags.allowOnly) {
  427. errors.push(this.createError('any.allowOnly', { valids: this._valids.values({ stripUndefined: true }) }, state, options));
  428. if (options.abortEarly) {
  429. return finish();
  430. }
  431. }
  432. // Helper.validate tests
  433. for (let i = 0; i < this._tests.length; ++i) {
  434. const test = this._tests[i];
  435. const ret = test.func.call(this, value, state, options);
  436. if (ret instanceof Errors.Err) {
  437. errors.push(ret);
  438. if (options.abortEarly) {
  439. return finish();
  440. }
  441. }
  442. else {
  443. value = ret;
  444. }
  445. }
  446. return finish();
  447. }
  448. _validateWithOptions(value, options, callback) {
  449. if (options) {
  450. this.checkOptions(options);
  451. }
  452. const settings = internals.concatSettings(internals.defaults, options);
  453. const result = this._validate(value, null, settings);
  454. const errors = Errors.process(result.errors, value);
  455. if (callback) {
  456. return callback(errors, result.value);
  457. }
  458. return { error: errors, value: result.value };
  459. }
  460. validate(value, options, callback) {
  461. if (typeof options === 'function') {
  462. return this._validateWithOptions(value, null, options);
  463. }
  464. return this._validateWithOptions(value, options, callback);
  465. }
  466. describe() {
  467. const description = {
  468. type: this._type
  469. };
  470. const flags = Object.keys(this._flags);
  471. if (flags.length) {
  472. if (['empty', 'default', 'lazy', 'label'].some((flag) => this._flags.hasOwnProperty(flag))) {
  473. description.flags = {};
  474. for (let i = 0; i < flags.length; ++i) {
  475. const flag = flags[i];
  476. if (flag === 'empty') {
  477. description.flags[flag] = this._flags[flag].describe();
  478. }
  479. else if (flag === 'default') {
  480. if (Ref.isRef(this._flags[flag])) {
  481. description.flags[flag] = this._flags[flag].toString();
  482. }
  483. else if (typeof this._flags[flag] === 'function') {
  484. description.flags[flag] = this._flags[flag].description;
  485. }
  486. else {
  487. description.flags[flag] = this._flags[flag];
  488. }
  489. }
  490. else if (flag === 'lazy' || flag === 'label') {
  491. // We don't want it in the description
  492. }
  493. else {
  494. description.flags[flag] = this._flags[flag];
  495. }
  496. }
  497. }
  498. else {
  499. description.flags = this._flags;
  500. }
  501. }
  502. if (this._description) {
  503. description.description = this._description;
  504. }
  505. if (this._notes.length) {
  506. description.notes = this._notes;
  507. }
  508. if (this._tags.length) {
  509. description.tags = this._tags;
  510. }
  511. if (this._meta.length) {
  512. description.meta = this._meta;
  513. }
  514. if (this._examples.length) {
  515. description.examples = this._examples;
  516. }
  517. if (this._unit) {
  518. description.unit = this._unit;
  519. }
  520. const valids = this._valids.values();
  521. if (valids.length) {
  522. description.valids = valids.map((v) => {
  523. return Ref.isRef(v) ? v.toString() : v;
  524. });
  525. }
  526. const invalids = this._invalids.values();
  527. if (invalids.length) {
  528. description.invalids = invalids.map((v) => {
  529. return Ref.isRef(v) ? v.toString() : v;
  530. });
  531. }
  532. description.rules = [];
  533. for (let i = 0; i < this._tests.length; ++i) {
  534. const validator = this._tests[i];
  535. const item = { name: validator.name };
  536. if (validator.arg !== void 0) {
  537. item.arg = Ref.isRef(validator.arg) ? validator.arg.toString() : validator.arg;
  538. }
  539. const options = validator.options;
  540. if (options) {
  541. if (options.hasRef) {
  542. item.arg = {};
  543. const keys = Object.keys(validator.arg);
  544. for (let j = 0; j < keys.length; ++j) {
  545. const key = keys[j];
  546. const value = validator.arg[key];
  547. item.arg[key] = Ref.isRef(value) ? value.toString() : value;
  548. }
  549. }
  550. if (typeof options.description === 'string') {
  551. item.description = options.description;
  552. }
  553. else if (typeof options.description === 'function') {
  554. item.description = options.description(item.arg);
  555. }
  556. }
  557. description.rules.push(item);
  558. }
  559. if (!description.rules.length) {
  560. delete description.rules;
  561. }
  562. const label = this._getLabel();
  563. if (label) {
  564. description.label = label;
  565. }
  566. return description;
  567. }
  568. label(name) {
  569. Hoek.assert(name && typeof name === 'string', 'Label name must be a non-empty string');
  570. const obj = this.clone();
  571. obj._flags.label = name;
  572. return obj;
  573. }
  574. _getLabel(def) {
  575. return this._flags.label || def;
  576. }
  577. };
  578. internals.Any.prototype.isImmutable = true; // Prevents Hoek from deep cloning schema objects
  579. // Aliases
  580. internals.Any.prototype.only = internals.Any.prototype.equal = internals.Any.prototype.valid;
  581. internals.Any.prototype.disallow = internals.Any.prototype.not = internals.Any.prototype.invalid;
  582. internals.Any.prototype.exist = internals.Any.prototype.required;
  583. internals._try = function (fn, args) {
  584. let err;
  585. let result;
  586. try {
  587. result = fn.apply(null, args);
  588. }
  589. catch (e) {
  590. err = e;
  591. }
  592. return {
  593. value: result,
  594. error: err
  595. };
  596. };
  597. internals.Set = class {
  598. constructor() {
  599. this._set = [];
  600. }
  601. add(value, refs) {
  602. if (!Ref.isRef(value) && this.has(value, null, null, false)) {
  603. return;
  604. }
  605. if (refs !== undefined) { // If it's a merge, we don't have any refs
  606. Ref.push(refs, value);
  607. }
  608. this._set.push(value);
  609. }
  610. merge(add, remove) {
  611. for (let i = 0; i < add._set.length; ++i) {
  612. this.add(add._set[i]);
  613. }
  614. for (let i = 0; i < remove._set.length; ++i) {
  615. this.remove(remove._set[i]);
  616. }
  617. }
  618. remove(value) {
  619. this._set = this._set.filter((item) => value !== item);
  620. }
  621. has(value, state, options, insensitive) {
  622. for (let i = 0; i < this._set.length; ++i) {
  623. let items = this._set[i];
  624. if (state && Ref.isRef(items)) { // Only resolve references if there is a state, otherwise it's a merge
  625. items = items(state.reference || state.parent, options);
  626. }
  627. if (!Array.isArray(items)) {
  628. items = [items];
  629. }
  630. for (let j = 0; j < items.length; ++j) {
  631. const item = items[j];
  632. if (typeof value !== typeof item) {
  633. continue;
  634. }
  635. if (value === item ||
  636. (value instanceof Date && item instanceof Date && value.getTime() === item.getTime()) ||
  637. (insensitive && typeof value === 'string' && value.toLowerCase() === item.toLowerCase()) ||
  638. (Buffer.isBuffer(value) && Buffer.isBuffer(item) && value.length === item.length && value.toString('binary') === item.toString('binary'))) {
  639. return true;
  640. }
  641. }
  642. }
  643. return false;
  644. }
  645. values(options) {
  646. if (options && options.stripUndefined) {
  647. const values = [];
  648. for (let i = 0; i < this._set.length; ++i) {
  649. const item = this._set[i];
  650. if (item !== undefined) {
  651. values.push(item);
  652. }
  653. }
  654. return values;
  655. }
  656. return this._set.slice();
  657. }
  658. };
  659. internals.concatSettings = function (target, source) {
  660. // Used to avoid cloning context
  661. if (!target &&
  662. !source) {
  663. return null;
  664. }
  665. const obj = {};
  666. if (target) {
  667. Object.assign(obj, target);
  668. }
  669. if (source) {
  670. const sKeys = Object.keys(source);
  671. for (let i = 0; i < sKeys.length; ++i) {
  672. const key = sKeys[i];
  673. if (key !== 'language' ||
  674. !obj.hasOwnProperty(key)) {
  675. obj[key] = source[key];
  676. }
  677. else {
  678. obj[key] = Hoek.applyToDefaults(obj[key], source[key]);
  679. }
  680. }
  681. }
  682. return obj;
  683. };