CLI tool to generate OpenAPI v3 YAML from Spring Boot Java source code
npm install @g1cloud/api-genjava-parser를 사용하여 Java 파일에서 클래스 및 메서드 정보 추출
@RestController, @Controller 및 HTTP 매핑 어노테이션 감지
@RequestParam, @PathVariable, @RequestHeader, @RequestBody 지원
@PreAuthorize, @PostAuthorize, @Secured 어노테이션 분석
SearchParam: 페이지네이션 파라미터 자동 추가 (offset, limit, filter, sort)
PaginatedList: 페이지네이션 헤더 추가 (X-Total-Count, X-Offset, X-Limit)
bash
의존성 설치
pnpm install
프로젝트 빌드
pnpm build
또는 ts-node로 직접 실행
pnpm dev -- --source ./examples --output ./test-output.yaml
`
사용법
`bash
기본 사용법 - 단일 통합 YAML 파일 생성
npx spring-to-openapi --source ./src/main/java --output ./openapi.yaml
컨트롤러별 모드 - 각 컨트롤러마다 별도 YAML 파일 생성
npx spring-to-openapi --source ./src/main/java --out-dir ./api-docs
모든 옵션 사용
npx spring-to-openapi \
--source ./src/main/java \
--output ./openapi.yaml \
--title "My API" \
--api-version "1.0.0" \
--base-path "/api"
`
$3
| 옵션 | 단축 | 설명 | 기본값 |
|--------|-------|-------------|---------|
| --source | -s | Java 소스 코드 디렉토리 경로 | (필수) |
| --output | -o | 출력 YAML 파일 경로 (단일 통합 파일) | openapi.yaml |
| --out-dir | -d | 출력 디렉토리 경로 (컨트롤러별 YAML 생성) | (없음) |
| --title | -t | API 제목 | API Documentation |
| --api-version | | API 버전 | 1.0.0 |
| --base-path | -b | API 기본 경로 | (없음) |
> 참고: --output과 --out-dir은 상호 배타적입니다. 단일 통합 파일은 --output을, 컨트롤러별 YAML 파일 생성은 --out-dir을 사용하세요.
예제
$3
`java
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
@PreAuthorize("hasRole('USER')")
public ResponseEntity> searchUsers(SearchParam searchParam) {
return ResponseEntity.ok(new PaginatedList<>());
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
public ResponseEntity getUserById(@PathVariable Long id) {
return ResponseEntity.ok(new UserDTO());
}
@PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or (#id == authentication.principal.id and hasRole('USER'))")
public ResponseEntity updateUser(
@PathVariable Long id,
@Valid @RequestBody UpdateUserRequest request) {
return ResponseEntity.ok(new UserDTO());
}
}
`
$3
`yaml
openapi: 3.0.0
info:
title: My API
version: 1.0.0
paths:
/api/users:
get:
summary: Search users
operationId: user_searchUsers
parameters:
- name: offset
in: query
schema:
type: integer
description: Pagination offset
- name: limit
in: query
schema:
type: integer
description: Pagination limit
- name: filter
in: query
schema:
$ref: '#/components/schemas/Filter'
- name: sort
in: query
schema:
$ref: '#/components/schemas/Sort'
responses:
'200':
description: Successful response
headers:
X-Total-Count:
schema:
type: integer
description: Total number of items
X-Offset:
schema:
type: integer
description: Current offset
X-Limit:
schema:
type: integer
description: Current limit
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedListUserDTO'
security:
- bearerAuth: []
x-required-roles:
- ROLE_USER
/api/users/{id}:
get:
summary: Get user by ID
operationId: user_getUserById
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/UserDTO'
security:
- bearerAuth: []
x-required-roles:
- ROLE_USER
- ROLE_ADMIN
put:
summary: Update user
operationId: user_updateUser
parameters:
- name: id
in: path
required: true
schema:
type: integer
format: int64
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateUserRequest'
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/UserDTO'
security:
- bearerAuth: []
x-required-roles:
- ROLE_ADMIN
- ROLE_USER
x-security-expression: "hasRole('ADMIN') or (#id == authentication.principal.id and hasRole('USER'))"
components:
schemas:
UserDTO:
type: object
properties:
id:
type: integer
format: int64
firstName:
type: string
minLength: 2
maxLength: 50
lastName:
type: string
minLength: 2
maxLength: 50
email:
type: string
required:
- firstName
- lastName
- email
Filter:
type: object
description: Filter conditions
additionalProperties: true
Sort:
type: object
description: Sort conditions
properties:
field:
type: string
direction:
type: string
enum:
- ASC
- DESC
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
`
특수 타입 처리
$3
메서드 파라미터가 SearchParam 타입인 경우, 생성기가 자동으로 페이지네이션 쿼리 파라미터를 추가합니다:
- offset (integer) - 페이지네이션 오프셋
- limit (integer) - 페이지네이션 제한
- filter (object) - 필터 조건 (Filter 스키마 참조)
- sort (object) - 정렬 조건 (Sort 스키마 참조)
$3
메서드가 PaginatedList를 반환하는 경우, 생성기가:
1. 응답에 페이지네이션 헤더를 추가합니다:
- X-Total-Count - 전체 항목 수
- X-Offset - 현재 오프셋
- X-Limit - 현재 제한
2. 다음 구조의 PaginatedListT 스키마를 생성합니다:
`yaml
PaginatedListUserDTO:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/UserDTO'
total:
type: integer
offset:
type: integer
limit:
type: integer
`
$3
생성기가 @PreAuthorize 및 @PostAuthorize의 SpEL 표현식을 파싱합니다:
- hasRole('ADMIN') 같은 단순 표현식 → x-required-roles: [ROLE_ADMIN]
- hasAnyRole('USER', 'ADMIN')의 다중 역할 → x-required-roles: [ROLE_USER, ROLE_ADMIN]
- 복잡한 표현식은 x-security-expression에 보존
유효성 검사 어노테이션
생성기가 DTO 필드의 다음 유효성 검사 어노테이션을 인식합니다:
| 어노테이션 | OpenAPI 매핑 |
|------------|-----------------|
| @NotNull, @NotBlank, @NotEmpty | required: true |
| @Size(min, max) | minLength, maxLength |
| @Min(value) | minimum |
| @Max(value) | maximum |
| @Pattern(regexp) | pattern |
| @Email | pattern (이메일 정규식) |
프로젝트 구조
`
api-gen/
├── src/
│ ├── index.ts # CLI 진입점
│ ├── parser/
│ │ ├── javaParser.ts # Java 파일 파싱
│ │ └── astAnalyzer.ts # AST 분석 유틸리티
│ ├── analyzer/
│ │ ├── controllerAnalyzer.ts # Controller 감지
│ │ ├── parameterAnalyzer.ts # 파라미터 추출
│ │ ├── responseAnalyzer.ts # 응답 분석
│ │ ├── securityAnalyzer.ts # 보안 어노테이션 파싱
│ │ └── schemaGenerator.ts # DTO 스키마 생성
│ ├── generator/
│ │ └── openapiGenerator.ts # OpenAPI YAML 생성
│ └── types/
│ └── index.ts # TypeScript 타입 정의
├── examples/ # 샘플 Java 파일
├── package.json
├── tsconfig.json
└── README.md
`
개발
`bash
의존성 설치
pnpm install
개발 모드로 실행
pnpm dev -- --source ./examples
프로덕션 빌드
pnpm build
테스트 실행 (예제 파일 사용)
pnpm test
`
실제 사용 예시
`shell
pnpm start --source /git/g1cloud2/g1cloud-sales --output ./out/g1cloud-sales.yaml --title "G1cloud Sales API" --api-version "2.0.0-SNAPSHOT"
npx @redocly/cli build-docs ./out/g1cloud-sales.yaml -o ./out/g1cloud-sales.html
`
`shell
pnpm start --source /git/g1cloud2/g1cloud-sales \
--api-source /git/g1cloud2/g1cloud-sales/g1cloud-sales-app \
--output ./out/g1cloud-sales-app.yaml \
--title "G1cloud Sales API" \
--api-version "2.0.0-SNAPSHOT"
npx @redocly/cli build-docs \
./out/g1cloud-sales-app.yaml \
-o ./out/g1cloud-sales-app.html
`
`shell
pnpm start --source /git/g1cloud2/g1cloud-sales-service \
--api-source /git/g1cloud2/g1cloud-sales-service/module-basis \
--output ./out/g1cloud-sales-basis.yaml \
--title "G1cloud Sales Basis API" \
--api-version "2.0.0-SNAPSHOT"
npx @redocly/cli build-docs \
./out/g1cloud-sales-basis.yaml \
-o ./out/g1cloud-sales-basis.html
``