š go-apispec - Awesome Go Library for Generators

Generate OpenAPI 3.1 specs from Go source code via static analysis with automatic framework detection
Detailed Description of go-apispec
go-apispec: Generate OpenAPI from Go code
go-apispec analyzes your Go source code and generates an OpenAPI 3.1 spec (YAML or JSON). Point it at your module ā it detects the framework, follows the call graph from routes to handlers, and infers request/response types from real code.
Quick Start
# Install
go install github.com/antst/go-apispec/cmd/apispec@latest
# Generate (auto-detects framework)
apispec --dir ./your-project --output openapi.yaml
That's it. The tool detects your framework, finds all routes, resolves handler types, and writes the spec.
Features
Framework Support
| Framework | Routes | Params | Request Body | Responses | Mounting/Groups |
|---|---|---|---|---|---|
| Chi | Full | chi.URLParam, render pkg | json.Decode, render.DecodeJSON | json.Encode, render.JSON, w.Write | Mount, Group |
| Gin | Full | c.Param, c.Query | ShouldBindJSON, BindJSON | c.JSON, c.String, c.Data | Group |
| Echo | Full | c.Param, c.QueryParam | c.Bind | c.JSON, c.String, c.Blob | Group |
| Fiber | Full | c.Params, c.Query | c.BodyParser | c.JSON, c.Status().JSON | Mount, Group |
| Gorilla Mux | Full | Path template {id} | json.Decode | json.Encode, w.Write | PathPrefix, Subrouter |
| net/http | Basic | Path template | json.Decode | json.Encode, w.Write, http.Error | Nested ServeMux |
Projects using multiple frameworks simultaneously are fully supported ā all routes from all detected frameworks appear in the spec.
All frameworks also detect fmt.Fprintf, io.Copy, and io.WriteString as response writes.
Analysis Capabilities
Response Detection
- Content-Type inference from
w.Header().Set("Content-Type", "image/png") WriteHeader(201)+json.Encode(user)merged into a single 201 response with schema- Status code variable resolution:
status := http.StatusCreated; w.WriteHeader(status)ā 201 - Cross-function status codes:
w.WriteHeader(getStatus())wheregetStatus()returns a constant - Multiple response types for the same status code ā
oneOfschema []byteresponses ātype: string, format: binary- Bodyless status codes (1xx, 204, 304) never get body schemas (per RFC 7231)
- Implicit 200 for handlers that write a body without explicit
WriteHeader
Type Resolution
- Generic struct instantiation:
APIResponse[User]ā schema withData: $ref User - Interface resolution: handlers registered via interface ā concrete implementation schemas
- Conditional HTTP methods via CFG:
switch r.Method { case "GET": ... case "POST": ... }ā separate operations - Route path variables:
path := "/users"; r.GET(path, h)ā resolves variable to literal path - Decode receiver tracing:
json.NewDecoder(file).Decode(&cfg)not misclassified as request body - io.Copy source tracing:
io.Copy(w, strings.NewReader(...))ātype: string;io.Copy(w, file)āformat: binary
Schema Inference
- Required fields from
json:",omitempty"absence andbinding:"required"tags - Validator
divetag:validate:"dive,email"on[]stringā items schema hasformat: email Mux.Vars()map index expressions ā path parameter names- Type mappings for
time.Time,uuid.UUID, and custom types
Output Quality
- Deterministic YAML/JSON ā sorted map keys, identical output across runs, safe for CI diffing
- Short names by default ā
DocumentHandler.GetContentinstead ofgithub.com/org/.../http.Deps.DocumentHandler.GetContent - Config merging ā
--configextends auto-detected framework defaults instead of replacing them
Call Graph Visualization
Interactive Cytoscape.js diagrams with:
- Hierarchical tree layout with zoom, pan, and click-to-highlight
- CFG branch coloring: green (if-then), red dashed (if-else), purple (switch-case)
- Branch labels showing case values (e.g., "GET", "POST")
- Paginated mode for large graphs (1000+ edges)
- PNG/SVG export
apispec --dir ./my-project --output openapi.yaml --diagram diagram.html
Usage
# Basic generation
apispec --dir ./my-project --output openapi.yaml
# With custom config
apispec --dir ./my-project --config apispec.yaml --output openapi.yaml
# Legacy naming (fully-qualified operationIds and schema names)
apispec --dir ./my-project --output openapi.yaml --short-names=false
# With call graph diagram
apispec --dir ./my-project --output openapi.yaml --diagram diagram.html
# Skip CGO packages
apispec --dir ./my-project --output openapi.yaml --skip-cgo
# Tune limits for large codebases
apispec --dir ./my-project --output openapi.yaml --max-nodes 100000 --max-recursion-depth 15
# Performance profiling
apispec --dir ./my-project --output openapi.yaml --cpu-profile --mem-profile
Key Flags
| Flag | Short | Default | Description |
|---|---|---|---|
--output | -o | openapi.json | Output file (.yaml/.json) |
--dir | -d | . | Project directory |
--config | -c | ā | Custom YAML config |
--short-names | ā | true | Strip module paths from names |
--diagram | -g | ā | Save call graph as HTML |
--title | -t | Generated API | API title |
--api-version | -v | 1.0.0 | API version |
--skip-cgo | ā | true | Skip CGO packages |
--max-nodes | -mn | 50000 | Max call graph nodes |
--max-recursion-depth | -mrd | 10 | Max recursion depth |
--verbose | -vb | false | Verbose output |
Full flag list: apispec --help
Programmatic Usage
import (
"os"
"github.com/antst/go-apispec/generator"
"github.com/antst/go-apispec/spec"
"gopkg.in/yaml.v3"
)
func main() {
cfg := spec.DefaultChiConfig() // or DefaultGinConfig, DefaultEchoConfig, etc.
gen := generator.NewGenerator(cfg)
openapi, err := gen.GenerateFromDirectory("./your-project")
if err != nil { panic(err) }
data, _ := yaml.Marshal(openapi)
os.WriteFile("openapi.yaml", data, 0644)
}
Configuration
Auto-detection works for most projects. For custom behavior, create apispec.yaml:
info:
title: My API
version: 2.0.0
shortNames: true # false for legacy fully-qualified names
framework:
routePatterns:
- callRegex: ^(?i)(GET|POST|PUT|DELETE|PATCH)$
recvTypeRegex: ^github\.com/gin-gonic/gin\.\*(Engine|RouterGroup)$
handlerArgIndex: 1
methodFromCall: true
pathFromArg: true
handlerFromArg: true
typeMapping:
- goType: time.Time
openapiType: { type: string, format: date-time }
- goType: uuid.UUID
openapiType: { type: string, format: uuid }
externalTypes:
- name: github.com/gin-gonic/gin.H
openapiType: { type: object, additionalProperties: true }
Full configuration examples for each framework: see the Default*Config() functions in internal/spec/config.go for the built-in patterns.
How It Works
Go source ā Package loading & type-checking ā Framework detection
ā AST traversal ā Call graph + CFG construction ā Pattern matching
ā OpenAPI mapping ā YAML/JSON output
- Loads and type-checks all Go packages in the module
- Detects web frameworks from imports (supports multiple frameworks simultaneously)
- Builds a call graph from router registrations to handlers
- Builds a control-flow graph (CFG) via
golang.org/x/tools/go/cfgfor branch analysis - Matches route, request, response, and parameter patterns against the call graph
- Resolves conditional methods, generic types, and interface implementations using CFG and type analysis
- Maps Go types to OpenAPI schemas (structs, enums, aliases, generics, validators)
- Serializes with sorted keys for deterministic output
Known Limitations
These are inherent to static analysis ā analyzing code without executing it:
| Limitation | Example | What Happens |
|---|---|---|
| Reflection-based routing | Routes registered via reflect.Value.Call | Not visible in static analysis |
| Computed paths | r.GET("/api/" + version, handler) | String concatenation not evaluated; only literal and variable-assigned paths resolved |
| Complex cross-function values | func compute() int { return a + b }; WriteHeader(compute()) | Only functions with a single constant return are resolved; computed values are not traced |
Interactive Diagram Server
# Build and start
go build -o apidiag ./cmd/apidiag
./apidiag --dir ./my-project --port 8080
# Open http://localhost:8080
Provides a web UI for exploring call graphs with filtering, pagination, and export. See cmd/apidiag/README.md.
Development
# Build
make build
# Test
make test
# Lint
make lint
# Coverage
make coverage
# Update golden files after intentional output changes
# (generates to temp, shows diff, requires confirmation)
go test ./internal/engine/ -run TestUpdateGolden -v
Project Structure
go-apispec/
āāā cmd/apispec/ CLI entry point
āāā cmd/apidiag/ Interactive diagram server
āāā generator/ High-level generator API
āāā spec/ Public types (re-exports from internal/spec)
āāā internal/
ā āāā core/ Framework detection
ā āāā engine/ Generation engine
ā āāā metadata/ AST analysis and metadata extraction
ā āāā spec/ OpenAPI mapping, patterns, schemas
āāā pkg/patterns/ Gitignore-style pattern matching
āāā testdata/ Framework fixtures + golden files
Golden File Tests
Every testdata/ directory has expected_openapi.json (short names) and expected_openapi_legacy.json (fully-qualified). Golden files use relative paths only ā no machine-specific absolute paths.
One code path: generateGoldenSpec() is the single function used by both comparison and generation. Tests never overwrite golden files.
# Compare (runs in CI ā fails on mismatch):
go test ./internal/engine/ -run TestGolden -v
# Update after intentional changes (explicit, never automatic):
go test ./internal/engine/ -run TestUpdateGolden -v
Contributing
See CONTRIBUTING.md. In short:
- Fork, branch, make changes
- Add tests (
make test) - Run lint (
make lint) - Open a PR
License
Apache License 2.0 ā see LICENSE.
Originally forked from apispec by Ehab Terra.