source: node_modules/swagger-client/es/specmap/index.js@ d24f17c

main
Last change on this file since d24f17c was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 11.0 KB
Line 
1import lib from './lib/index.js';
2import refs from './lib/refs.js';
3import allOf from './lib/all-of.js';
4import parameters from './lib/parameters.js';
5import properties from './lib/properties.js';
6import ContextTree from './lib/context-tree.js';
7const HARD_LIMIT = 100;
8const noop = () => {};
9class SpecMap {
10 static getPluginName(plugin) {
11 return plugin.pluginName;
12 }
13 static getPatchesOfType(patches, fn) {
14 return patches.filter(fn);
15 }
16 constructor(opts) {
17 Object.assign(this, {
18 spec: '',
19 debugLevel: 'info',
20 plugins: [],
21 pluginHistory: {},
22 errors: [],
23 mutations: [],
24 promisedPatches: [],
25 state: {},
26 patches: [],
27 context: {},
28 contextTree: new ContextTree(),
29 showDebug: false,
30 allPatches: [],
31 // only populated if showDebug is true
32 pluginProp: 'specMap',
33 libMethods: Object.assign(Object.create(this), lib, {
34 getInstance: () => this
35 }),
36 allowMetaPatches: false
37 }, opts);
38
39 // Lib methods bound
40 this.get = this._get.bind(this); // eslint-disable-line no-underscore-dangle
41 this.getContext = this._getContext.bind(this); // eslint-disable-line no-underscore-dangle
42 this.hasRun = this._hasRun.bind(this); // eslint-disable-line no-underscore-dangle
43
44 this.wrappedPlugins = this.plugins.map(this.wrapPlugin.bind(this)).filter(lib.isFunction);
45
46 // Initial patch(s)
47 this.patches.push(lib.add([], this.spec));
48 this.patches.push(lib.context([], this.context));
49 this.updatePatches(this.patches);
50 }
51 debug(level, ...args) {
52 if (this.debugLevel === level) {
53 console.log(...args); // eslint-disable-line no-console
54 }
55 }
56 verbose(header, ...args) {
57 if (this.debugLevel === 'verbose') {
58 console.log(`[${header}] `, ...args); // eslint-disable-line no-console
59 }
60 }
61 wrapPlugin(plugin, name) {
62 const {
63 pathDiscriminator
64 } = this;
65 let ctx = null;
66 let fn;
67 if (plugin[this.pluginProp]) {
68 ctx = plugin;
69 fn = plugin[this.pluginProp];
70 } else if (lib.isFunction(plugin)) {
71 fn = plugin;
72 } else if (lib.isObject(plugin)) {
73 fn = createKeyBasedPlugin(plugin);
74 }
75 return Object.assign(fn.bind(ctx), {
76 pluginName: plugin.name || name,
77 isGenerator: lib.isGenerator(fn)
78 });
79
80 // Expected plugin interface: {key: string, plugin: fn*}
81 // This traverses depth-first and immediately applies yielded patches.
82 // This strategy should work well for most plugins (including the built-ins).
83 // We might consider making this (traversing & application) configurable later.
84 function createKeyBasedPlugin(pluginObj) {
85 const isSubPath = (path, tested) => {
86 if (!Array.isArray(path)) {
87 return true;
88 }
89 return path.every((val, i) => val === tested[i]);
90 };
91 return function* generator(patches, specmap) {
92 const refCache = {};
93
94 // eslint-disable-next-line no-restricted-syntax
95 for (const patch of patches.filter(lib.isAdditiveMutation)) {
96 yield* traverse(patch.value, patch.path, patch);
97 }
98 function* traverse(obj, path, patch) {
99 if (!lib.isObject(obj)) {
100 if (pluginObj.key === path[path.length - 1]) {
101 yield pluginObj.plugin(obj, pluginObj.key, path, specmap);
102 }
103 } else {
104 const parentIndex = path.length - 1;
105 const parent = path[parentIndex];
106 const indexOfFirstProperties = path.indexOf('properties');
107 const isRootProperties = parent === 'properties' && parentIndex === indexOfFirstProperties;
108 const traversed = specmap.allowMetaPatches && refCache[obj.$$ref];
109
110 // eslint-disable-next-line no-restricted-syntax
111 for (const key of Object.keys(obj)) {
112 const val = obj[key];
113 const updatedPath = path.concat(key);
114 const isObj = lib.isObject(val);
115 const objRef = obj.$$ref;
116 if (!traversed) {
117 if (isObj) {
118 // Only store the ref if it exists
119 if (specmap.allowMetaPatches && objRef) {
120 refCache[objRef] = true;
121 }
122 yield* traverse(val, updatedPath, patch);
123 }
124 }
125 if (!isRootProperties && key === pluginObj.key) {
126 const isWithinPathDiscriminator = isSubPath(pathDiscriminator, path);
127 if (!pathDiscriminator || isWithinPathDiscriminator) {
128 yield pluginObj.plugin(val, key, updatedPath, specmap, patch);
129 }
130 }
131 }
132 }
133 }
134 };
135 }
136 }
137 nextPlugin() {
138 return this.wrappedPlugins.find(plugin => {
139 const mutations = this.getMutationsForPlugin(plugin);
140 return mutations.length > 0;
141 });
142 }
143 nextPromisedPatch() {
144 if (this.promisedPatches.length > 0) {
145 return Promise.race(this.promisedPatches.map(patch => patch.value));
146 }
147 return undefined;
148 }
149 getPluginHistory(plugin) {
150 const name = this.constructor.getPluginName(plugin);
151 return this.pluginHistory[name] || [];
152 }
153 getPluginRunCount(plugin) {
154 return this.getPluginHistory(plugin).length;
155 }
156 getPluginHistoryTip(plugin) {
157 const history = this.getPluginHistory(plugin);
158 const val = history && history[history.length - 1];
159 return val || {};
160 }
161 getPluginMutationIndex(plugin) {
162 const mi = this.getPluginHistoryTip(plugin).mutationIndex;
163 return typeof mi !== 'number' ? -1 : mi;
164 }
165 updatePluginHistory(plugin, val) {
166 const name = this.constructor.getPluginName(plugin);
167 this.pluginHistory[name] = this.pluginHistory[name] || [];
168 this.pluginHistory[name].push(val);
169 }
170 updatePatches(patches) {
171 lib.normalizeArray(patches).forEach(patch => {
172 if (patch instanceof Error) {
173 this.errors.push(patch);
174 return;
175 }
176 try {
177 if (!lib.isObject(patch)) {
178 this.debug('updatePatches', 'Got a non-object patch', patch);
179 return;
180 }
181 if (this.showDebug) {
182 this.allPatches.push(patch);
183 }
184 if (lib.isPromise(patch.value)) {
185 this.promisedPatches.push(patch);
186 this.promisedPatchThen(patch);
187 return;
188 }
189 if (lib.isContextPatch(patch)) {
190 this.setContext(patch.path, patch.value);
191 return;
192 }
193 if (lib.isMutation(patch)) {
194 this.updateMutations(patch);
195 }
196 } catch (e) {
197 console.error(e); // eslint-disable-line no-console
198 this.errors.push(e);
199 }
200 });
201 }
202 updateMutations(patch) {
203 if (typeof patch.value === 'object' && !Array.isArray(patch.value) && this.allowMetaPatches) {
204 patch.value = {
205 ...patch.value
206 };
207 }
208 const result = lib.applyPatch(this.state, patch, {
209 allowMetaPatches: this.allowMetaPatches
210 });
211 if (result) {
212 this.mutations.push(patch);
213 this.state = result;
214 }
215 }
216 removePromisedPatch(patch) {
217 const index = this.promisedPatches.indexOf(patch);
218 if (index < 0) {
219 this.debug("Tried to remove a promisedPatch that isn't there!");
220 return;
221 }
222 this.promisedPatches.splice(index, 1);
223 }
224 promisedPatchThen(patch) {
225 patch.value = patch.value.then(val => {
226 const promisedPatch = {
227 ...patch,
228 value: val
229 };
230 this.removePromisedPatch(patch);
231 this.updatePatches(promisedPatch);
232 }).catch(e => {
233 this.removePromisedPatch(patch);
234 this.updatePatches(e);
235 });
236 return patch.value;
237 }
238 getMutations(from, to) {
239 from = from || 0;
240 if (typeof to !== 'number') {
241 to = this.mutations.length;
242 }
243 return this.mutations.slice(from, to);
244 }
245 getCurrentMutations() {
246 return this.getMutationsForPlugin(this.getCurrentPlugin());
247 }
248 getMutationsForPlugin(plugin) {
249 const tip = this.getPluginMutationIndex(plugin);
250 return this.getMutations(tip + 1);
251 }
252 getCurrentPlugin() {
253 return this.currentPlugin;
254 }
255 getLib() {
256 return this.libMethods;
257 }
258
259 // eslint-disable-next-line no-underscore-dangle
260 _get(path) {
261 return lib.getIn(this.state, path);
262 }
263
264 // eslint-disable-next-line no-underscore-dangle
265 _getContext(path) {
266 return this.contextTree.get(path);
267 }
268 setContext(path, value) {
269 return this.contextTree.set(path, value);
270 }
271
272 // eslint-disable-next-line no-underscore-dangle
273 _hasRun(count) {
274 const times = this.getPluginRunCount(this.getCurrentPlugin());
275 return times > (count || 0);
276 }
277 dispatch() {
278 const that = this;
279 const plugin = this.nextPlugin();
280 if (!plugin) {
281 const nextPromise = this.nextPromisedPatch();
282 if (nextPromise) {
283 return nextPromise.then(() => this.dispatch()).catch(() => this.dispatch());
284 }
285
286 // We're done!
287 const result = {
288 spec: this.state,
289 errors: this.errors
290 };
291 if (this.showDebug) {
292 result.patches = this.allPatches;
293 }
294 return Promise.resolve(result);
295 }
296
297 // Makes sure plugin isn't running an endless loop
298 that.pluginCount = that.pluginCount || {};
299 that.pluginCount[plugin] = (that.pluginCount[plugin] || 0) + 1;
300 if (that.pluginCount[plugin] > HARD_LIMIT) {
301 return Promise.resolve({
302 spec: that.state,
303 errors: that.errors.concat(new Error(`We've reached a hard limit of ${HARD_LIMIT} plugin runs`))
304 });
305 }
306
307 // A different plugin runs, wait for all promises to resolve, then retry
308 if (plugin !== this.currentPlugin && this.promisedPatches.length) {
309 const promises = this.promisedPatches.map(p => p.value);
310
311 // Waits for all to settle instead of Promise.all which stops on rejection
312 return Promise.all(promises.map(promise => promise.then(noop, noop))).then(() => this.dispatch());
313 }
314
315 // Ok, run the plugin
316 return executePlugin();
317 function executePlugin() {
318 that.currentPlugin = plugin;
319 const mutations = that.getCurrentMutations();
320 const lastMutationIndex = that.mutations.length - 1;
321 try {
322 if (plugin.isGenerator) {
323 // eslint-disable-next-line no-restricted-syntax
324 for (const yieldedPatches of plugin(mutations, that.getLib())) {
325 updatePatches(yieldedPatches);
326 }
327 } else {
328 const newPatches = plugin(mutations, that.getLib());
329 updatePatches(newPatches);
330 }
331 } catch (e) {
332 console.error(e); // eslint-disable-line no-console
333 updatePatches([Object.assign(Object.create(e), {
334 plugin
335 })]);
336 } finally {
337 that.updatePluginHistory(plugin, {
338 mutationIndex: lastMutationIndex
339 });
340 }
341 return that.dispatch();
342 }
343 function updatePatches(patches) {
344 if (patches) {
345 patches = lib.fullyNormalizeArray(patches);
346 that.updatePatches(patches, plugin);
347 }
348 }
349 }
350}
351export default function mapSpec(opts) {
352 return new SpecMap(opts).dispatch();
353}
354const plugins = {
355 refs,
356 allOf,
357 parameters,
358 properties
359};
360export { SpecMap, plugins };
Note: See TracBrowser for help on using the repository browser.