-
Notifications
You must be signed in to change notification settings - Fork 2
/
PrototypeResolver.js
108 lines (84 loc) · 3.18 KB
/
PrototypeResolver.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
const { CrafterError } = require('./utils');
class PrototypeResolver {
constructor() {
this.prototypes = {};
this.resolvedPrototypes = new Set();
this.prototypeLocations = {};
}
registerPrototype(prototype, sourceFile) {
const protoName = prototype.title.string;
if (this.prototypes[protoName]) {
throw new CrafterError(`Resource prototype "${protoName}" already defined`, prototype.sourceMap);
}
this.prototypes[protoName] = prototype;
this.prototypeLocations[protoName] = sourceFile;
}
extendWith(externalResolver) {
if (!externalResolver) return;
const [isValid, errorText] = validateResolver(externalResolver);
if (!isValid) {
throw new Error(`Failed to extend prototype resolver: ${errorText}`);
}
Object.entries(externalResolver.prototypeLocations).forEach(([protoName, location]) => {
const existingLocation = this.prototypeLocations[protoName];
if (existingLocation !== undefined && existingLocation !== location) {
throw new CrafterError(`Resource prototype "${protoName}" already defined in ${existingLocation}`);
}
if (existingLocation === undefined) {
this.prototypeLocations[protoName] = location;
}
});
Object.entries(externalResolver.prototypes).forEach(([protoName, prototype]) => {
if (this.prototypes[protoName] === undefined) {
this.prototypes[protoName] = prototype;
}
});
this.resolvedPrototypes = new Set([...this.resolvedPrototypes, ...externalResolver.resolvedPrototypes]);
}
resolveRegisteredPrototypes() {
const usedPrototypes = [];
const resolvePrototype = (targetProto) => {
if (usedPrototypes.includes(targetProto.title.string)) {
throw new CrafterError(`Dependencies loop: ${usedPrototypes.concat([targetProto.title.string]).join(' - ')}`, targetProto.sourceMap);
}
if (this.resolvedPrototypes.has(targetProto.title.string)) {
return;
}
usedPrototypes.push(targetProto.title.string);
const baseProtoNames = targetProto.prototypes;
baseProtoNames.forEach((protoName) => {
const basePrototype = this.prototypes[protoName.string];
if (!basePrototype) {
throw new CrafterError(`Unknown prototype: ${protoName.string}`, protoName.sourceMap);
}
resolvePrototype(basePrototype);
copyNewContent(basePrototype, targetProto);
});
usedPrototypes.pop();
this.resolvedPrototypes.add(targetProto.title.string);
};
Object.values(this.prototypes).forEach((p) => {
resolvePrototype(p);
});
}
}
function copyNewContent(src, target) {
src.responses.forEach((r) => {
if (!hasResponse(r)) {
target.responses.push(r);
}
});
function hasResponse(srcAttr) {
return !!target.responses.find(a => a.statusCode.equals(srcAttr.statusCode));
}
}
function validateResolver(resolver) {
if (typeof resolver !== 'object') {
return [false, 'resolver should be an object'];
}
if ([resolver.prototypes, resolver.resolvedPrototypes].some(field => (typeof field !== 'object'))) {
return [false, 'resolver should have a valid form'];
}
return [true];
}
module.exports = PrototypeResolver;