'use strict'; const Assert = require('@hapi/hoek/lib/assert'); const DeepEqual = require('@hapi/hoek/lib/deepEqual'); const Common = require('./common'); const internals = {}; module.exports = internals.Values = class { constructor(values, refs) { this._values = new Set(values); this._refs = new Set(refs); this._lowercase = internals.lowercases(values); this._override = false; } get length() { return this._values.size + this._refs.size; } add(value, refs) { // Reference if (Common.isResolvable(value)) { if (!this._refs.has(value)) { this._refs.add(value); if (refs) { // Skipped in a merge refs.register(value); } } return; } // Value if (!this.has(value, null, null, false)) { this._values.add(value); if (typeof value === 'string') { this._lowercase.set(value.toLowerCase(), value); } } } static merge(target, source, remove) { target = target || new internals.Values(); if (source) { if (source._override) { return source.clone(); } for (const item of [...source._values, ...source._refs]) { target.add(item); } } if (remove) { for (const item of [...remove._values, ...remove._refs]) { target.remove(item); } } return target.length ? target : null; } remove(value) { // Reference if (Common.isResolvable(value)) { this._refs.delete(value); return; } // Value this._values.delete(value); if (typeof value === 'string') { this._lowercase.delete(value.toLowerCase()); } } has(value, state, prefs, insensitive) { return !!this.get(value, state, prefs, insensitive); } get(value, state, prefs, insensitive) { if (!this.length) { return false; } // Simple match if (this._values.has(value)) { return { value }; } // Case insensitive string match if (typeof value === 'string' && value && insensitive) { const found = this._lowercase.get(value.toLowerCase()); if (found) { return { value: found }; } } if (!this._refs.size && typeof value !== 'object') { return false; } // Objects if (typeof value === 'object') { for (const item of this._values) { if (DeepEqual(item, value)) { return { value: item }; } } } // References if (state) { for (const ref of this._refs) { const resolved = ref.resolve(value, state, prefs, null, { in: true }); if (resolved === undefined) { continue; } const items = !ref.in || typeof resolved !== 'object' ? [resolved] : Array.isArray(resolved) ? resolved : Object.keys(resolved); for (const item of items) { if (typeof item !== typeof value) { continue; } if (insensitive && value && typeof value === 'string') { if (item.toLowerCase() === value.toLowerCase()) { return { value: item, ref }; } } else { if (DeepEqual(item, value)) { return { value: item, ref }; } } } } } return false; } override() { this._override = true; } values(options) { if (options && options.display) { const values = []; for (const item of [...this._values, ...this._refs]) { if (item !== undefined) { values.push(item); } } return values; } return Array.from([...this._values, ...this._refs]); } clone() { const set = new internals.Values(this._values, this._refs); set._override = this._override; return set; } concat(source) { Assert(!source._override, 'Cannot concat override set of values'); const set = new internals.Values([...this._values, ...source._values], [...this._refs, ...source._refs]); set._override = this._override; return set; } describe() { const normalized = []; if (this._override) { normalized.push({ override: true }); } for (const value of this._values.values()) { normalized.push(value && typeof value === 'object' ? { value } : value); } for (const value of this._refs.values()) { normalized.push(value.describe()); } return normalized; } }; internals.Values.prototype[Common.symbols.values] = true; // Aliases internals.Values.prototype.slice = internals.Values.prototype.clone; // Helpers internals.lowercases = function (from) { const map = new Map(); if (from) { for (const value of from) { if (typeof value === 'string') { map.set(value.toLowerCase(), value); } } } return map; };