1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const errors_1 = require("../errors");
|
4 | const locations_1 = require("../locations");
|
5 | const types_1 = require("../types");
|
6 | const util_1 = require("../util");
|
7 | const config_parser_1 = require("./config-parser");
|
8 | const endpoint_parser_1 = require("./endpoint-parser");
|
9 | const parser_helpers_1 = require("./parser-helpers");
|
10 | const security_header_parser_1 = require("./security-header-parser");
|
11 |
|
12 |
|
13 |
|
14 | function parseContract(file) {
|
15 | const typeTable = new types_1.TypeTable();
|
16 | const lociTable = new locations_1.LociTable();
|
17 | const klass = parser_helpers_1.getClassWithDecoratorOrThrow(file, "api");
|
18 | const decorator = klass.getDecoratorOrThrow("api");
|
19 | const decoratorConfig = parser_helpers_1.getDecoratorConfigOrThrow(decorator);
|
20 |
|
21 | const nameProp = parser_helpers_1.getObjLiteralPropOrThrow(decoratorConfig, "name");
|
22 | const nameLiteral = parser_helpers_1.getPropValueAsStringOrThrow(nameProp);
|
23 | const name = nameLiteral.getLiteralText().trim();
|
24 | if (name.length === 0) {
|
25 | return util_1.err(new errors_1.ParserError("api name cannot be empty", {
|
26 | file: nameLiteral.getSourceFile().getFilePath(),
|
27 | position: nameLiteral.getPos()
|
28 | }));
|
29 | }
|
30 | if (!/^[\w\s-]*$/.test(name)) {
|
31 | return util_1.err(new errors_1.ParserError("api name may only contain alphanumeric, space, underscore and hyphen characters", {
|
32 | file: nameLiteral.getSourceFile().getFilePath(),
|
33 | position: nameLiteral.getPos()
|
34 | }));
|
35 | }
|
36 |
|
37 | const descriptionDoc = parser_helpers_1.getJsDoc(klass);
|
38 | const description = descriptionDoc === null || descriptionDoc === void 0 ? void 0 : descriptionDoc.getDescription().trim();
|
39 |
|
40 | const configResult = resolveConfig(klass);
|
41 | if (configResult.isErr())
|
42 | return configResult;
|
43 | const config = configResult.unwrap();
|
44 |
|
45 | const securityHeaderProp = parser_helpers_1.getPropertyWithDecorator(klass, "securityHeader");
|
46 | const securityResult = securityHeaderProp &&
|
47 | security_header_parser_1.parseSecurityHeader(securityHeaderProp, typeTable, lociTable);
|
48 | if (securityResult && securityResult.isErr())
|
49 | return securityResult;
|
50 | const security = securityResult === null || securityResult === void 0 ? void 0 : securityResult.unwrap();
|
51 |
|
52 | lociTable.addMorphNode(locations_1.LociTable.apiClassKey(), klass);
|
53 | lociTable.addMorphNode(locations_1.LociTable.apiDecoratorKey(), decorator);
|
54 | lociTable.addMorphNode(locations_1.LociTable.apiNameKey(), nameProp);
|
55 | if (descriptionDoc) {
|
56 | lociTable.addMorphNode(locations_1.LociTable.apiDescriptionKey(), descriptionDoc);
|
57 | }
|
58 |
|
59 | const projectFiles = parser_helpers_1.getSelfAndLocalDependencies(file);
|
60 |
|
61 | const endpointClasses = projectFiles.reduce((acc, currentFile) => acc.concat(currentFile
|
62 | .getClasses()
|
63 | .filter(k => k.getDecorator("endpoint") !== undefined)), []);
|
64 | const endpointsResult = extractEndpoints(endpointClasses, typeTable, lociTable);
|
65 | if (endpointsResult.isErr())
|
66 | return endpointsResult;
|
67 | const endpoints = endpointsResult.unwrap();
|
68 |
|
69 | const types = typeTable.toArray();
|
70 | const contract = { name, description, types, config, security, endpoints };
|
71 | return util_1.ok({ contract, lociTable });
|
72 | }
|
73 | exports.parseContract = parseContract;
|
74 | function resolveConfig(klass) {
|
75 | const hasConfigDecorator = klass.getDecorator("config") !== undefined;
|
76 | if (hasConfigDecorator) {
|
77 | return config_parser_1.parseConfig(klass);
|
78 | }
|
79 | else {
|
80 | return util_1.ok(config_parser_1.defaultConfig());
|
81 | }
|
82 | }
|
83 | function extractEndpoints(endpointClasses, typeTable, lociTable) {
|
84 | const endpointNames = endpointClasses.map(k => k.getNameOrThrow());
|
85 | const duplicateEndpointNames = [
|
86 | ...new Set(endpointNames.filter((name, index) => endpointNames.indexOf(name) !== index))
|
87 | ];
|
88 | if (duplicateEndpointNames.length !== 0) {
|
89 | const locations = duplicateEndpointNames.reduce((acc, name) => {
|
90 | const nameLocations = endpointClasses
|
91 | .filter(k => k.getNameOrThrow() === name)
|
92 | .map(k => {
|
93 | return {
|
94 | file: k.getSourceFile().getFilePath(),
|
95 | position: k.getPos()
|
96 | };
|
97 | });
|
98 | return acc.concat(nameLocations);
|
99 | }, []);
|
100 | return util_1.err(new errors_1.ParserError("endpoints must have unique names", ...locations));
|
101 | }
|
102 | const endpoints = [];
|
103 | for (const k of endpointClasses) {
|
104 | const endpointResult = endpoint_parser_1.parseEndpoint(k, typeTable, lociTable);
|
105 | if (endpointResult.isErr())
|
106 | return endpointResult;
|
107 | endpoints.push(endpointResult.unwrap());
|
108 | }
|
109 | return util_1.ok(endpoints.sort((a, b) => (b.name > a.name ? -1 : 1)));
|
110 | }
|