123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- 'use strict';
- const Assert = require('@hapi/hoek/lib/assert');
- const Common = require('./common');
- const Ref = require('./ref');
- const internals = {};
- exports.Ids = internals.Ids = class {
- constructor() {
- this._byId = new Map();
- this._byKey = new Map();
- this._schemaChain = false;
- }
- clone() {
- const clone = new internals.Ids();
- clone._byId = new Map(this._byId);
- clone._byKey = new Map(this._byKey);
- clone._schemaChain = this._schemaChain;
- return clone;
- }
- concat(source) {
- if (source._schemaChain) {
- this._schemaChain = true;
- }
- for (const [id, value] of source._byId.entries()) {
- Assert(!this._byKey.has(id), 'Schema id conflicts with existing key:', id);
- this._byId.set(id, value);
- }
- for (const [key, value] of source._byKey.entries()) {
- Assert(!this._byId.has(key), 'Schema key conflicts with existing id:', key);
- this._byKey.set(key, value);
- }
- }
- fork(path, adjuster, root) {
- const chain = this._collect(path);
- chain.push({ schema: root });
- const tail = chain.shift();
- let adjusted = { id: tail.id, schema: adjuster(tail.schema) };
- Assert(Common.isSchema(adjusted.schema), 'adjuster function failed to return a joi schema type');
- for (const node of chain) {
- adjusted = { id: node.id, schema: internals.fork(node.schema, adjusted.id, adjusted.schema) };
- }
- return adjusted.schema;
- }
- labels(path, behind = []) {
- const current = path[0];
- const node = this._get(current);
- if (!node) {
- return [...behind, ...path].join('.');
- }
- const forward = path.slice(1);
- behind = [...behind, node.schema._flags.label || current];
- if (!forward.length) {
- return behind.join('.');
- }
- return node.schema._ids.labels(forward, behind);
- }
- reach(path, behind = []) {
- const current = path[0];
- const node = this._get(current);
- Assert(node, 'Schema does not contain path', [...behind, ...path].join('.'));
- const forward = path.slice(1);
- if (!forward.length) {
- return node.schema;
- }
- return node.schema._ids.reach(forward, [...behind, current]);
- }
- register(schema, { key } = {}) {
- if (!schema ||
- !Common.isSchema(schema)) {
- return;
- }
- if (schema.$_property('schemaChain') ||
- schema._ids._schemaChain) {
- this._schemaChain = true;
- }
- const id = schema._flags.id;
- if (id) {
- const existing = this._byId.get(id);
- Assert(!existing || existing.schema === schema, 'Cannot add different schemas with the same id:', id);
- Assert(!this._byKey.has(id), 'Schema id conflicts with existing key:', id);
- this._byId.set(id, { schema, id });
- }
- if (key) {
- Assert(!this._byKey.has(key), 'Schema already contains key:', key);
- Assert(!this._byId.has(key), 'Schema key conflicts with existing id:', key);
- this._byKey.set(key, { schema, id: key });
- }
- }
- reset() {
- this._byId = new Map();
- this._byKey = new Map();
- this._schemaChain = false;
- }
- _collect(path, behind = [], nodes = []) {
- const current = path[0];
- const node = this._get(current);
- Assert(node, 'Schema does not contain path', [...behind, ...path].join('.'));
- nodes = [node, ...nodes];
- const forward = path.slice(1);
- if (!forward.length) {
- return nodes;
- }
- return node.schema._ids._collect(forward, [...behind, current], nodes);
- }
- _get(id) {
- return this._byId.get(id) || this._byKey.get(id);
- }
- };
- internals.fork = function (schema, id, replacement) {
- const each = (item, { key }) => {
- if (id === (item._flags.id || key)) {
- return replacement;
- }
- };
- const obj = exports.schema(schema, { each, ref: false });
- return obj ? obj.$_mutateRebuild() : schema;
- };
- exports.schema = function (schema, options) {
- let obj;
- for (const name in schema._flags) {
- if (name[0] === '_') {
- continue;
- }
- const result = internals.scan(schema._flags[name], { source: 'flags', name }, options);
- if (result !== undefined) {
- obj = obj || schema.clone();
- obj._flags[name] = result;
- }
- }
- for (let i = 0; i < schema._rules.length; ++i) {
- const rule = schema._rules[i];
- const result = internals.scan(rule.args, { source: 'rules', name: rule.name }, options);
- if (result !== undefined) {
- obj = obj || schema.clone();
- const clone = Object.assign({}, rule);
- clone.args = result;
- obj._rules[i] = clone;
- const existingUnique = obj._singleRules.get(rule.name);
- if (existingUnique === rule) {
- obj._singleRules.set(rule.name, clone);
- }
- }
- }
- for (const name in schema.$_terms) {
- if (name[0] === '_') {
- continue;
- }
- const result = internals.scan(schema.$_terms[name], { source: 'terms', name }, options);
- if (result !== undefined) {
- obj = obj || schema.clone();
- obj.$_terms[name] = result;
- }
- }
- return obj;
- };
- internals.scan = function (item, source, options, _path, _key) {
- const path = _path || [];
- if (item === null ||
- typeof item !== 'object') {
- return;
- }
- let clone;
- if (Array.isArray(item)) {
- for (let i = 0; i < item.length; ++i) {
- const key = source.source === 'terms' && source.name === 'keys' && item[i].key;
- const result = internals.scan(item[i], source, options, [i, ...path], key);
- if (result !== undefined) {
- clone = clone || item.slice();
- clone[i] = result;
- }
- }
- return clone;
- }
- if (options.schema !== false && Common.isSchema(item) ||
- options.ref !== false && Ref.isRef(item)) {
- const result = options.each(item, { ...source, path, key: _key });
- if (result === item) {
- return;
- }
- return result;
- }
- for (const key in item) {
- if (key[0] === '_') {
- continue;
- }
- const result = internals.scan(item[key], source, options, [key, ...path], _key);
- if (result !== undefined) {
- clone = clone || Object.assign({}, item);
- clone[key] = result;
- }
- }
- return clone;
- };
|