@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,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(pnpm build:*)",
|
|
5
|
+
"Bash(pnpm dev:*)",
|
|
6
|
+
"Bash(npx ts-node src/index.ts --source ./examples --out-dir ./test-output)",
|
|
7
|
+
"Bash(chmod:*)",
|
|
8
|
+
"Bash(node dist/index.js:*)",
|
|
9
|
+
"Bash(grep:*)",
|
|
10
|
+
"Bash(node -e:*)",
|
|
11
|
+
"Bash(pnpm add:*)",
|
|
12
|
+
"Bash(pnpm approve-builds:*)",
|
|
13
|
+
"Bash(pnpm bundle:*)",
|
|
14
|
+
"Bash(cat:*)",
|
|
15
|
+
"Bash(./dist/index.js:*)",
|
|
16
|
+
"Bash(node /Users/kimos/git/g1cloud-tool/api-gen/dist/index.js:*)",
|
|
17
|
+
"Bash(pnpm install:*)",
|
|
18
|
+
"Bash(timeout 3 node:*)",
|
|
19
|
+
"Bash(npm whoami:*)"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a CLI tool that generates OpenAPI v3 YAML specifications from Spring Boot Java source code. It parses Java files, detects REST controllers, extracts endpoint information, and produces a valid OpenAPI spec.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install dependencies
|
|
13
|
+
pnpm install
|
|
14
|
+
|
|
15
|
+
# Build (compiles TypeScript to dist/)
|
|
16
|
+
pnpm build
|
|
17
|
+
|
|
18
|
+
# Run in development mode
|
|
19
|
+
pnpm dev -- --source ./examples --output ./test-output.yaml
|
|
20
|
+
|
|
21
|
+
# Run the test command (uses example files)
|
|
22
|
+
pnpm test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Architecture
|
|
26
|
+
|
|
27
|
+
The tool follows a pipeline architecture with four main stages:
|
|
28
|
+
|
|
29
|
+
1. **Parsing** (`src/parser/`) - Uses `java-parser` library to parse Java source files into AST
|
|
30
|
+
- `javaParser.ts`: Recursively finds and parses `.java` files, extracting class metadata
|
|
31
|
+
- `astAnalyzer.ts`: Utility functions for querying the parsed AST
|
|
32
|
+
|
|
33
|
+
2. **Analysis** (`src/analyzer/`) - Extracts API-relevant information from parsed classes
|
|
34
|
+
- `controllerAnalyzer.ts`: Orchestrates analysis, identifies `@RestController`/`@Controller` classes
|
|
35
|
+
- `parameterAnalyzer.ts`: Extracts `@RequestParam`, `@PathVariable`, `@RequestHeader`, `@RequestBody`
|
|
36
|
+
- `responseAnalyzer.ts`: Determines return types and generates response schemas
|
|
37
|
+
- `securityAnalyzer.ts`: Parses `@PreAuthorize`/`@PostAuthorize` SpEL expressions for roles
|
|
38
|
+
- `schemaGenerator.ts`: Recursively generates DTO schemas with validation annotations
|
|
39
|
+
|
|
40
|
+
3. **Generation** (`src/generator/openapiGenerator.ts`) - Converts analyzed data to OpenAPI spec and writes YAML
|
|
41
|
+
|
|
42
|
+
4. **Entry Point** (`src/index.ts`) - CLI using Commander, orchestrates the full pipeline
|
|
43
|
+
|
|
44
|
+
### Key Data Flow
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Java Files → JavaFileParser → JavaClass objects → ControllerAnalyzer → EndpointInfo objects → OpenAPIGenerator → YAML
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### ProcessingContext
|
|
51
|
+
|
|
52
|
+
The `ProcessingContext` type is passed through the pipeline and contains:
|
|
53
|
+
- `javaClasses`: Map of parsed Java classes by name
|
|
54
|
+
- `controllers`: Analyzed controller information
|
|
55
|
+
- `dtoSchemas`: Generated schemas for DTOs
|
|
56
|
+
- `referencedTypes`: Set of types that need schema generation
|
|
57
|
+
|
|
58
|
+
## Special Type Handling
|
|
59
|
+
|
|
60
|
+
The tool has built-in handling for:
|
|
61
|
+
- **SearchParam**: Auto-adds pagination query params (offset, limit, filter, sort)
|
|
62
|
+
- **PaginatedList<T>**: Adds pagination headers (X-Total-Count, X-Offset, X-Limit) and generates wrapper schema
|
|
63
|
+
- **ResponseEntity<T>**: Unwraps the generic type for response schema generation
|
package/README.md
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
# @g1cloud/api-gen
|
|
2
|
+
|
|
3
|
+
Spring Boot Java 소스 코드에서 OpenAPI v3 YAML 스펙을 생성하는 CLI 도구입니다.
|
|
4
|
+
|
|
5
|
+
## 기능
|
|
6
|
+
|
|
7
|
+
- **Java 소스 파싱**: `java-parser`를 사용하여 Java 파일에서 클래스 및 메서드 정보 추출
|
|
8
|
+
- **REST Controller 분석**: `@RestController`, `@Controller` 및 HTTP 매핑 어노테이션 감지
|
|
9
|
+
- **파라미터 추출**: `@RequestParam`, `@PathVariable`, `@RequestHeader`, `@RequestBody` 지원
|
|
10
|
+
- **응답 분석**: 반환 타입 추출 및 응답 스키마 생성
|
|
11
|
+
- **보안 어노테이션 파싱**: `@PreAuthorize`, `@PostAuthorize`, `@Secured` 어노테이션 분석
|
|
12
|
+
- **SpEL 표현식 파싱**: Spring Security 표현식에서 역할(role) 및 권한(authority) 추출
|
|
13
|
+
- **DTO 스키마 생성**: 요청/응답 DTO에 대한 스키마 재귀적 생성
|
|
14
|
+
- **특수 타입 처리**:
|
|
15
|
+
- `SearchParam`: 페이지네이션 파라미터 자동 추가 (offset, limit, filter, sort)
|
|
16
|
+
- `PaginatedList<T>`: 페이지네이션 헤더 추가 (X-Total-Count, X-Offset, X-Limit)
|
|
17
|
+
|
|
18
|
+
## 설치
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# 의존성 설치
|
|
22
|
+
pnpm install
|
|
23
|
+
|
|
24
|
+
# 프로젝트 빌드
|
|
25
|
+
pnpm build
|
|
26
|
+
|
|
27
|
+
# 또는 ts-node로 직접 실행
|
|
28
|
+
pnpm dev -- --source ./examples --output ./test-output.yaml
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 사용법
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 기본 사용법 - 단일 통합 YAML 파일 생성
|
|
35
|
+
npx spring-to-openapi --source ./src/main/java --output ./openapi.yaml
|
|
36
|
+
|
|
37
|
+
# 컨트롤러별 모드 - 각 컨트롤러마다 별도 YAML 파일 생성
|
|
38
|
+
npx spring-to-openapi --source ./src/main/java --out-dir ./api-docs
|
|
39
|
+
|
|
40
|
+
# 모든 옵션 사용
|
|
41
|
+
npx spring-to-openapi \
|
|
42
|
+
--source ./src/main/java \
|
|
43
|
+
--output ./openapi.yaml \
|
|
44
|
+
--title "My API" \
|
|
45
|
+
--api-version "1.0.0" \
|
|
46
|
+
--base-path "/api"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### CLI 옵션
|
|
50
|
+
|
|
51
|
+
| 옵션 | 단축 | 설명 | 기본값 |
|
|
52
|
+
|--------|-------|-------------|---------|
|
|
53
|
+
| `--source` | `-s` | Java 소스 코드 디렉토리 경로 | (필수) |
|
|
54
|
+
| `--output` | `-o` | 출력 YAML 파일 경로 (단일 통합 파일) | `openapi.yaml` |
|
|
55
|
+
| `--out-dir` | `-d` | 출력 디렉토리 경로 (컨트롤러별 YAML 생성) | (없음) |
|
|
56
|
+
| `--title` | `-t` | API 제목 | `API Documentation` |
|
|
57
|
+
| `--api-version` | | API 버전 | `1.0.0` |
|
|
58
|
+
| `--base-path` | `-b` | API 기본 경로 | (없음) |
|
|
59
|
+
|
|
60
|
+
> **참고**: `--output`과 `--out-dir`은 상호 배타적입니다. 단일 통합 파일은 `--output`을, 컨트롤러별 YAML 파일 생성은 `--out-dir`을 사용하세요.
|
|
61
|
+
|
|
62
|
+
## 예제
|
|
63
|
+
|
|
64
|
+
### 입력: Java Controller
|
|
65
|
+
|
|
66
|
+
```java
|
|
67
|
+
@RestController
|
|
68
|
+
@RequestMapping("/api/users")
|
|
69
|
+
public class UserController {
|
|
70
|
+
|
|
71
|
+
@GetMapping
|
|
72
|
+
@PreAuthorize("hasRole('USER')")
|
|
73
|
+
public ResponseEntity<PaginatedList<UserDTO>> searchUsers(SearchParam searchParam) {
|
|
74
|
+
return ResponseEntity.ok(new PaginatedList<>());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@GetMapping("/{id}")
|
|
78
|
+
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
|
|
79
|
+
public ResponseEntity<UserDTO> getUserById(@PathVariable Long id) {
|
|
80
|
+
return ResponseEntity.ok(new UserDTO());
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@PutMapping("/{id}")
|
|
84
|
+
@PreAuthorize("hasRole('ADMIN') or (#id == authentication.principal.id and hasRole('USER'))")
|
|
85
|
+
public ResponseEntity<UserDTO> updateUser(
|
|
86
|
+
@PathVariable Long id,
|
|
87
|
+
@Valid @RequestBody UpdateUserRequest request) {
|
|
88
|
+
return ResponseEntity.ok(new UserDTO());
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 출력: OpenAPI YAML
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
openapi: 3.0.0
|
|
97
|
+
info:
|
|
98
|
+
title: My API
|
|
99
|
+
version: 1.0.0
|
|
100
|
+
paths:
|
|
101
|
+
/api/users:
|
|
102
|
+
get:
|
|
103
|
+
summary: Search users
|
|
104
|
+
operationId: user_searchUsers
|
|
105
|
+
parameters:
|
|
106
|
+
- name: offset
|
|
107
|
+
in: query
|
|
108
|
+
schema:
|
|
109
|
+
type: integer
|
|
110
|
+
description: Pagination offset
|
|
111
|
+
- name: limit
|
|
112
|
+
in: query
|
|
113
|
+
schema:
|
|
114
|
+
type: integer
|
|
115
|
+
description: Pagination limit
|
|
116
|
+
- name: filter
|
|
117
|
+
in: query
|
|
118
|
+
schema:
|
|
119
|
+
$ref: '#/components/schemas/Filter'
|
|
120
|
+
- name: sort
|
|
121
|
+
in: query
|
|
122
|
+
schema:
|
|
123
|
+
$ref: '#/components/schemas/Sort'
|
|
124
|
+
responses:
|
|
125
|
+
'200':
|
|
126
|
+
description: Successful response
|
|
127
|
+
headers:
|
|
128
|
+
X-Total-Count:
|
|
129
|
+
schema:
|
|
130
|
+
type: integer
|
|
131
|
+
description: Total number of items
|
|
132
|
+
X-Offset:
|
|
133
|
+
schema:
|
|
134
|
+
type: integer
|
|
135
|
+
description: Current offset
|
|
136
|
+
X-Limit:
|
|
137
|
+
schema:
|
|
138
|
+
type: integer
|
|
139
|
+
description: Current limit
|
|
140
|
+
content:
|
|
141
|
+
application/json:
|
|
142
|
+
schema:
|
|
143
|
+
$ref: '#/components/schemas/PaginatedListUserDTO'
|
|
144
|
+
security:
|
|
145
|
+
- bearerAuth: []
|
|
146
|
+
x-required-roles:
|
|
147
|
+
- ROLE_USER
|
|
148
|
+
|
|
149
|
+
/api/users/{id}:
|
|
150
|
+
get:
|
|
151
|
+
summary: Get user by ID
|
|
152
|
+
operationId: user_getUserById
|
|
153
|
+
parameters:
|
|
154
|
+
- name: id
|
|
155
|
+
in: path
|
|
156
|
+
required: true
|
|
157
|
+
schema:
|
|
158
|
+
type: integer
|
|
159
|
+
format: int64
|
|
160
|
+
responses:
|
|
161
|
+
'200':
|
|
162
|
+
description: Successful response
|
|
163
|
+
content:
|
|
164
|
+
application/json:
|
|
165
|
+
schema:
|
|
166
|
+
$ref: '#/components/schemas/UserDTO'
|
|
167
|
+
security:
|
|
168
|
+
- bearerAuth: []
|
|
169
|
+
x-required-roles:
|
|
170
|
+
- ROLE_USER
|
|
171
|
+
- ROLE_ADMIN
|
|
172
|
+
|
|
173
|
+
put:
|
|
174
|
+
summary: Update user
|
|
175
|
+
operationId: user_updateUser
|
|
176
|
+
parameters:
|
|
177
|
+
- name: id
|
|
178
|
+
in: path
|
|
179
|
+
required: true
|
|
180
|
+
schema:
|
|
181
|
+
type: integer
|
|
182
|
+
format: int64
|
|
183
|
+
requestBody:
|
|
184
|
+
required: true
|
|
185
|
+
content:
|
|
186
|
+
application/json:
|
|
187
|
+
schema:
|
|
188
|
+
$ref: '#/components/schemas/UpdateUserRequest'
|
|
189
|
+
responses:
|
|
190
|
+
'200':
|
|
191
|
+
description: Successful response
|
|
192
|
+
content:
|
|
193
|
+
application/json:
|
|
194
|
+
schema:
|
|
195
|
+
$ref: '#/components/schemas/UserDTO'
|
|
196
|
+
security:
|
|
197
|
+
- bearerAuth: []
|
|
198
|
+
x-required-roles:
|
|
199
|
+
- ROLE_ADMIN
|
|
200
|
+
- ROLE_USER
|
|
201
|
+
x-security-expression: "hasRole('ADMIN') or (#id == authentication.principal.id and hasRole('USER'))"
|
|
202
|
+
|
|
203
|
+
components:
|
|
204
|
+
schemas:
|
|
205
|
+
UserDTO:
|
|
206
|
+
type: object
|
|
207
|
+
properties:
|
|
208
|
+
id:
|
|
209
|
+
type: integer
|
|
210
|
+
format: int64
|
|
211
|
+
firstName:
|
|
212
|
+
type: string
|
|
213
|
+
minLength: 2
|
|
214
|
+
maxLength: 50
|
|
215
|
+
lastName:
|
|
216
|
+
type: string
|
|
217
|
+
minLength: 2
|
|
218
|
+
maxLength: 50
|
|
219
|
+
email:
|
|
220
|
+
type: string
|
|
221
|
+
required:
|
|
222
|
+
- firstName
|
|
223
|
+
- lastName
|
|
224
|
+
- email
|
|
225
|
+
|
|
226
|
+
Filter:
|
|
227
|
+
type: object
|
|
228
|
+
description: Filter conditions
|
|
229
|
+
additionalProperties: true
|
|
230
|
+
|
|
231
|
+
Sort:
|
|
232
|
+
type: object
|
|
233
|
+
description: Sort conditions
|
|
234
|
+
properties:
|
|
235
|
+
field:
|
|
236
|
+
type: string
|
|
237
|
+
direction:
|
|
238
|
+
type: string
|
|
239
|
+
enum:
|
|
240
|
+
- ASC
|
|
241
|
+
- DESC
|
|
242
|
+
|
|
243
|
+
securitySchemes:
|
|
244
|
+
bearerAuth:
|
|
245
|
+
type: http
|
|
246
|
+
scheme: bearer
|
|
247
|
+
bearerFormat: JWT
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## 특수 타입 처리
|
|
251
|
+
|
|
252
|
+
### SearchParam
|
|
253
|
+
|
|
254
|
+
메서드 파라미터가 `SearchParam` 타입인 경우, 생성기가 자동으로 페이지네이션 쿼리 파라미터를 추가합니다:
|
|
255
|
+
|
|
256
|
+
- `offset` (integer) - 페이지네이션 오프셋
|
|
257
|
+
- `limit` (integer) - 페이지네이션 제한
|
|
258
|
+
- `filter` (object) - 필터 조건 (Filter 스키마 참조)
|
|
259
|
+
- `sort` (object) - 정렬 조건 (Sort 스키마 참조)
|
|
260
|
+
|
|
261
|
+
### PaginatedList<T>
|
|
262
|
+
|
|
263
|
+
메서드가 `PaginatedList<T>`를 반환하는 경우, 생성기가:
|
|
264
|
+
|
|
265
|
+
1. 응답에 페이지네이션 헤더를 추가합니다:
|
|
266
|
+
- `X-Total-Count` - 전체 항목 수
|
|
267
|
+
- `X-Offset` - 현재 오프셋
|
|
268
|
+
- `X-Limit` - 현재 제한
|
|
269
|
+
|
|
270
|
+
2. 다음 구조의 `PaginatedListT` 스키마를 생성합니다:
|
|
271
|
+
```yaml
|
|
272
|
+
PaginatedListUserDTO:
|
|
273
|
+
type: object
|
|
274
|
+
properties:
|
|
275
|
+
items:
|
|
276
|
+
type: array
|
|
277
|
+
items:
|
|
278
|
+
$ref: '#/components/schemas/UserDTO'
|
|
279
|
+
total:
|
|
280
|
+
type: integer
|
|
281
|
+
offset:
|
|
282
|
+
type: integer
|
|
283
|
+
limit:
|
|
284
|
+
type: integer
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### 보안 표현식
|
|
288
|
+
|
|
289
|
+
생성기가 `@PreAuthorize` 및 `@PostAuthorize`의 SpEL 표현식을 파싱합니다:
|
|
290
|
+
|
|
291
|
+
- `hasRole('ADMIN')` 같은 단순 표현식 → `x-required-roles: [ROLE_ADMIN]`
|
|
292
|
+
- `hasAnyRole('USER', 'ADMIN')`의 다중 역할 → `x-required-roles: [ROLE_USER, ROLE_ADMIN]`
|
|
293
|
+
- 복잡한 표현식은 `x-security-expression`에 보존
|
|
294
|
+
|
|
295
|
+
## 유효성 검사 어노테이션
|
|
296
|
+
|
|
297
|
+
생성기가 DTO 필드의 다음 유효성 검사 어노테이션을 인식합니다:
|
|
298
|
+
|
|
299
|
+
| 어노테이션 | OpenAPI 매핑 |
|
|
300
|
+
|------------|-----------------|
|
|
301
|
+
| `@NotNull`, `@NotBlank`, `@NotEmpty` | `required: true` |
|
|
302
|
+
| `@Size(min, max)` | `minLength`, `maxLength` |
|
|
303
|
+
| `@Min(value)` | `minimum` |
|
|
304
|
+
| `@Max(value)` | `maximum` |
|
|
305
|
+
| `@Pattern(regexp)` | `pattern` |
|
|
306
|
+
| `@Email` | `pattern` (이메일 정규식) |
|
|
307
|
+
|
|
308
|
+
## 프로젝트 구조
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
api-gen/
|
|
312
|
+
├── src/
|
|
313
|
+
│ ├── index.ts # CLI 진입점
|
|
314
|
+
│ ├── parser/
|
|
315
|
+
│ │ ├── javaParser.ts # Java 파일 파싱
|
|
316
|
+
│ │ └── astAnalyzer.ts # AST 분석 유틸리티
|
|
317
|
+
│ ├── analyzer/
|
|
318
|
+
│ │ ├── controllerAnalyzer.ts # Controller 감지
|
|
319
|
+
│ │ ├── parameterAnalyzer.ts # 파라미터 추출
|
|
320
|
+
│ │ ├── responseAnalyzer.ts # 응답 분석
|
|
321
|
+
│ │ ├── securityAnalyzer.ts # 보안 어노테이션 파싱
|
|
322
|
+
│ │ └── schemaGenerator.ts # DTO 스키마 생성
|
|
323
|
+
│ ├── generator/
|
|
324
|
+
│ │ └── openapiGenerator.ts # OpenAPI YAML 생성
|
|
325
|
+
│ └── types/
|
|
326
|
+
│ └── index.ts # TypeScript 타입 정의
|
|
327
|
+
├── examples/ # 샘플 Java 파일
|
|
328
|
+
├── package.json
|
|
329
|
+
├── tsconfig.json
|
|
330
|
+
└── README.md
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## 개발
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
# 의존성 설치
|
|
337
|
+
pnpm install
|
|
338
|
+
|
|
339
|
+
# 개발 모드로 실행
|
|
340
|
+
pnpm dev -- --source ./examples
|
|
341
|
+
|
|
342
|
+
# 프로덕션 빌드
|
|
343
|
+
pnpm build
|
|
344
|
+
|
|
345
|
+
# 테스트 실행 (예제 파일 사용)
|
|
346
|
+
pnpm test
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
# 실제 사용 예시
|
|
350
|
+
|
|
351
|
+
```shell
|
|
352
|
+
pnpm start --source /git/g1cloud2/g1cloud-sales --output ./out/g1cloud-sales.yaml --title "G1cloud Sales API" --api-version "2.0.0-SNAPSHOT"
|
|
353
|
+
|
|
354
|
+
npx @redocly/cli build-docs ./out/g1cloud-sales.yaml -o ./out/g1cloud-sales.html
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
```shell
|
|
358
|
+
pnpm start --source /git/g1cloud2/g1cloud-sales \
|
|
359
|
+
--api-source /git/g1cloud2/g1cloud-sales/g1cloud-sales-app \
|
|
360
|
+
--output ./out/g1cloud-sales-app.yaml \
|
|
361
|
+
--title "G1cloud Sales API" \
|
|
362
|
+
--api-version "2.0.0-SNAPSHOT"
|
|
363
|
+
|
|
364
|
+
npx @redocly/cli build-docs \
|
|
365
|
+
./out/g1cloud-sales-app.yaml \
|
|
366
|
+
-o ./out/g1cloud-sales-app.html
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
```shell
|
|
370
|
+
pnpm start --source /git/g1cloud2/g1cloud-sales-service \
|
|
371
|
+
--api-source /git/g1cloud2/g1cloud-sales-service/module-basis \
|
|
372
|
+
--output ./out/g1cloud-sales-basis.yaml \
|
|
373
|
+
--title "G1cloud Sales Basis API" \
|
|
374
|
+
--api-version "2.0.0-SNAPSHOT"
|
|
375
|
+
|
|
376
|
+
npx @redocly/cli build-docs \
|
|
377
|
+
./out/g1cloud-sales-basis.yaml \
|
|
378
|
+
-o ./out/g1cloud-sales-basis.html
|
|
379
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ControllerInfo, ProcessingContext } from '../types';
|
|
2
|
+
export declare class ControllerAnalyzer {
|
|
3
|
+
private context;
|
|
4
|
+
constructor(context: ProcessingContext);
|
|
5
|
+
/**
|
|
6
|
+
* Analyze all Java classes and extract controller information
|
|
7
|
+
* If apiSourcePath is specified, only analyze controllers within that directory
|
|
8
|
+
*/
|
|
9
|
+
analyzeControllers(): ControllerInfo[];
|
|
10
|
+
/**
|
|
11
|
+
* Analyze a single controller class
|
|
12
|
+
*/
|
|
13
|
+
private analyzeController;
|
|
14
|
+
/**
|
|
15
|
+
* Analyze a single endpoint method
|
|
16
|
+
*/
|
|
17
|
+
private analyzeEndpoint;
|
|
18
|
+
}
|
|
19
|
+
export declare function analyzeControllers(context: ProcessingContext): ControllerInfo[];
|
|
20
|
+
//# sourceMappingURL=controllerAnalyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controllerAnalyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/controllerAnalyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,cAAc,EAGd,iBAAiB,EAClB,MAAM,UAAU,CAAC;AAMlB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,OAAO,CAAoB;gBAEvB,OAAO,EAAE,iBAAiB;IAItC;;;OAGG;IACH,kBAAkB,IAAI,cAAc,EAAE;IAqBtC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA4BzB;;OAEG;IACH,OAAO,CAAC,eAAe;CAwCxB;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAG/E"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ControllerAnalyzer = void 0;
|
|
4
|
+
exports.analyzeControllers = analyzeControllers;
|
|
5
|
+
const astAnalyzer_1 = require("../parser/astAnalyzer");
|
|
6
|
+
const parameterAnalyzer_1 = require("./parameterAnalyzer");
|
|
7
|
+
const responseAnalyzer_1 = require("./responseAnalyzer");
|
|
8
|
+
const securityAnalyzer_1 = require("./securityAnalyzer");
|
|
9
|
+
class ControllerAnalyzer {
|
|
10
|
+
context;
|
|
11
|
+
constructor(context) {
|
|
12
|
+
this.context = context;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Analyze all Java classes and extract controller information
|
|
16
|
+
* If apiSourcePath is specified, only analyze controllers within that directory
|
|
17
|
+
*/
|
|
18
|
+
analyzeControllers() {
|
|
19
|
+
const controllers = [];
|
|
20
|
+
const { apiSourcePath } = this.context;
|
|
21
|
+
for (const [, javaClass] of this.context.javaClasses) {
|
|
22
|
+
// If apiSourcePath is specified, only include controllers from that directory
|
|
23
|
+
if (apiSourcePath && !javaClass.filePath.startsWith(apiSourcePath)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (astAnalyzer_1.ASTAnalyzer.isRestController(javaClass)) {
|
|
27
|
+
const controllerInfo = this.analyzeController(javaClass);
|
|
28
|
+
if (controllerInfo) {
|
|
29
|
+
controllers.push(controllerInfo);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return controllers;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Analyze a single controller class
|
|
37
|
+
*/
|
|
38
|
+
analyzeController(javaClass) {
|
|
39
|
+
const basePath = astAnalyzer_1.ASTAnalyzer.getClassBasePath(javaClass);
|
|
40
|
+
const endpoints = [];
|
|
41
|
+
console.log(` Found controller: ${javaClass.name} (base path: ${basePath || '/'})`);
|
|
42
|
+
for (const method of javaClass.methods) {
|
|
43
|
+
if (astAnalyzer_1.ASTAnalyzer.isEndpointMethod(method)) {
|
|
44
|
+
const endpoint = this.analyzeEndpoint(javaClass, method, basePath);
|
|
45
|
+
if (endpoint) {
|
|
46
|
+
endpoints.push(endpoint);
|
|
47
|
+
console.log(` - ${endpoint.method.toUpperCase()} ${endpoint.path}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (endpoints.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
className: javaClass.name,
|
|
56
|
+
basePath,
|
|
57
|
+
endpoints,
|
|
58
|
+
javaClass,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Analyze a single endpoint method
|
|
63
|
+
*/
|
|
64
|
+
analyzeEndpoint(javaClass, method, basePath) {
|
|
65
|
+
const httpMethod = astAnalyzer_1.ASTAnalyzer.getHttpMethod(method);
|
|
66
|
+
if (!httpMethod) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const methodPath = astAnalyzer_1.ASTAnalyzer.getMethodPath(method);
|
|
70
|
+
const fullPath = astAnalyzer_1.ASTAnalyzer.combinePaths(basePath, methodPath);
|
|
71
|
+
// Get @param descriptions from javadoc
|
|
72
|
+
const paramDescriptions = method.javadocInfo?.params || {};
|
|
73
|
+
// Analyze parameters (pass javadoc @param descriptions)
|
|
74
|
+
const { parameters, requestBody, hasSearchParam } = (0, parameterAnalyzer_1.analyzeParameters)(method, this.context, paramDescriptions);
|
|
75
|
+
// Analyze response
|
|
76
|
+
const { responses, hasPaginatedList, paginatedListItemType } = (0, responseAnalyzer_1.analyzeResponse)(method, this.context);
|
|
77
|
+
// Analyze security
|
|
78
|
+
const security = (0, securityAnalyzer_1.analyzeMethodSecurity)(method);
|
|
79
|
+
return {
|
|
80
|
+
path: fullPath,
|
|
81
|
+
method: httpMethod,
|
|
82
|
+
operationId: astAnalyzer_1.ASTAnalyzer.generateOperationId(javaClass.name, method.name),
|
|
83
|
+
summary: astAnalyzer_1.ASTAnalyzer.generateSummary(method.name),
|
|
84
|
+
description: method.javadoc,
|
|
85
|
+
returnDescription: method.javadocInfo?.returns,
|
|
86
|
+
parameters,
|
|
87
|
+
requestBody,
|
|
88
|
+
responses,
|
|
89
|
+
security,
|
|
90
|
+
hasSearchParam,
|
|
91
|
+
hasPaginatedList,
|
|
92
|
+
paginatedListItemType,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.ControllerAnalyzer = ControllerAnalyzer;
|
|
97
|
+
function analyzeControllers(context) {
|
|
98
|
+
const analyzer = new ControllerAnalyzer(context);
|
|
99
|
+
return analyzer.analyzeControllers();
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=controllerAnalyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controllerAnalyzer.js","sourceRoot":"","sources":["../../src/analyzer/controllerAnalyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { JavaMethod, ParameterInfo, RequestBodyInfo, ProcessingContext, SchemaInfo } from '../types';
|
|
2
|
+
interface ParameterAnalysisResult {
|
|
3
|
+
parameters: ParameterInfo[];
|
|
4
|
+
requestBody?: RequestBodyInfo;
|
|
5
|
+
hasSearchParam: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Analyze method parameters and extract OpenAPI parameter/request body info
|
|
9
|
+
* @param method The Java method to analyze
|
|
10
|
+
* @param context Processing context
|
|
11
|
+
* @param paramDescriptions Javadoc @param descriptions (param name -> description)
|
|
12
|
+
*/
|
|
13
|
+
export declare function analyzeParameters(method: JavaMethod, context: ProcessingContext, paramDescriptions?: Record<string, string>): ParameterAnalysisResult;
|
|
14
|
+
/**
|
|
15
|
+
* Create a schema for a Java type
|
|
16
|
+
*/
|
|
17
|
+
export declare function createSchemaForType(javaType: string, genericType: string | undefined, context: ProcessingContext): SchemaInfo;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=parameterAnalyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parameterAnalyzer.d.ts","sourceRoot":"","sources":["../../src/analyzer/parameterAnalyzer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAGV,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,UAAU,EACX,MAAM,UAAU,CAAC;AAGlB,UAAU,uBAAuB;IAC/B,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,iBAAiB,EAC1B,iBAAiB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAC7C,uBAAuB,CAiCzB;AAkID;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,iBAAiB,GACzB,UAAU,CA4CZ"}
|