Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
585fda7
docs: add Gin framework support design
spencercjh Mar 7, 2026
b56bc89
chore(gin): create package structure with stubs
spencercjh Mar 7, 2026
f7f073a
feat(gin): add Gin project types and data structures
spencercjh Mar 7, 2026
884318b
feat(gin): implement Detector with go.mod parsing
spencercjh Mar 7, 2026
d13af13
feat(gin): implement no-op Patcher for Gin projects
spencercjh Mar 7, 2026
19ea3a8
feat(gin): implement AST parser for route extraction
spencercjh Mar 7, 2026
a2e51eb
feat(gin): implement Handler analyzer for param and response extraction
spencercjh Mar 7, 2026
e32da78
feat(gin): implement Schema extractor for Go struct to OpenAPI conver…
spencercjh Mar 7, 2026
25ceced
feat(gin): implement Generator with OpenAPI document building
spencercjh Mar 7, 2026
ea89c67
feat(extractor): register Gin extractor in builtin registry
spencercjh Mar 7, 2026
b91136a
test(integration): add Gin demo project and e2e test
spencercjh Mar 7, 2026
6e37b27
docs: update README and AGENTS with Gin support documentation
spencercjh Mar 7, 2026
5307630
fix(gin): fix path parameter format and schema reference handling
spencercjh Mar 7, 2026
a36117a
feat(gin): add support for more Gin binding and response methods
spencercjh Mar 7, 2026
d8bba36
fix(gin): fix body type extraction to use actual type name instead of…
spencercjh Mar 7, 2026
0eecf1b
chore: remove accidentally generated openapi.yaml
spencercjh Mar 7, 2026
863b796
test(integration): enhance gin_demo_test.go with comprehensive valida…
spencercjh Mar 7, 2026
10e8595
feat(gin): enhance type extraction for wrapped response types and nes…
spencercjh Mar 7, 2026
ae3751f
style(gin): fix all lint issues (40 -> 0)
spencercjh Mar 8, 2026
af8b87a
feat(gin): add comprehensive slog logging to gin extractor
spencercjh Mar 8, 2026
1e3663b
refactor(gin): pass ctx to slog calls in functions that accept context
spencercjh Mar 8, 2026
f4eb3a7
docs: update Gin framework documentation
spencercjh Mar 8, 2026
231abe7
ci: use go install instead of golangci-lint-action
spencercjh Mar 8, 2026
4a90f53
fix(gin): extract validation rule strings to constants
spencercjh Mar 8, 2026
a878d6b
ci: update golangci-lint setup to use action with specified version
spencercjh Mar 8, 2026
b126ca8
refactor(gin): use http.MethodXXX constants instead of string literals
spencercjh Mar 8, 2026
97c0ce3
style(gin): add nosec comment for prealloc linter
spencercjh Mar 8, 2026
3ef926d
ci: remove unnecessary args from golangci-lint action
spencercjh Mar 8, 2026
60557f7
ci: update golangci-lint action to enable verification and adjust cac…
spencercjh Mar 8, 2026
57b981a
ci: fix .golangci.yml schema validation errors
spencercjh Mar 8, 2026
7938e29
ci: update golangci-lint action to v9 and enable verification
spencercjh Mar 8, 2026
1c72883
ci: update golangci-lint in ci to latest version
spencercjh Mar 8, 2026
e8a58ef
fix(gin): address code review feedback from PR #13
spencercjh Mar 8, 2026
f8484b5
fix(gin): address more code review feedback from PR #13
spencercjh Mar 8, 2026
92b3ec5
fix(gin): address additional code review feedback from PR #13
spencercjh Mar 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ jobs:
run: git diff --exit-code -- go.mod go.sum

- name: Setup golangci-lint
uses: golangci/golangci-lint-action@v7
uses: golangci/golangci-lint-action@v9
with:
version: v2.10
args: --help
verify: false
version: latest
Comment thread
spencercjh marked this conversation as resolved.
verify: true
install-only: true

- name: Format check
run: make fmt
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,5 @@ output/
integration-tests/gozero-demo/openapi.yaml
integration-tests/gozero-demo/openapi.json

integration-tests/gin-demo/openapi.yaml
/openapi/openapi.yaml
5 changes: 2 additions & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ linters:
msg: use log/slog for logging
- pattern: fmt\.Fprint(os\.(Stdout|Stderr),.*)$
msg: use log/slog for logging
# Exclude test files from this check
exclude_godoc_examples: true

exclusions:
generated: lax
Expand Down Expand Up @@ -179,7 +177,8 @@ formatters:
extra-rules: true

goimports:
local-prefixes: github.com/spencercjh/spec-forge
local-prefixes:
- github.com/spencercjh/spec-forge

exclusions:
generated: lax
Expand Down
61 changes: 51 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,28 @@ internal/
├── executor/ # Shell command execution with timeout
├── extractor/ # OpenAPI spec extraction
│ ├── types.go # GenerateOptions, GenerateResult, etc.
│ ├── builtin/ # Built-in extractor registry
│ ├── spring/ # Spring Boot implementation
│ ├── spring/ # Spring Boot specific implementation
│ │ ├── detector.go # Project type detection (Maven/Gradle)
│ │ ├── patcher.go # springdoc dependency injection
│ │ ├── generator.go # Maven/Gradle command execution
│ │ ├── maven.go # POM parsing, spring-boot plugin config
│ │ └── gradle.go # build.gradle parsing
│ ├── gozero/ # go-zero implementation
│ ├── gozero/ # go-zero framework support
│ │ ├── detector.go # go.mod parsing, dependency detection
│ │ ├── patcher.go # go-swagger installation check
│ │ └── generator.go # goctl command execution
│ └── grpcprotoc/ # gRPC-protoc implementation
│ ├── detector.go # .proto file detection, buf.yaml rejection
│ ├── patcher.go # protoc tools check
│ ├── generator.go # protoc command execution
│ └── grpcprotoc.go # Info struct with ProtoFiles, ServiceProtoFiles
│ ├── grpcprotoc/ # gRPC-protoc implementation
│ │ ├── detector.go # .proto file detection, buf.yaml rejection
│ │ ├── patcher.go # protoc tools check
│ │ ├── generator.go # protoc command execution
│ │ └── grpcprotoc.go # Info struct with ProtoFiles, ServiceProtoFiles
│ └── gin/ # Gin framework support (AST-based)
│ ├── detector.go # go.mod parsing for gin dependency
│ ├── patcher.go # No-op (no patching needed)
│ ├── generator.go # AST-based OpenAPI generation
│ ├── ast_parser.go # Go AST parsing for routes
│ ├── handler_analyzer.go # Handler function analysis
│ └── schema_extractor.go # Go struct to OpenAPI schema
├── validator/ # kin-openapi validation
├── enricher/ # LLM-based description enrichment
│ ├── enricher.go # Main enricher interface
Expand All @@ -84,6 +90,7 @@ internal/

```
Spring Project → springdoc plugin → openapi.json → Enricher (LLM) → openapi.yaml
Gin Project → AST Parser → OpenAPI Generator → Enricher (LLM) → openapi.yaml
```

## Critical Constraints
Expand Down Expand Up @@ -168,7 +175,7 @@ API keys should be provided via environment variables:

## Functional Testing with Example Projects

The `integration-tests/` directory contains example Spring Boot projects for testing:
The `integration-tests/` directory contains example projects for testing:

```
integration-tests/
Expand All @@ -177,7 +184,41 @@ integration-tests/
├── maven-springboot-openapi-demo/ # Maven-based Spring Boot project
├── gradle-springboot-openapi-demo/ # Gradle-based Spring Boot project
├── maven-multi-module-demo/ # Multi-module Maven project
└── gradle-multi-module-demo/ # Multi-module Gradle project
├── gradle-multi-module-demo/ # Multi-module Gradle project
└── gin-demo/ # Gin framework project
```

### Gin Framework Development

The Gin extractor is located in `internal/extractor/gin/`.

**Architecture:**
- Uses Go AST (go/ast, go/parser) for static analysis
- No runtime execution required (unlike Spring Boot)
- Patcher is a no-op (no dependencies to install)

**Key Components:**
- `ASTParser` - Parses Go files and extracts routes
- `HandlerAnalyzer` - Analyzes handler functions for params/responses
- `SchemaExtractor` - Converts Go structs to OpenAPI schemas

**Testing:**
```bash
# Run Gin-specific tests
go test -v ./internal/extractor/gin/...

# Run Gin e2e test (requires go.mod with gin dependency)
go test -v -tags=e2e ./integration-tests/... -run TestGinDemo
```

**Example Usage:**
```bash
# Generate OpenAPI spec from a Gin project
spec-forge generate ./integration-tests/gin-demo

# Generate with AI enrichment
LLM_API_KEY="your-key" spec-forge generate ./integration-tests/gin-demo \
--enrich --language zh
```

### Running E2E Tests
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,41 @@ LLM_API_KEY="sk-xxx" spec-forge enrich ./openapi.json \
--model deepseek-chat
```

### Framework-Specific Usage

#### Gin Framework

For Gin projects, spec-forge uses static AST analysis (no runtime required):

```bash
# Basic generation from a Gin project
cd my-gin-project
spec-forge generate . -o ./openapi

# Generate with AI enrichment
LLM_API_KEY="sk-xxx" spec-forge generate . \
--enrich \
--provider custom \
--model deepseek-chat \
--language zh

# Verbose mode to see extraction details
spec-forge generate . -v
```

Supported Gin patterns:
- Direct route registration: `r.GET("/users", handler)`
- Route groups: `api := r.Group("/api")`
- Middleware chains: `r.Use(auth).GET("/protected", handler)`
- Parameter binding: `c.Param()`, `c.Query()`, `c.ShouldBindJSON()`
- Response types: extracted from `c.JSON()` calls with type inference

## Supported Frameworks

| Framework | Language | Status |
|----------------------------------------------------------------------------------------------------------------------------------------------|----------------|----------------|
| [Spring Boot](https://springdoc.org/#plugins) | Java | ✅ Supported |
| [Gin](https://gin-gonic.com/) | Go | ✅ Supported |
| [go-zero](https://go-zero.dev/reference/cli-guide/swagger/) | Go | ✅ Supported |
| [gRPC (protoc)](https://github.com/sudorandom/protoc-gen-connect-openapi) | Multi-language | ✅ Supported |
| [Hertz](https://github.com/hertz-contrib/swagger-generate/tree/main/protoc-gen-http-swagger) | Go | 🚧 Coming soon |
Expand Down
15 changes: 10 additions & 5 deletions cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,17 @@ func runGenerate(cmd *cobra.Command, args []string) error { //nolint:gocyclo //
slog.InfoContext(ctx, "Publisher output", "message", pubResult.Message)
}
} else if outputDir != "" {
// Skip publish: just copy to output directory
if err := copySpecToOutput(genResult.SpecFilePath, outputDir); err != nil {
return errWrap("failed to copy spec to output directory", err)
// Skip publish: just copy to output directory if needed
genDir := filepath.Dir(genResult.SpecFilePath)
if genDir != outputDir {
Comment thread
spencercjh marked this conversation as resolved.
if err := copySpecToOutput(genResult.SpecFilePath, outputDir); err != nil {
return errWrap("failed to copy spec to output directory", err)
}
finalSpecPath := filepath.Join(outputDir, filepath.Base(genResult.SpecFilePath))
slog.InfoContext(ctx, "Spec copied to output directory (publish skipped)", "path", finalSpecPath)
} else {
slog.InfoContext(ctx, "Spec already in output directory", "path", genResult.SpecFilePath)
Comment thread
spencercjh marked this conversation as resolved.
Comment thread
spencercjh marked this conversation as resolved.
}
finalSpecPath := filepath.Join(outputDir, filepath.Base(genResult.SpecFilePath))
slog.InfoContext(ctx, "Spec copied to output directory (publish skipped)", "path", finalSpecPath)
}

// Step 8: Output final result
Expand Down
Loading