@g1cloud/api-gen 1.0.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/.claude/settings.local.json +22 -0
- package/CLAUDE.md +63 -0
- package/README.md +379 -0
- package/dist/analyzer/controllerAnalyzer.d.ts +20 -0
- package/dist/analyzer/controllerAnalyzer.d.ts.map +1 -0
- package/dist/analyzer/controllerAnalyzer.js +101 -0
- package/dist/analyzer/controllerAnalyzer.js.map +1 -0
- package/dist/analyzer/parameterAnalyzer.d.ts +19 -0
- package/dist/analyzer/parameterAnalyzer.d.ts.map +1 -0
- package/dist/analyzer/parameterAnalyzer.js +207 -0
- package/dist/analyzer/parameterAnalyzer.js.map +1 -0
- package/dist/analyzer/responseAnalyzer.d.ts +12 -0
- package/dist/analyzer/responseAnalyzer.d.ts.map +1 -0
- package/dist/analyzer/responseAnalyzer.js +116 -0
- package/dist/analyzer/responseAnalyzer.js.map +1 -0
- package/dist/analyzer/schemaGenerator.d.ts +6 -0
- package/dist/analyzer/schemaGenerator.d.ts.map +1 -0
- package/dist/analyzer/schemaGenerator.js +347 -0
- package/dist/analyzer/schemaGenerator.js.map +1 -0
- package/dist/analyzer/securityAnalyzer.d.ts +6 -0
- package/dist/analyzer/securityAnalyzer.d.ts.map +1 -0
- package/dist/analyzer/securityAnalyzer.js +177 -0
- package/dist/analyzer/securityAnalyzer.js.map +1 -0
- package/dist/generator/openapiGenerator.d.ts +14 -0
- package/dist/generator/openapiGenerator.d.ts.map +1 -0
- package/dist/generator/openapiGenerator.js +340 -0
- package/dist/generator/openapiGenerator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +61 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +199 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +257 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp-server.mjs +45586 -0
- package/dist/parser/astAnalyzer.d.ts +87 -0
- package/dist/parser/astAnalyzer.d.ts.map +1 -0
- package/dist/parser/astAnalyzer.js +321 -0
- package/dist/parser/astAnalyzer.js.map +1 -0
- package/dist/parser/javaParser.d.ts +10 -0
- package/dist/parser/javaParser.d.ts.map +1 -0
- package/dist/parser/javaParser.js +805 -0
- package/dist/parser/javaParser.js.map +1 -0
- package/dist/types/index.d.ts +217 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/examples/CreateUserRequest.java +80 -0
- package/examples/DepartmentDTO.java +45 -0
- package/examples/Filter.java +39 -0
- package/examples/PaginatedList.java +71 -0
- package/examples/ProductController.java +136 -0
- package/examples/ProductDTO.java +129 -0
- package/examples/RoleDTO.java +47 -0
- package/examples/SearchParam.java +55 -0
- package/examples/Sort.java +70 -0
- package/examples/UpdateUserRequest.java +74 -0
- package/examples/UserController.java +98 -0
- package/examples/UserDTO.java +119 -0
- package/package.json +51 -0
- package/prompt/01_Initial.md +358 -0
- package/prompt/02_/354/266/224/352/260/200.md +31 -0
- package/src/analyzer/controllerAnalyzer.ts +125 -0
- package/src/analyzer/parameterAnalyzer.ts +259 -0
- package/src/analyzer/responseAnalyzer.ts +142 -0
- package/src/analyzer/schemaGenerator.ts +412 -0
- package/src/analyzer/securityAnalyzer.ts +200 -0
- package/src/generator/openapiGenerator.ts +378 -0
- package/src/index.ts +212 -0
- package/src/lib.ts +240 -0
- package/src/mcp-server.ts +310 -0
- package/src/parser/astAnalyzer.ts +373 -0
- package/src/parser/javaParser.ts +901 -0
- package/src/types/index.ts +238 -0
- package/test-boolean.yaml +607 -0
- package/test-filter.yaml +576 -0
- package/test-inner.ts +59 -0
- package/test-output.yaml +650 -0
- package/test-paginated.yaml +585 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +30 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { JavaClass, JavaAnnotation, JavaMethod } from '../types';
|
|
2
|
+
|
|
3
|
+
export class ASTAnalyzer {
|
|
4
|
+
/**
|
|
5
|
+
* Check if a class is a REST controller
|
|
6
|
+
*/
|
|
7
|
+
static isRestController(javaClass: JavaClass): boolean {
|
|
8
|
+
const annotations = javaClass.annotations;
|
|
9
|
+
|
|
10
|
+
// Check for @RestController
|
|
11
|
+
if (annotations.some((a) => a.name === 'RestController')) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Check for @Controller + @ResponseBody
|
|
16
|
+
const hasController = annotations.some((a) => a.name === 'Controller');
|
|
17
|
+
const hasResponseBody = annotations.some((a) => a.name === 'ResponseBody');
|
|
18
|
+
|
|
19
|
+
return hasController && hasResponseBody;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract base path from @RequestMapping annotation on class level
|
|
24
|
+
*/
|
|
25
|
+
static getClassBasePath(javaClass: JavaClass): string {
|
|
26
|
+
const requestMapping = javaClass.annotations.find((a) => a.name === 'RequestMapping');
|
|
27
|
+
|
|
28
|
+
if (!requestMapping) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const value = requestMapping.values['value'] || requestMapping.values['path'] || '';
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return value[0] || '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof value === 'boolean') {
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if a method is an endpoint (has HTTP method annotation)
|
|
47
|
+
*/
|
|
48
|
+
static isEndpointMethod(method: JavaMethod): boolean {
|
|
49
|
+
const httpAnnotations = [
|
|
50
|
+
'GetMapping',
|
|
51
|
+
'PostMapping',
|
|
52
|
+
'PutMapping',
|
|
53
|
+
'DeleteMapping',
|
|
54
|
+
'PatchMapping',
|
|
55
|
+
'RequestMapping',
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
return method.annotations.some((a) => httpAnnotations.includes(a.name));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get HTTP method from annotation
|
|
63
|
+
*/
|
|
64
|
+
static getHttpMethod(method: JavaMethod): string | null {
|
|
65
|
+
const mappingAnnotations: Record<string, string> = {
|
|
66
|
+
GetMapping: 'get',
|
|
67
|
+
PostMapping: 'post',
|
|
68
|
+
PutMapping: 'put',
|
|
69
|
+
DeleteMapping: 'delete',
|
|
70
|
+
PatchMapping: 'patch',
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
for (const annotation of method.annotations) {
|
|
74
|
+
if (annotation.name in mappingAnnotations) {
|
|
75
|
+
return mappingAnnotations[annotation.name];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (annotation.name === 'RequestMapping') {
|
|
79
|
+
const methodValue = annotation.values['method'];
|
|
80
|
+
if (methodValue && typeof methodValue !== 'boolean') {
|
|
81
|
+
const methodStr = Array.isArray(methodValue) ? methodValue[0] : methodValue;
|
|
82
|
+
const normalizedMethod = methodStr.toLowerCase().replace('requestmethod.', '');
|
|
83
|
+
return normalizedMethod;
|
|
84
|
+
}
|
|
85
|
+
// Default to GET if no method specified
|
|
86
|
+
return 'get';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get endpoint path from method annotation
|
|
95
|
+
*/
|
|
96
|
+
static getMethodPath(method: JavaMethod): string {
|
|
97
|
+
const mappingAnnotations = [
|
|
98
|
+
'GetMapping',
|
|
99
|
+
'PostMapping',
|
|
100
|
+
'PutMapping',
|
|
101
|
+
'DeleteMapping',
|
|
102
|
+
'PatchMapping',
|
|
103
|
+
'RequestMapping',
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
for (const annotation of method.annotations) {
|
|
107
|
+
if (mappingAnnotations.includes(annotation.name)) {
|
|
108
|
+
const value = annotation.values['value'] || annotation.values['path'] || '';
|
|
109
|
+
|
|
110
|
+
if (Array.isArray(value)) {
|
|
111
|
+
return value[0] || '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (typeof value === 'boolean') {
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if an annotation indicates a request parameter
|
|
127
|
+
*/
|
|
128
|
+
static isParameterAnnotation(annotation: JavaAnnotation): boolean {
|
|
129
|
+
const paramAnnotations = ['RequestParam', 'PathVariable', 'RequestHeader', 'RequestBody'];
|
|
130
|
+
return paramAnnotations.includes(annotation.name);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get parameter type (query, path, header, body)
|
|
135
|
+
*/
|
|
136
|
+
static getParameterType(annotations: JavaAnnotation[]): 'query' | 'path' | 'header' | 'body' | null {
|
|
137
|
+
for (const annotation of annotations) {
|
|
138
|
+
switch (annotation.name) {
|
|
139
|
+
case 'RequestParam':
|
|
140
|
+
return 'query';
|
|
141
|
+
case 'PathVariable':
|
|
142
|
+
return 'path';
|
|
143
|
+
case 'RequestHeader':
|
|
144
|
+
return 'header';
|
|
145
|
+
case 'RequestBody':
|
|
146
|
+
return 'body';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check if a type is a SearchParam
|
|
154
|
+
*/
|
|
155
|
+
static isSearchParam(type: string): boolean {
|
|
156
|
+
return type === 'SearchParam' || type.endsWith('.SearchParam');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check if a type is a PaginatedList
|
|
161
|
+
*/
|
|
162
|
+
static isPaginatedList(type: string): boolean {
|
|
163
|
+
return type === 'PaginatedList' || type.startsWith('PaginatedList') || type.endsWith('.PaginatedList');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Extract generic type from a parameterized type
|
|
168
|
+
*/
|
|
169
|
+
static extractGenericType(genericType: string | undefined): string | undefined {
|
|
170
|
+
if (!genericType) return undefined;
|
|
171
|
+
|
|
172
|
+
// Handle nested generics like PaginatedList<UserDTO>
|
|
173
|
+
const match = genericType.match(/^([^<]+)(?:<(.+)>)?$/);
|
|
174
|
+
if (match) {
|
|
175
|
+
return match[1].trim();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return genericType;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if method has security annotations
|
|
183
|
+
*/
|
|
184
|
+
static hasSecurityAnnotation(method: JavaMethod): boolean {
|
|
185
|
+
const securityAnnotations = ['PreAuthorize', 'PostAuthorize', 'Secured', 'RolesAllowed'];
|
|
186
|
+
return method.annotations.some((a) => securityAnnotations.includes(a.name));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get security annotation from method
|
|
191
|
+
*/
|
|
192
|
+
static getSecurityAnnotations(method: JavaMethod): JavaAnnotation[] {
|
|
193
|
+
const securityAnnotations = ['PreAuthorize', 'PostAuthorize', 'Secured', 'RolesAllowed'];
|
|
194
|
+
return method.annotations.filter((a) => securityAnnotations.includes(a.name));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Combine base path with method path
|
|
199
|
+
*/
|
|
200
|
+
static combinePaths(basePath: string, methodPath: string): string {
|
|
201
|
+
// Normalize paths
|
|
202
|
+
const normalizedBase = basePath.replace(/\/+$/, '');
|
|
203
|
+
const normalizedMethod = methodPath.replace(/^\/+/, '');
|
|
204
|
+
|
|
205
|
+
if (!normalizedBase && !normalizedMethod) {
|
|
206
|
+
return '/';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!normalizedBase) {
|
|
210
|
+
return '/' + normalizedMethod;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (!normalizedMethod) {
|
|
214
|
+
return normalizedBase.startsWith('/') ? normalizedBase : '/' + normalizedBase;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const combined = `${normalizedBase}/${normalizedMethod}`;
|
|
218
|
+
return combined.startsWith('/') ? combined : '/' + combined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Convert Java type to OpenAPI type
|
|
223
|
+
*/
|
|
224
|
+
static javaTypeToOpenAPI(javaType: string): { type: string; format?: string } {
|
|
225
|
+
const typeMap: Record<string, { type: string; format?: string }> = {
|
|
226
|
+
// Primitives
|
|
227
|
+
int: { type: 'integer', format: 'int32' },
|
|
228
|
+
long: { type: 'integer', format: 'int64' },
|
|
229
|
+
short: { type: 'integer' },
|
|
230
|
+
byte: { type: 'integer' },
|
|
231
|
+
float: { type: 'number', format: 'float' },
|
|
232
|
+
double: { type: 'number', format: 'double' },
|
|
233
|
+
boolean: { type: 'boolean' },
|
|
234
|
+
char: { type: 'string' },
|
|
235
|
+
|
|
236
|
+
// Wrapper types
|
|
237
|
+
Integer: { type: 'integer', format: 'int32' },
|
|
238
|
+
Long: { type: 'integer', format: 'int64' },
|
|
239
|
+
Short: { type: 'integer' },
|
|
240
|
+
Byte: { type: 'integer' },
|
|
241
|
+
Float: { type: 'number', format: 'float' },
|
|
242
|
+
Double: { type: 'number', format: 'double' },
|
|
243
|
+
Boolean: { type: 'boolean' },
|
|
244
|
+
Character: { type: 'string' },
|
|
245
|
+
|
|
246
|
+
// String types
|
|
247
|
+
String: { type: 'string' },
|
|
248
|
+
|
|
249
|
+
// Date/Time types
|
|
250
|
+
Date: { type: 'string', format: 'date-time' },
|
|
251
|
+
LocalDate: { type: 'string', format: 'date' },
|
|
252
|
+
LocalDateTime: { type: 'string', format: 'date-time' },
|
|
253
|
+
LocalTime: { type: 'string', format: 'time' },
|
|
254
|
+
Instant: { type: 'string', format: 'date-time' },
|
|
255
|
+
ZonedDateTime: { type: 'string', format: 'date-time' },
|
|
256
|
+
OffsetDateTime: { type: 'string', format: 'date-time' },
|
|
257
|
+
|
|
258
|
+
// UUID
|
|
259
|
+
UUID: { type: 'string', format: 'uuid' },
|
|
260
|
+
|
|
261
|
+
// BigDecimal/BigInteger
|
|
262
|
+
BigDecimal: { type: 'number' },
|
|
263
|
+
BigInteger: { type: 'integer' },
|
|
264
|
+
|
|
265
|
+
// Currency
|
|
266
|
+
Currency: { type: 'string' },
|
|
267
|
+
|
|
268
|
+
// Void
|
|
269
|
+
void: { type: 'object' },
|
|
270
|
+
Void: { type: 'object' },
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Check if it's an array type
|
|
274
|
+
if (javaType.endsWith('[]')) {
|
|
275
|
+
return { type: 'array' };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Extract simple type name from fully qualified name
|
|
279
|
+
const simpleName = javaType.split('.').pop() || javaType;
|
|
280
|
+
|
|
281
|
+
return typeMap[simpleName] || { type: 'object' };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Check if a Java type is a primitive or wrapper type
|
|
286
|
+
*/
|
|
287
|
+
static isPrimitiveOrWrapper(javaType: string): boolean {
|
|
288
|
+
const primitiveTypes = [
|
|
289
|
+
'int',
|
|
290
|
+
'long',
|
|
291
|
+
'short',
|
|
292
|
+
'byte',
|
|
293
|
+
'float',
|
|
294
|
+
'double',
|
|
295
|
+
'boolean',
|
|
296
|
+
'char',
|
|
297
|
+
'Integer',
|
|
298
|
+
'Long',
|
|
299
|
+
'Short',
|
|
300
|
+
'Byte',
|
|
301
|
+
'Float',
|
|
302
|
+
'Double',
|
|
303
|
+
'Boolean',
|
|
304
|
+
'Character',
|
|
305
|
+
'String',
|
|
306
|
+
'Date',
|
|
307
|
+
'LocalDate',
|
|
308
|
+
'LocalDateTime',
|
|
309
|
+
'LocalTime',
|
|
310
|
+
'Instant',
|
|
311
|
+
'ZonedDateTime',
|
|
312
|
+
'OffsetDateTime',
|
|
313
|
+
'UUID',
|
|
314
|
+
'BigDecimal',
|
|
315
|
+
'BigInteger',
|
|
316
|
+
'Currency',
|
|
317
|
+
'void',
|
|
318
|
+
'Void',
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
const simpleName = javaType.split('.').pop() || javaType;
|
|
322
|
+
return primitiveTypes.includes(simpleName);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Check if a type is a collection type
|
|
327
|
+
*/
|
|
328
|
+
static isCollectionType(javaType: string): boolean {
|
|
329
|
+
const collectionTypes = ['List', 'Set', 'Collection', 'ArrayList', 'HashSet', 'LinkedList', 'TreeSet'];
|
|
330
|
+
const simpleName = javaType.split('.').pop() || javaType;
|
|
331
|
+
return collectionTypes.includes(simpleName) || javaType.endsWith('[]');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Check if a type is a Map type
|
|
336
|
+
*/
|
|
337
|
+
static isMapType(javaType: string): boolean {
|
|
338
|
+
const mapTypes = ['Map', 'HashMap', 'TreeMap', 'LinkedHashMap', 'ConcurrentHashMap'];
|
|
339
|
+
const simpleName = javaType.split('.').pop() || javaType;
|
|
340
|
+
return mapTypes.includes(simpleName);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Check if the return type is ResponseEntity
|
|
345
|
+
*/
|
|
346
|
+
static isResponseEntity(returnType: string): boolean {
|
|
347
|
+
return returnType === 'ResponseEntity' || returnType.endsWith('.ResponseEntity');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Generate operation ID from method name
|
|
352
|
+
*/
|
|
353
|
+
static generateOperationId(className: string, methodName: string): string {
|
|
354
|
+
// Remove Controller suffix from class name
|
|
355
|
+
const simplifiedClassName = className.replace(/Controller$/, '');
|
|
356
|
+
|
|
357
|
+
// Convert to camelCase
|
|
358
|
+
const operationId = simplifiedClassName.charAt(0).toLowerCase() + simplifiedClassName.slice(1) + '_' + methodName;
|
|
359
|
+
|
|
360
|
+
return operationId;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Generate summary from method name
|
|
365
|
+
*/
|
|
366
|
+
static generateSummary(methodName: string): string {
|
|
367
|
+
// Convert camelCase to words
|
|
368
|
+
const words = methodName.replace(/([A-Z])/g, ' $1').trim();
|
|
369
|
+
|
|
370
|
+
// Capitalize first letter
|
|
371
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
372
|
+
}
|
|
373
|
+
}
|