UNPKG

6.85 kBJavaScriptView Raw
1'use strict';
2
3const Assert = require('@hapi/hoek/lib/assert');
4
5const Common = require('./common');
6const Ref = require('./ref');
7
8
9const internals = {};
10
11
12
13exports.Ids = internals.Ids = class {
14
15 constructor() {
16
17 this._byId = new Map();
18 this._byKey = new Map();
19 this._schemaChain = false;
20 }
21
22 clone() {
23
24 const clone = new internals.Ids();
25 clone._byId = new Map(this._byId);
26 clone._byKey = new Map(this._byKey);
27 clone._schemaChain = this._schemaChain;
28 return clone;
29 }
30
31 concat(source) {
32
33 if (source._schemaChain) {
34 this._schemaChain = true;
35 }
36
37 for (const [id, value] of source._byId.entries()) {
38 Assert(!this._byKey.has(id), 'Schema id conflicts with existing key:', id);
39 this._byId.set(id, value);
40 }
41
42 for (const [key, value] of source._byKey.entries()) {
43 Assert(!this._byId.has(key), 'Schema key conflicts with existing id:', key);
44 this._byKey.set(key, value);
45 }
46 }
47
48 fork(path, adjuster, root) {
49
50 const chain = this._collect(path);
51 chain.push({ schema: root });
52 const tail = chain.shift();
53 let adjusted = { id: tail.id, schema: adjuster(tail.schema) };
54
55 Assert(Common.isSchema(adjusted.schema), 'adjuster function failed to return a joi schema type');
56
57 for (const node of chain) {
58 adjusted = { id: node.id, schema: internals.fork(node.schema, adjusted.id, adjusted.schema) };
59 }
60
61 return adjusted.schema;
62 }
63
64 labels(path, behind = []) {
65
66 const current = path[0];
67 const node = this._get(current);
68 if (!node) {
69 return [...behind, ...path].join('.');
70 }
71
72 const forward = path.slice(1);
73 behind = [...behind, node.schema._flags.label || current];
74 if (!forward.length) {
75 return behind.join('.');
76 }
77
78 return node.schema._ids.labels(forward, behind);
79 }
80
81 reach(path, behind = []) {
82
83 const current = path[0];
84 const node = this._get(current);
85 Assert(node, 'Schema does not contain path', [...behind, ...path].join('.'));
86
87 const forward = path.slice(1);
88 if (!forward.length) {
89 return node.schema;
90 }
91
92 return node.schema._ids.reach(forward, [...behind, current]);
93 }
94
95 register(schema, { key } = {}) {
96
97 if (!schema ||
98 !Common.isSchema(schema)) {
99
100 return;
101 }
102
103 if (schema.$_property('schemaChain') ||
104 schema._ids._schemaChain) {
105
106 this._schemaChain = true;
107 }
108
109 const id = schema._flags.id;
110 if (id) {
111 const existing = this._byId.get(id);
112 Assert(!existing || existing.schema === schema, 'Cannot add different schemas with the same id:', id);
113 Assert(!this._byKey.has(id), 'Schema id conflicts with existing key:', id);
114
115 this._byId.set(id, { schema, id });
116 }
117
118 if (key) {
119 Assert(!this._byKey.has(key), 'Schema already contains key:', key);
120 Assert(!this._byId.has(key), 'Schema key conflicts with existing id:', key);
121
122 this._byKey.set(key, { schema, id: key });
123 }
124 }
125
126 reset() {
127
128 this._byId = new Map();
129 this._byKey = new Map();
130 this._schemaChain = false;
131 }
132
133 _collect(path, behind = [], nodes = []) {
134
135 const current = path[0];
136 const node = this._get(current);
137 Assert(node, 'Schema does not contain path', [...behind, ...path].join('.'));
138
139 nodes = [node, ...nodes];
140
141 const forward = path.slice(1);
142 if (!forward.length) {
143 return nodes;
144 }
145
146 return node.schema._ids._collect(forward, [...behind, current], nodes);
147 }
148
149 _get(id) {
150
151 return this._byId.get(id) || this._byKey.get(id);
152 }
153};
154
155
156internals.fork = function (schema, id, replacement) {
157
158 const each = (item, { key }) => {
159
160 if (id === (item._flags.id || key)) {
161 return replacement;
162 }
163 };
164
165 const obj = exports.schema(schema, { each, ref: false });
166 return obj ? obj.$_mutateRebuild() : schema;
167};
168
169
170exports.schema = function (schema, options) {
171
172 let obj;
173
174 for (const name in schema._flags) {
175 if (name[0] === '_') {
176 continue;
177 }
178
179 const result = internals.scan(schema._flags[name], { source: 'flags', name }, options);
180 if (result !== undefined) {
181 obj = obj || schema.clone();
182 obj._flags[name] = result;
183 }
184 }
185
186 for (let i = 0; i < schema._rules.length; ++i) {
187 const rule = schema._rules[i];
188 const result = internals.scan(rule.args, { source: 'rules', name: rule.name }, options);
189 if (result !== undefined) {
190 obj = obj || schema.clone();
191 const clone = Object.assign({}, rule);
192 clone.args = result;
193 obj._rules[i] = clone;
194
195 const existingUnique = obj._singleRules.get(rule.name);
196 if (existingUnique === rule) {
197 obj._singleRules.set(rule.name, clone);
198 }
199 }
200 }
201
202 for (const name in schema.$_terms) {
203 if (name[0] === '_') {
204 continue;
205 }
206
207 const result = internals.scan(schema.$_terms[name], { source: 'terms', name }, options);
208 if (result !== undefined) {
209 obj = obj || schema.clone();
210 obj.$_terms[name] = result;
211 }
212 }
213
214 return obj;
215};
216
217
218internals.scan = function (item, source, options, _path, _key) {
219
220 const path = _path || [];
221
222 if (item === null ||
223 typeof item !== 'object') {
224
225 return;
226 }
227
228 let clone;
229
230 if (Array.isArray(item)) {
231 for (let i = 0; i < item.length; ++i) {
232 const key = source.source === 'terms' && source.name === 'keys' && item[i].key;
233 const result = internals.scan(item[i], source, options, [i, ...path], key);
234 if (result !== undefined) {
235 clone = clone || item.slice();
236 clone[i] = result;
237 }
238 }
239
240 return clone;
241 }
242
243 if (options.schema !== false && Common.isSchema(item) ||
244 options.ref !== false && Ref.isRef(item)) {
245
246 const result = options.each(item, { ...source, path, key: _key });
247 if (result === item) {
248 return;
249 }
250
251 return result;
252 }
253
254 for (const key in item) {
255 if (key[0] === '_') {
256 continue;
257 }
258
259 const result = internals.scan(item[key], source, options, [key, ...path], _key);
260 if (result !== undefined) {
261 clone = clone || Object.assign({}, item);
262 clone[key] = result;
263 }
264 }
265
266 return clone;
267};