UNPKG

8.42 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const ts_morph_1 = require("ts-morph");
4const errors_1 = require("../errors");
5const locations_1 = require("../locations");
6const util_1 = require("../util");
7const default_response_parser_1 = require("./default-response-parser");
8const parser_helpers_1 = require("./parser-helpers");
9const request_parser_1 = require("./request-parser");
10const response_parser_1 = require("./response-parser");
11function parseEndpoint(klass, typeTable, lociTable) {
12 var _a, _b;
13 const decorator = klass.getDecoratorOrThrow("endpoint");
14 const decoratorConfig = parser_helpers_1.getDecoratorConfigOrThrow(decorator);
15 // Handle name
16 const name = klass.getNameOrThrow();
17 // Handle method
18 const methodProp = parser_helpers_1.getObjLiteralPropOrThrow(decoratorConfig, "method");
19 const methodLiteral = parser_helpers_1.getPropValueAsStringOrThrow(methodProp);
20 const method = methodLiteral.getLiteralText();
21 if (!parser_helpers_1.isHttpMethod(method)) {
22 throw new Error(`expected a HttpMethod, got ${method}`);
23 }
24 // Handle tags
25 const tagsResult = extractEndpointTags(decoratorConfig);
26 if (tagsResult.isErr())
27 return tagsResult;
28 const tags = tagsResult.unwrap();
29 // Handle jsdoc
30 const description = (_a = parser_helpers_1.getJsDoc(klass)) === null || _a === void 0 ? void 0 : _a.getDescription().trim();
31 // Handle draft
32 const draft = klass.getDecorator("draft") !== undefined;
33 // Handle request
34 const requestMethod = parser_helpers_1.getMethodWithDecorator(klass, "request");
35 const requestResult = requestMethod
36 ? request_parser_1.parseRequest(requestMethod, typeTable, lociTable)
37 : util_1.ok(undefined);
38 if (requestResult.isErr())
39 return requestResult;
40 const request = requestResult.unwrap();
41 // Handle responses
42 const responsesResult = extractEndpointResponses(klass, typeTable, lociTable);
43 if (responsesResult.isErr())
44 return responsesResult;
45 const responses = responsesResult.unwrap();
46 // Handle default response
47 const defaultResponseMethod = parser_helpers_1.getMethodWithDecorator(klass, "defaultResponse");
48 const defaultResponseResult = defaultResponseMethod
49 ? default_response_parser_1.parseDefaultResponse(defaultResponseMethod, typeTable, lociTable)
50 : util_1.ok(undefined);
51 if (defaultResponseResult.isErr())
52 return defaultResponseResult;
53 const defaultResponse = defaultResponseResult.unwrap();
54 // Handle path
55 const pathResult = extractEndpointPath(decoratorConfig);
56 if (pathResult.isErr())
57 return pathResult;
58 const path = pathResult.unwrap();
59 // Check request path params cover the path dynamic components
60 const pathParamsInPath = getDynamicPathComponents(path);
61 const pathParamsInRequest = (_b = request === null || request === void 0 ? void 0 : request.pathParams.map(pathParam => pathParam.name)) !== null && _b !== void 0 ? _b : [];
62 const exclusivePathParamsInPath = pathParamsInPath.filter(pathParam => !pathParamsInRequest.includes(pathParam));
63 const exclusivePathParamsInRequest = pathParamsInRequest.filter(pathParam => !pathParamsInPath.includes(pathParam));
64 if (exclusivePathParamsInPath.length !== 0) {
65 return util_1.err(new errors_1.ParserError(`endpoint path dynamic components must have a corresponding path param defined in @request. Violating path components: ${exclusivePathParamsInPath.join(", ")}`, {
66 file: klass.getSourceFile().getFilePath(),
67 position: klass.getPos()
68 }));
69 }
70 if (exclusivePathParamsInRequest.length !== 0) {
71 return util_1.err(new errors_1.ParserError(`endpoint request path params must have a corresponding dynamic path component defined in @endpoint. Violating path params: ${exclusivePathParamsInRequest.join(", ")}`, {
72 file: klass.getSourceFile().getFilePath(),
73 position: klass.getPos()
74 }));
75 }
76 // Add location data
77 lociTable.addMorphNode(locations_1.LociTable.endpointClassKey(name), klass);
78 lociTable.addMorphNode(locations_1.LociTable.endpointDecoratorKey(name), decorator);
79 lociTable.addMorphNode(locations_1.LociTable.endpointMethodKey(name), methodProp);
80 return util_1.ok({
81 name,
82 description,
83 tags,
84 method,
85 path,
86 request,
87 responses,
88 defaultResponse,
89 draft
90 });
91}
92exports.parseEndpoint = parseEndpoint;
93function extractEndpointTags(decoratorConfig) {
94 const tagsProp = parser_helpers_1.getObjLiteralProp(decoratorConfig, "tags");
95 if (tagsProp === undefined)
96 return util_1.ok([]);
97 const tagsLiteral = parser_helpers_1.getPropValueAsArrayOrThrow(tagsProp);
98 const tags = [];
99 for (const elementExpr of tagsLiteral.getElements()) {
100 // Sanity check, typesafety should prevent any non-string tags
101 if (!ts_morph_1.TypeGuards.isStringLiteral(elementExpr)) {
102 return util_1.err(new errors_1.ParserError("endpoint tag must be a string", {
103 file: elementExpr.getSourceFile().getFilePath(),
104 position: elementExpr.getPos()
105 }));
106 }
107 const tag = elementExpr.getLiteralText().trim();
108 if (tag.length === 0) {
109 return util_1.err(new errors_1.ParserError("endpoint tag cannot be blank", {
110 file: elementExpr.getSourceFile().getFilePath(),
111 position: elementExpr.getPos()
112 }));
113 }
114 if (!/^[\w\s-]*$/.test(tag)) {
115 return util_1.err(new errors_1.ParserError("endpoint tag may only contain alphanumeric, space, underscore and hyphen characters", {
116 file: elementExpr.getSourceFile().getFilePath(),
117 position: elementExpr.getPos()
118 }));
119 }
120 tags.push(tag);
121 }
122 const duplicateTags = [
123 ...new Set(tags.filter((tag, index) => tags.indexOf(tag) !== index))
124 ];
125 if (duplicateTags.length !== 0) {
126 return util_1.err(new errors_1.ParserError(`endpoint tags may not contain duplicates: ${duplicateTags.join(", ")}`, {
127 file: tagsProp.getSourceFile().getFilePath(),
128 position: tagsProp.getPos()
129 }));
130 }
131 return util_1.ok(tags.sort((a, b) => (b > a ? -1 : 1)));
132}
133function extractEndpointPath(decoratorConfig) {
134 const pathProp = parser_helpers_1.getObjLiteralPropOrThrow(decoratorConfig, "path");
135 const pathLiteral = parser_helpers_1.getPropValueAsStringOrThrow(pathProp);
136 const path = pathLiteral.getLiteralText();
137 const dynamicComponents = getDynamicPathComponents(path);
138 const duplicateDynamicComponents = [
139 ...new Set(dynamicComponents.filter((component, index) => dynamicComponents.indexOf(component) !== index))
140 ];
141 if (duplicateDynamicComponents.length !== 0) {
142 return util_1.err(new errors_1.ParserError("endpoint path dynamic components must have unique names", {
143 file: pathProp.getSourceFile().getFilePath(),
144 position: pathProp.getPos()
145 }));
146 }
147 return util_1.ok(path);
148}
149function extractEndpointResponses(klass, typeTable, lociTable) {
150 const responseMethods = klass
151 .getMethods()
152 .filter(m => m.getDecorator("response") !== undefined);
153 const responses = [];
154 for (const method of responseMethods) {
155 const responseResult = response_parser_1.parseResponse(method, typeTable, lociTable);
156 if (responseResult.isErr())
157 return responseResult;
158 responses.push(responseResult.unwrap());
159 }
160 // ensure unique response statsues
161 const statuses = responses.map(r => r.status);
162 const duplicateStatuses = [
163 ...new Set(statuses.filter((status, index) => statuses.indexOf(status) !== index))
164 ];
165 if (duplicateStatuses.length !== 0) {
166 return util_1.err(new errors_1.ParserError(`endpoint responses must have unique statuses. Duplicates found: ${duplicateStatuses.join(", ")}`, { file: klass.getSourceFile().getFilePath(), position: klass.getPos() }));
167 }
168 return util_1.ok(responses.sort((a, b) => (b.status > a.status ? -1 : 1)));
169}
170function getDynamicPathComponents(path) {
171 return path
172 .split("/")
173 .filter(component => component.startsWith(":"))
174 .map(component => component.substr(1));
175}