1 | const debug = require('debug')('upward-js:ResolverVisitor');
|
2 | const { inspect } = require('util');
|
3 | const { ResolverList, ResolversByType } = require('./resolvers');
|
4 | const { zipObject } = require('lodash');
|
5 | const isPrimitive = require('./isPrimitive');
|
6 |
|
7 | class ResolverVisitor {
|
8 | constructor(io, rootDefinition, context, upwardPath) {
|
9 | this.io = io;
|
10 | this.rootDefinition = rootDefinition;
|
11 | this.context = context;
|
12 | this.upwardPath = upwardPath;
|
13 | this.context.setVisitor(this);
|
14 | }
|
15 | async downward(contextNames) {
|
16 | debug('resolving downward: %o', contextNames);
|
17 | let passedMiddleware = false;
|
18 | const valuePromises = contextNames.map(async name => {
|
19 | const value = await this.upward(this.rootDefinition, name);
|
20 | if (typeof value === 'function') {
|
21 | debug(
|
22 | '%s request returned a function, we are assuming it is a middleware'
|
23 | );
|
24 | passedMiddleware = value;
|
25 | throw new Error('PASSED_MIDDLEWARE');
|
26 | }
|
27 | return value;
|
28 | });
|
29 | try {
|
30 | const values = await Promise.all(valuePromises);
|
31 | return zipObject(contextNames, values);
|
32 | } catch (e) {
|
33 | if (e.message === 'PASSED_MIDDLEWARE') {
|
34 | debug(
|
35 | `returning middleware from visitor.downward() instead of object`
|
36 | );
|
37 | return passedMiddleware;
|
38 | } else {
|
39 | throw e;
|
40 | }
|
41 | }
|
42 | }
|
43 | async upward(definition, propertyName) {
|
44 | debug('resolving upward: %s from %o', propertyName, definition);
|
45 | if (!definition.hasOwnProperty(propertyName)) {
|
46 | throw new Error(
|
47 | `Context value '${propertyName}' not defined in ${inspect(
|
48 | definition
|
49 | )}.`
|
50 | );
|
51 | }
|
52 | const defined = definition[propertyName];
|
53 |
|
54 | const resolver = this.getResolverFor(defined, propertyName);
|
55 |
|
56 | if (resolver) {
|
57 | return resolver.resolve(defined);
|
58 | }
|
59 |
|
60 | if (isPrimitive(defined)) {
|
61 | debug(
|
62 | 'defined: %s is primitive, yielding to context.get("%s")',
|
63 | defined,
|
64 | defined
|
65 | );
|
66 | return this.context.get(defined);
|
67 | }
|
68 |
|
69 | if (typeof defined !== 'object' || !this.getResolverFailure) {
|
70 | throw new Error(`Unexpected value in config: ${defined}`);
|
71 | } else {
|
72 | throw new Error(this.getResolverFailure);
|
73 | }
|
74 | }
|
75 | getResolverFor(defined, propertyName) {
|
76 | let Resolver;
|
77 | for (Resolver of ResolverList) {
|
78 | const recognized =
|
79 | Resolver.recognize && Resolver.recognize(defined);
|
80 | if (recognized) {
|
81 | return {
|
82 | resolve: () => new Resolver(this).resolve(recognized)
|
83 | };
|
84 | }
|
85 | }
|
86 | if (defined.resolver) {
|
87 | Resolver = ResolversByType[defined.resolver];
|
88 | if (!Resolver) {
|
89 | this.getResolverFailure = `Unrecognized resolver type: ${
|
90 | defined.resolver
|
91 | }`;
|
92 | }
|
93 | } else {
|
94 | Resolver = ResolverList.find(({ telltale }) =>
|
95 | defined.hasOwnProperty(telltale)
|
96 | );
|
97 | if (!Resolver) {
|
98 | this.getResolverFailure = `Unrecognized configuration. Could not match a resolver to ${propertyName}: ${inspect(
|
99 | defined
|
100 | )}`;
|
101 | }
|
102 | }
|
103 | if (Resolver) return new Resolver(this);
|
104 | }
|
105 | }
|
106 |
|
107 | module.exports = ResolverVisitor;
|