@nestia/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/bundle/HttpError.ts +1 -0
- package/assets/bundle/IConnection.ts +1 -0
- package/assets/bundle/Primitive.ts +1 -0
- package/assets/config/nestia.config.ts +70 -0
- package/lib/INestiaConfig.d.ts +110 -0
- package/lib/INestiaConfig.js +3 -0
- package/lib/INestiaConfig.js.map +1 -0
- package/lib/NestiaSdkApplication.d.ts +11 -0
- package/lib/NestiaSdkApplication.js +156 -0
- package/lib/NestiaSdkApplication.js.map +1 -0
- package/lib/analyses/ControllerAnalyzer.d.ts +6 -0
- package/lib/analyses/ControllerAnalyzer.js +106 -0
- package/lib/analyses/ControllerAnalyzer.js.map +1 -0
- package/lib/analyses/GenericAnalyzer.d.ts +5 -0
- package/lib/analyses/GenericAnalyzer.js +41 -0
- package/lib/analyses/GenericAnalyzer.js.map +1 -0
- package/lib/analyses/ImportAnalyzer.d.ts +13 -0
- package/lib/analyses/ImportAnalyzer.js +86 -0
- package/lib/analyses/ImportAnalyzer.js.map +1 -0
- package/lib/analyses/PathAnalyzer.d.ts +5 -0
- package/lib/analyses/PathAnalyzer.js +51 -0
- package/lib/analyses/PathAnalyzer.js.map +1 -0
- package/lib/analyses/ReflectAnalyzer.d.ts +4 -0
- package/lib/analyses/ReflectAnalyzer.js +231 -0
- package/lib/analyses/ReflectAnalyzer.js.map +1 -0
- package/lib/analyses/SourceFinder.d.ts +4 -0
- package/lib/analyses/SourceFinder.js +71 -0
- package/lib/analyses/SourceFinder.js.map +1 -0
- package/lib/executable/internal/CommandParser.d.ts +3 -0
- package/lib/executable/internal/CommandParser.js +21 -0
- package/lib/executable/internal/CommandParser.js.map +1 -0
- package/lib/executable/internal/NestiaConfigCompilerOptions.d.ts +11 -0
- package/lib/executable/internal/NestiaConfigCompilerOptions.js +18 -0
- package/lib/executable/internal/NestiaConfigCompilerOptions.js.map +1 -0
- package/lib/executable/internal/NestiaSdkCommand.d.ts +4 -0
- package/lib/executable/internal/NestiaSdkCommand.js +128 -0
- package/lib/executable/internal/NestiaSdkCommand.js.map +1 -0
- package/lib/executable/internal/NestiaSdkConfig.d.ts +4 -0
- package/lib/executable/internal/NestiaSdkConfig.js +539 -0
- package/lib/executable/internal/NestiaSdkConfig.js.map +1 -0
- package/lib/executable/internal/nestia.config.getter.d.ts +1 -0
- package/lib/executable/internal/nestia.config.getter.js +24 -0
- package/lib/executable/internal/nestia.config.getter.js.map +1 -0
- package/lib/executable/sdk.d.ts +2 -0
- package/lib/executable/sdk.js +86 -0
- package/lib/executable/sdk.js.map +1 -0
- package/lib/generates/FileGenerator.d.ts +5 -0
- package/lib/generates/FileGenerator.js +138 -0
- package/lib/generates/FileGenerator.js.map +1 -0
- package/lib/generates/FunctionGenerator.d.ts +5 -0
- package/lib/generates/FunctionGenerator.js +204 -0
- package/lib/generates/FunctionGenerator.js.map +1 -0
- package/lib/generates/SdkGenerator.d.ts +7 -0
- package/lib/generates/SdkGenerator.js +45 -0
- package/lib/generates/SdkGenerator.js.map +1 -0
- package/lib/generates/SwaggerGenerator.d.ts +6 -0
- package/lib/generates/SwaggerGenerator.js +244 -0
- package/lib/generates/SwaggerGenerator.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +28 -0
- package/lib/index.js.map +1 -0
- package/lib/module.d.ts +2 -0
- package/lib/module.js +19 -0
- package/lib/module.js.map +1 -0
- package/lib/structures/IController.d.ts +23 -0
- package/lib/structures/IController.js +3 -0
- package/lib/structures/IController.js.map +1 -0
- package/lib/structures/IRoute.d.ts +24 -0
- package/lib/structures/IRoute.js +3 -0
- package/lib/structures/IRoute.js.map +1 -0
- package/lib/structures/ISwagger.d.ts +48 -0
- package/lib/structures/ISwagger.js +3 -0
- package/lib/structures/ISwagger.js.map +1 -0
- package/lib/structures/ITypeTuple.d.ts +5 -0
- package/lib/structures/ITypeTuple.js +3 -0
- package/lib/structures/ITypeTuple.js.map +1 -0
- package/lib/structures/MethodType.d.ts +4 -0
- package/lib/structures/MethodType.js +14 -0
- package/lib/structures/MethodType.js.map +1 -0
- package/lib/structures/ParamCategory.d.ts +1 -0
- package/lib/structures/ParamCategory.js +3 -0
- package/lib/structures/ParamCategory.js.map +1 -0
- package/lib/structures/TypeEntry.d.ts +9 -0
- package/lib/structures/TypeEntry.js +21 -0
- package/lib/structures/TypeEntry.js.map +1 -0
- package/lib/test/TestBuilder.d.ts +4 -0
- package/lib/test/TestBuilder.js +64 -0
- package/lib/test/TestBuilder.js.map +1 -0
- package/lib/test/index.d.ts +1 -0
- package/lib/test/index.js +61 -0
- package/lib/test/index.js.map +1 -0
- package/lib/test/test.builder.executor.d.ts +1 -0
- package/lib/test/test.builder.executor.js +24 -0
- package/lib/test/test.builder.executor.js.map +1 -0
- package/lib/utils/ArrayUtil.d.ts +5 -0
- package/lib/utils/ArrayUtil.js +39 -0
- package/lib/utils/ArrayUtil.js.map +1 -0
- package/lib/utils/ImportDictionary.d.ts +6 -0
- package/lib/utils/ImportDictionary.js +50 -0
- package/lib/utils/ImportDictionary.js.map +1 -0
- package/lib/utils/MapUtil.d.ts +3 -0
- package/lib/utils/MapUtil.js +16 -0
- package/lib/utils/MapUtil.js.map +1 -0
- package/lib/utils/StripEnums.d.ts +3 -0
- package/lib/utils/StripEnums.js +3 -0
- package/lib/utils/StripEnums.js.map +1 -0
- package/package.json +74 -0
- package/src/INestiaConfig.ts +120 -0
- package/src/NestiaSdkApplication.ts +183 -0
- package/src/analyses/ControllerAnalyzer.ts +203 -0
- package/src/analyses/GenericAnalyzer.ts +53 -0
- package/src/analyses/ImportAnalyzer.ts +143 -0
- package/src/analyses/PathAnalyzer.ts +58 -0
- package/src/analyses/ReflectAnalyzer.ts +279 -0
- package/src/analyses/SourceFinder.ts +59 -0
- package/src/executable/internal/CommandParser.ts +15 -0
- package/src/executable/internal/NestiaConfigCompilerOptions.ts +18 -0
- package/src/executable/internal/NestiaSdkCommand.ts +174 -0
- package/src/executable/internal/NestiaSdkConfig.ts +35 -0
- package/src/executable/internal/nestia.config.getter.ts +12 -0
- package/src/executable/sdk.ts +51 -0
- package/src/generates/FileGenerator.ts +156 -0
- package/src/generates/FunctionGenerator.ts +287 -0
- package/src/generates/SdkGenerator.ts +50 -0
- package/src/generates/SwaggerGenerator.ts +393 -0
- package/src/index.ts +3 -0
- package/src/module.ts +2 -0
- package/src/structures/IController.ts +27 -0
- package/src/structures/IRoute.ts +29 -0
- package/src/structures/ISwagger.ts +55 -0
- package/src/structures/ITypeTuple.ts +6 -0
- package/src/structures/MethodType.ts +11 -0
- package/src/structures/ParamCategory.ts +1 -0
- package/src/structures/TypeEntry.ts +22 -0
- package/src/utils/ArrayUtil.ts +26 -0
- package/src/utils/ImportDictionary.ts +56 -0
- package/src/utils/MapUtil.ts +14 -0
- package/src/utils/StripEnums.ts +10 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import NodePath from "path";
|
|
3
|
+
import { Singleton } from "tstl/thread/Singleton";
|
|
4
|
+
import { VariadicSingleton } from "tstl/thread/VariadicSingleton";
|
|
5
|
+
import ts from "typescript";
|
|
6
|
+
import { IJsonApplication, IJsonSchema } from "typia";
|
|
7
|
+
import { CommentFactory } from "typia/lib/factories/CommentFactory";
|
|
8
|
+
import { MetadataCollection } from "typia/lib/factories/MetadataCollection";
|
|
9
|
+
import { MetadataFactory } from "typia/lib/factories/MetadataFactory";
|
|
10
|
+
import { Metadata } from "typia/lib/metadata/Metadata";
|
|
11
|
+
import { ApplicationProgrammer } from "typia/lib/programmers/ApplicationProgrammer";
|
|
12
|
+
|
|
13
|
+
import { INestiaConfig } from "../INestiaConfig";
|
|
14
|
+
import { IRoute } from "../structures/IRoute";
|
|
15
|
+
import { ISwagger } from "../structures/ISwagger";
|
|
16
|
+
import { MapUtil } from "../utils/MapUtil";
|
|
17
|
+
|
|
18
|
+
export namespace SwaggerGenerator {
|
|
19
|
+
export async function generate(
|
|
20
|
+
checker: ts.TypeChecker,
|
|
21
|
+
config: INestiaConfig.ISwagger,
|
|
22
|
+
routeList: IRoute[],
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
// PREPARE ASSETS
|
|
25
|
+
const parsed: NodePath.ParsedPath = NodePath.parse(config.output);
|
|
26
|
+
const location: string = !!parsed.ext
|
|
27
|
+
? NodePath.resolve(config.output)
|
|
28
|
+
: NodePath.join(NodePath.resolve(config.output), "swagger.json");
|
|
29
|
+
|
|
30
|
+
const collection: MetadataCollection = new MetadataCollection({
|
|
31
|
+
replace: MetadataCollection.replace,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// CONSTRUCT SWAGGER DOCUMENTS
|
|
35
|
+
const tupleList: Array<ISchemaTuple> = [];
|
|
36
|
+
const swagger: ISwagger = await initialize(location);
|
|
37
|
+
const pathDict: Map<string, ISwagger.IPath> = new Map();
|
|
38
|
+
|
|
39
|
+
for (const route of routeList) {
|
|
40
|
+
const path: ISwagger.IPath = MapUtil.take(
|
|
41
|
+
pathDict,
|
|
42
|
+
get_path(route.path, route.parameters),
|
|
43
|
+
() => ({}),
|
|
44
|
+
);
|
|
45
|
+
path[route.method.toLowerCase()] = generate_route(
|
|
46
|
+
checker,
|
|
47
|
+
collection,
|
|
48
|
+
tupleList,
|
|
49
|
+
route,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
swagger.paths = {};
|
|
53
|
+
for (const [path, routes] of pathDict) {
|
|
54
|
+
swagger.paths[path] = routes;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// FILL JSON-SCHEMAS
|
|
58
|
+
const application: IJsonApplication = ApplicationProgrammer.generate(
|
|
59
|
+
tupleList.map(({ metadata }) => metadata),
|
|
60
|
+
{
|
|
61
|
+
purpose: "swagger",
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
swagger.components = {
|
|
65
|
+
...(swagger.components || {}),
|
|
66
|
+
schemas: application.components.schemas,
|
|
67
|
+
};
|
|
68
|
+
tupleList.forEach(({ schema }, index) => {
|
|
69
|
+
Object.assign(schema, application.schemas[index]!);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ERASE IJsonComponents.IObject.$id
|
|
73
|
+
for (const obj of Object.values(swagger.components.schemas))
|
|
74
|
+
if (obj.$id) delete obj.$id;
|
|
75
|
+
|
|
76
|
+
// DO GENERATE
|
|
77
|
+
await fs.promises.writeFile(
|
|
78
|
+
location,
|
|
79
|
+
JSON.stringify(swagger, null, 2),
|
|
80
|
+
"utf8",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* ---------------------------------------------------------
|
|
85
|
+
INITIALIZERS
|
|
86
|
+
--------------------------------------------------------- */
|
|
87
|
+
async function initialize(path: string): Promise<ISwagger> {
|
|
88
|
+
// LOAD OR CREATE NEW SWAGGER DATA
|
|
89
|
+
const swagger: ISwagger = fs.existsSync(path)
|
|
90
|
+
? JSON.parse(await fs.promises.readFile(path, "utf8"))
|
|
91
|
+
: {
|
|
92
|
+
openapi: "3.0.1",
|
|
93
|
+
servers: [
|
|
94
|
+
{
|
|
95
|
+
url: "https://github.com/samchon/nestia",
|
|
96
|
+
description: "insert your server url",
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
info: {
|
|
100
|
+
version: "0.1.0",
|
|
101
|
+
title: "Generated by nestia - https://github.com/samchon/nestia",
|
|
102
|
+
},
|
|
103
|
+
paths: {},
|
|
104
|
+
components: {},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// RETURNS
|
|
108
|
+
return swagger;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function get_path(path: string, parameters: IRoute.IParameter[]): string {
|
|
112
|
+
const filtered: IRoute.IParameter[] = parameters.filter(
|
|
113
|
+
(param) => param.category === "param" && !!param.field,
|
|
114
|
+
);
|
|
115
|
+
for (const param of filtered)
|
|
116
|
+
path = path.replace(`:${param.field}`, `{${param.field}}`);
|
|
117
|
+
return path;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function generate_route(
|
|
121
|
+
checker: ts.TypeChecker,
|
|
122
|
+
collection: MetadataCollection,
|
|
123
|
+
tupleList: Array<ISchemaTuple>,
|
|
124
|
+
route: IRoute,
|
|
125
|
+
): ISwagger.IRoute {
|
|
126
|
+
const bodyParam = route.parameters.find(
|
|
127
|
+
(param) => param.category === "body",
|
|
128
|
+
);
|
|
129
|
+
const tags: string[] = route.tags
|
|
130
|
+
.filter(
|
|
131
|
+
(tag) =>
|
|
132
|
+
tag.name === "tag" &&
|
|
133
|
+
tag.text &&
|
|
134
|
+
tag.text.find(
|
|
135
|
+
(elem) => elem.kind === "text" && elem.text.length,
|
|
136
|
+
) !== undefined,
|
|
137
|
+
)
|
|
138
|
+
.map((tag) => tag.text!.find((elem) => elem.kind === "text")!.text);
|
|
139
|
+
|
|
140
|
+
const encrypted: boolean =
|
|
141
|
+
route.encrypted === true ||
|
|
142
|
+
!!route.parameters.find((param) => param.encrypted === true);
|
|
143
|
+
return {
|
|
144
|
+
tags,
|
|
145
|
+
summary: encrypted ? "encrypted" : undefined,
|
|
146
|
+
parameters: route.parameters
|
|
147
|
+
.filter((param) => param.category !== "body")
|
|
148
|
+
.map((param) =>
|
|
149
|
+
generate_parameter(
|
|
150
|
+
checker,
|
|
151
|
+
collection,
|
|
152
|
+
tupleList,
|
|
153
|
+
route,
|
|
154
|
+
param,
|
|
155
|
+
),
|
|
156
|
+
),
|
|
157
|
+
requestBody: bodyParam
|
|
158
|
+
? generate_request_body(
|
|
159
|
+
checker,
|
|
160
|
+
collection,
|
|
161
|
+
tupleList,
|
|
162
|
+
route,
|
|
163
|
+
bodyParam,
|
|
164
|
+
)
|
|
165
|
+
: undefined,
|
|
166
|
+
responses: generate_response_body(
|
|
167
|
+
checker,
|
|
168
|
+
collection,
|
|
169
|
+
tupleList,
|
|
170
|
+
route,
|
|
171
|
+
),
|
|
172
|
+
description: CommentFactory.generate(route.comments),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* ---------------------------------------------------------
|
|
177
|
+
REQUEST & RESPONSE
|
|
178
|
+
--------------------------------------------------------- */
|
|
179
|
+
function generate_parameter(
|
|
180
|
+
checker: ts.TypeChecker,
|
|
181
|
+
collection: MetadataCollection,
|
|
182
|
+
tupleList: Array<ISchemaTuple>,
|
|
183
|
+
route: IRoute,
|
|
184
|
+
parameter: IRoute.IParameter,
|
|
185
|
+
): ISwagger.IParameter {
|
|
186
|
+
const schema: IJsonSchema | null = generate_schema(
|
|
187
|
+
checker,
|
|
188
|
+
collection,
|
|
189
|
+
tupleList,
|
|
190
|
+
parameter.type.type,
|
|
191
|
+
);
|
|
192
|
+
if (schema === null)
|
|
193
|
+
throw new Error(
|
|
194
|
+
`Error on NestiaApplication.sdk(): invalid parameter type on ${route.symbol}#${parameter.name}`,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
name: parameter.field || parameter.name,
|
|
199
|
+
in: parameter.category === "param" ? "path" : parameter.category,
|
|
200
|
+
description:
|
|
201
|
+
get_parametric_description(route, "param", parameter.name) ||
|
|
202
|
+
"",
|
|
203
|
+
schema,
|
|
204
|
+
required: true,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function generate_request_body(
|
|
209
|
+
checker: ts.TypeChecker,
|
|
210
|
+
collection: MetadataCollection,
|
|
211
|
+
tupleList: Array<ISchemaTuple>,
|
|
212
|
+
route: IRoute,
|
|
213
|
+
parameter: IRoute.IParameter,
|
|
214
|
+
): ISwagger.IRequestBody {
|
|
215
|
+
const schema: IJsonSchema | null = generate_schema(
|
|
216
|
+
checker,
|
|
217
|
+
collection,
|
|
218
|
+
tupleList,
|
|
219
|
+
parameter.type.type,
|
|
220
|
+
);
|
|
221
|
+
if (schema === null)
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Error on NestiaApplication.sdk(): invalid request body type on ${route.symbol}.`,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
description:
|
|
228
|
+
warning.get(parameter.encrypted).get("request") +
|
|
229
|
+
(get_parametric_description(route, "param", parameter.name) ||
|
|
230
|
+
""),
|
|
231
|
+
content: {
|
|
232
|
+
"application/json": {
|
|
233
|
+
schema,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
required: true,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function generate_response_body(
|
|
241
|
+
checker: ts.TypeChecker,
|
|
242
|
+
collection: MetadataCollection,
|
|
243
|
+
tupleList: Array<ISchemaTuple>,
|
|
244
|
+
route: IRoute,
|
|
245
|
+
): ISwagger.IResponseBody {
|
|
246
|
+
// OUTPUT WITH SUCCESS STATUS
|
|
247
|
+
const status: string =
|
|
248
|
+
route.method === "GET" || route.method === "DELETE" ? "200" : "201";
|
|
249
|
+
const schema: IJsonSchema | null = generate_schema(
|
|
250
|
+
checker,
|
|
251
|
+
collection,
|
|
252
|
+
tupleList,
|
|
253
|
+
route.output.type,
|
|
254
|
+
);
|
|
255
|
+
const success: ISwagger.IResponseBody = {
|
|
256
|
+
[status]: {
|
|
257
|
+
description:
|
|
258
|
+
warning.get(route.encrypted).get("response", route.method) +
|
|
259
|
+
(get_parametric_description(route, "return") ||
|
|
260
|
+
get_parametric_description(route, "returns") ||
|
|
261
|
+
""),
|
|
262
|
+
content:
|
|
263
|
+
schema === null || route.output.name === "void"
|
|
264
|
+
? undefined
|
|
265
|
+
: {
|
|
266
|
+
"application/json": {
|
|
267
|
+
schema,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// EXCEPTION STATUSES
|
|
274
|
+
const exceptions: ISwagger.IResponseBody = Object.fromEntries(
|
|
275
|
+
route.tags
|
|
276
|
+
.filter(
|
|
277
|
+
(tag) =>
|
|
278
|
+
tag.name === "throw" &&
|
|
279
|
+
tag.text &&
|
|
280
|
+
tag.text.find(
|
|
281
|
+
(elem) =>
|
|
282
|
+
elem.kind === "text" &&
|
|
283
|
+
isNaN(
|
|
284
|
+
Number(
|
|
285
|
+
elem.text
|
|
286
|
+
.split(" ")
|
|
287
|
+
.map((str) => str.trim())[0],
|
|
288
|
+
),
|
|
289
|
+
) === false,
|
|
290
|
+
) !== undefined,
|
|
291
|
+
)
|
|
292
|
+
.map((tag) => {
|
|
293
|
+
const text: string = tag.text!.find(
|
|
294
|
+
(elem) => elem.kind === "text",
|
|
295
|
+
)!.text;
|
|
296
|
+
const elements: string[] = text
|
|
297
|
+
.split(" ")
|
|
298
|
+
.map((str) => str.trim());
|
|
299
|
+
|
|
300
|
+
return [
|
|
301
|
+
elements[0],
|
|
302
|
+
{
|
|
303
|
+
description: elements.slice(1).join(" "),
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
return { ...exceptions, ...success };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* ---------------------------------------------------------
|
|
312
|
+
UTILS
|
|
313
|
+
--------------------------------------------------------- */
|
|
314
|
+
function generate_schema(
|
|
315
|
+
checker: ts.TypeChecker,
|
|
316
|
+
collection: MetadataCollection,
|
|
317
|
+
tupleList: Array<ISchemaTuple>,
|
|
318
|
+
type: ts.Type,
|
|
319
|
+
): IJsonSchema | null {
|
|
320
|
+
const metadata: Metadata = MetadataFactory.generate(
|
|
321
|
+
checker,
|
|
322
|
+
collection,
|
|
323
|
+
type,
|
|
324
|
+
{
|
|
325
|
+
resolve: false,
|
|
326
|
+
constant: true,
|
|
327
|
+
},
|
|
328
|
+
);
|
|
329
|
+
if (metadata.empty() && metadata.nullable === false) return null;
|
|
330
|
+
|
|
331
|
+
const schema: IJsonSchema = {} as IJsonSchema;
|
|
332
|
+
tupleList.push({ metadata, schema });
|
|
333
|
+
return schema;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function get_parametric_description(
|
|
337
|
+
route: IRoute,
|
|
338
|
+
tagName: string,
|
|
339
|
+
parameterName?: string,
|
|
340
|
+
): string | undefined {
|
|
341
|
+
const parametric: (elem: ts.JSDocTagInfo) => boolean = parameterName
|
|
342
|
+
? (tag) =>
|
|
343
|
+
tag.text!.find(
|
|
344
|
+
(elem) =>
|
|
345
|
+
elem.kind === "parameterName" &&
|
|
346
|
+
elem.text === parameterName,
|
|
347
|
+
) !== undefined
|
|
348
|
+
: () => true;
|
|
349
|
+
|
|
350
|
+
const tag: ts.JSDocTagInfo | undefined = route.tags.find(
|
|
351
|
+
(tag) => tag.name === tagName && tag.text && parametric(tag),
|
|
352
|
+
);
|
|
353
|
+
return tag && tag.text
|
|
354
|
+
? tag.text.find((elem) => elem.kind === "text")?.text
|
|
355
|
+
: undefined;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const warning = new VariadicSingleton((encrypted: boolean) => {
|
|
360
|
+
if (encrypted === false) return new Singleton(() => "");
|
|
361
|
+
|
|
362
|
+
return new VariadicSingleton(
|
|
363
|
+
(type: "request" | "response", method?: string) => {
|
|
364
|
+
const summary =
|
|
365
|
+
type === "request"
|
|
366
|
+
? "Request body must be encrypted."
|
|
367
|
+
: "Response data have been encrypted.";
|
|
368
|
+
|
|
369
|
+
const component =
|
|
370
|
+
type === "request"
|
|
371
|
+
? "[EncryptedBody](https://github.com/samchon/@nestia/core#encryptedbody)"
|
|
372
|
+
: `[EncryptedRoute.${method![0].toUpperCase()}.${method!
|
|
373
|
+
.substring(1)
|
|
374
|
+
.toLowerCase()}](https://github.com/samchon/@nestia/core#encryptedroute)`;
|
|
375
|
+
|
|
376
|
+
return `## Warning
|
|
377
|
+
${summary}
|
|
378
|
+
|
|
379
|
+
The ${type} body data would be encrypted as "AES-128(256) / CBC mode / PKCS#5 Padding / Base64 Encoding", through the ${component} component.
|
|
380
|
+
|
|
381
|
+
Therefore, just utilize this swagger editor only for referencing. If you need to call the real API, using [SDK](https://github.com/samchon/nestia#software-development-kit) would be much better.
|
|
382
|
+
|
|
383
|
+
-----------------
|
|
384
|
+
|
|
385
|
+
`;
|
|
386
|
+
},
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
interface ISchemaTuple {
|
|
391
|
+
metadata: Metadata;
|
|
392
|
+
schema: IJsonSchema;
|
|
393
|
+
}
|
package/src/index.ts
ADDED
package/src/module.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ParamCategory } from "./ParamCategory";
|
|
2
|
+
|
|
3
|
+
export interface IController {
|
|
4
|
+
file: string;
|
|
5
|
+
name: string;
|
|
6
|
+
paths: string[];
|
|
7
|
+
functions: IController.IFunction[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export namespace IController {
|
|
11
|
+
export interface IFunction {
|
|
12
|
+
name: string;
|
|
13
|
+
method: string;
|
|
14
|
+
paths: string[];
|
|
15
|
+
encrypted: boolean;
|
|
16
|
+
|
|
17
|
+
parameters: IParameter[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface IParameter {
|
|
21
|
+
name: string;
|
|
22
|
+
index: number;
|
|
23
|
+
field: string | undefined;
|
|
24
|
+
category: ParamCategory;
|
|
25
|
+
encrypted: boolean;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
|
|
3
|
+
import { ITypeTuple } from "./ITypeTuple";
|
|
4
|
+
import { ParamCategory } from "./ParamCategory";
|
|
5
|
+
|
|
6
|
+
export interface IRoute {
|
|
7
|
+
name: string;
|
|
8
|
+
method: string;
|
|
9
|
+
path: string;
|
|
10
|
+
encrypted: boolean;
|
|
11
|
+
|
|
12
|
+
parameters: IRoute.IParameter[];
|
|
13
|
+
imports: [string, string[]][];
|
|
14
|
+
output: ITypeTuple;
|
|
15
|
+
|
|
16
|
+
symbol: string;
|
|
17
|
+
comments: ts.SymbolDisplayPart[];
|
|
18
|
+
tags: ts.JSDocTagInfo[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export namespace IRoute {
|
|
22
|
+
export interface IParameter {
|
|
23
|
+
name: string;
|
|
24
|
+
field: string | undefined;
|
|
25
|
+
category: ParamCategory;
|
|
26
|
+
encrypted: boolean;
|
|
27
|
+
type: ITypeTuple;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { IJsonComponents, IJsonSchema } from "typia";
|
|
2
|
+
|
|
3
|
+
export interface ISwagger {
|
|
4
|
+
openapi: "3.0";
|
|
5
|
+
servers: ISwagger.IServer[];
|
|
6
|
+
info: ISwagger.IInfo;
|
|
7
|
+
paths: Record<string, ISwagger.IPath>;
|
|
8
|
+
components: IJsonComponents;
|
|
9
|
+
}
|
|
10
|
+
export namespace ISwagger {
|
|
11
|
+
export type IPath = Record<string, IRoute>;
|
|
12
|
+
export interface IRoute {
|
|
13
|
+
description: string;
|
|
14
|
+
tags: string[];
|
|
15
|
+
parameters: IParameter[];
|
|
16
|
+
responses: IResponseBody;
|
|
17
|
+
requestBody?: IRequestBody;
|
|
18
|
+
summary?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IInfo {
|
|
22
|
+
version: string;
|
|
23
|
+
title: string;
|
|
24
|
+
}
|
|
25
|
+
export interface IParameter {
|
|
26
|
+
name: string;
|
|
27
|
+
in: string;
|
|
28
|
+
schema: IJsonSchema;
|
|
29
|
+
required: true;
|
|
30
|
+
description: string;
|
|
31
|
+
}
|
|
32
|
+
export interface IRequestBody {
|
|
33
|
+
description: string;
|
|
34
|
+
content: IJsonContent;
|
|
35
|
+
required: true;
|
|
36
|
+
}
|
|
37
|
+
export type IResponseBody = Record<
|
|
38
|
+
string,
|
|
39
|
+
{
|
|
40
|
+
description: string;
|
|
41
|
+
content?: IJsonContent;
|
|
42
|
+
}
|
|
43
|
+
>;
|
|
44
|
+
|
|
45
|
+
export interface IServer {
|
|
46
|
+
url: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface IJsonContent {
|
|
51
|
+
"application/json": {
|
|
52
|
+
schema: IJsonSchema;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type ParamCategory = "param" | "query" | "body";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { hash } from "tstl/functional/hash";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
|
|
4
|
+
export class TypeEntry {
|
|
5
|
+
public constructor(
|
|
6
|
+
public readonly type: ts.Type,
|
|
7
|
+
public readonly nullable: boolean,
|
|
8
|
+
public readonly required: boolean,
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
public equals(obj: TypeEntry): boolean {
|
|
12
|
+
return (
|
|
13
|
+
this.type === obj.type &&
|
|
14
|
+
this.nullable === obj.nullable &&
|
|
15
|
+
this.required === obj.required
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public hashCode(): number {
|
|
20
|
+
return hash(this.type, this.nullable, this.required);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export namespace ArrayUtil {
|
|
2
|
+
export function has<T>(array: T[], ...items: T[]): boolean {
|
|
3
|
+
return items.every(
|
|
4
|
+
(elem) => array.find((org) => org === elem) !== undefined,
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function asyncMap<Input, Output>(
|
|
9
|
+
array: Input[],
|
|
10
|
+
closure: (input: Input) => Promise<Output>,
|
|
11
|
+
): Promise<Output[]> {
|
|
12
|
+
const ret: Output[] = [];
|
|
13
|
+
for (const elem of array) ret.push(await closure(elem));
|
|
14
|
+
return ret;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function asyncFilter<Input>(
|
|
18
|
+
array: Input[],
|
|
19
|
+
closure: (input: Input) => Promise<boolean>,
|
|
20
|
+
): Promise<Input[]> {
|
|
21
|
+
const ret: Input[] = [];
|
|
22
|
+
for (const elem of array)
|
|
23
|
+
if ((await closure(elem)) === true) ret.push(elem);
|
|
24
|
+
return ret;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { HashMap } from "tstl/container/HashMap";
|
|
3
|
+
import { HashSet } from "tstl/container/HashSet";
|
|
4
|
+
import { Pair } from "tstl/utility/Pair";
|
|
5
|
+
|
|
6
|
+
export class ImportDictionary {
|
|
7
|
+
private readonly dict_: HashMap<string, Pair<boolean, HashSet<string>>> =
|
|
8
|
+
new HashMap();
|
|
9
|
+
|
|
10
|
+
public empty(): boolean {
|
|
11
|
+
return this.dict_.empty();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public emplace(file: string, realistic: boolean, instance: string): void {
|
|
15
|
+
if (file.substr(-5) === ".d.ts") file = file.substr(0, file.length - 5);
|
|
16
|
+
else if (file.substr(-3) === ".ts")
|
|
17
|
+
file = file.substr(0, file.length - 3);
|
|
18
|
+
else
|
|
19
|
+
throw new Error(
|
|
20
|
+
`Error on ImportDictionary.emplace(): extension of the target file "${file}" is not "ts".`,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const pair: Pair<boolean, HashSet<string>> = this.dict_.take(
|
|
24
|
+
file,
|
|
25
|
+
() => new Pair(realistic, new HashSet()),
|
|
26
|
+
);
|
|
27
|
+
pair.second.insert(instance);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public toScript(outDir: string): string {
|
|
31
|
+
const statements: string[] = [];
|
|
32
|
+
for (const it of this.dict_) {
|
|
33
|
+
const file: string = (() => {
|
|
34
|
+
const location: string = path
|
|
35
|
+
.relative(outDir, it.first)
|
|
36
|
+
.split("\\")
|
|
37
|
+
.join("/");
|
|
38
|
+
const index: number = location.lastIndexOf(NODE_MODULES);
|
|
39
|
+
return index === -1
|
|
40
|
+
? `./${location}`
|
|
41
|
+
: location.substring(index + NODE_MODULES.length);
|
|
42
|
+
})();
|
|
43
|
+
const realistic: boolean = it.second.first;
|
|
44
|
+
const instances: string[] = it.second.second.toJSON();
|
|
45
|
+
|
|
46
|
+
statements.push(
|
|
47
|
+
`import ${!realistic ? "type " : ""}{ ${instances.join(
|
|
48
|
+
", ",
|
|
49
|
+
)} } from "${file}";`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return statements.join("\n");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const NODE_MODULES = "node_modules/";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export namespace MapUtil {
|
|
2
|
+
export function take<Key, T>(
|
|
3
|
+
dict: Map<Key, T>,
|
|
4
|
+
key: Key,
|
|
5
|
+
generator: () => T,
|
|
6
|
+
): T {
|
|
7
|
+
const oldbie: T | undefined = dict.get(key);
|
|
8
|
+
if (oldbie) return oldbie;
|
|
9
|
+
|
|
10
|
+
const value: T = generator();
|
|
11
|
+
dict.set(key, value);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
}
|