šŸ“š go-apispec - Awesome Go Library for Generators

Go Gopher mascot for go-apispec

Generate OpenAPI 3.1 specs from Go source code via static analysis with automatic framework detection

šŸ·ļø Generators
šŸ“‚ Generators
⭐ 0 stars
View on GitHub šŸ”—

Detailed Description of go-apispec

go-apispec: Generate OpenAPI from Go code

CI Release Coverage Go Version License Go Reference

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

FrameworkRoutesParamsRequest BodyResponsesMounting/Groups
ChiFullchi.URLParam, render pkgjson.Decode, render.DecodeJSONjson.Encode, render.JSON, w.WriteMount, Group
GinFullc.Param, c.QueryShouldBindJSON, BindJSONc.JSON, c.String, c.DataGroup
EchoFullc.Param, c.QueryParamc.Bindc.JSON, c.String, c.BlobGroup
FiberFullc.Params, c.Queryc.BodyParserc.JSON, c.Status().JSONMount, Group
Gorilla MuxFullPath template {id}json.Decodejson.Encode, w.WritePathPrefix, Subrouter
net/httpBasicPath templatejson.Decodejson.Encode, w.Write, http.ErrorNested 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()) where getStatus() returns a constant
  • Multiple response types for the same status code → oneOf schema
  • []byte responses → 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 with Data: $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 and binding:"required" tags
  • Validator dive tag: validate:"dive,email" on []string → items schema has format: 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.GetContent instead of github.com/org/.../http.Deps.DocumentHandler.GetContent
  • Config merging — --config extends 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

FlagShortDefaultDescription
--output-oopenapi.jsonOutput file (.yaml/.json)
--dir-d.Project directory
--config-c—Custom YAML config
--short-names—trueStrip module paths from names
--diagram-g—Save call graph as HTML
--title-tGenerated APIAPI title
--api-version-v1.0.0API version
--skip-cgo—trueSkip CGO packages
--max-nodes-mn50000Max call graph nodes
--max-recursion-depth-mrd10Max recursion depth
--verbose-vbfalseVerbose 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
  1. Loads and type-checks all Go packages in the module
  2. Detects web frameworks from imports (supports multiple frameworks simultaneously)
  3. Builds a call graph from router registrations to handlers
  4. Builds a control-flow graph (CFG) via golang.org/x/tools/go/cfg for branch analysis
  5. Matches route, request, response, and parameter patterns against the call graph
  6. Resolves conditional methods, generic types, and interface implementations using CFG and type analysis
  7. Maps Go types to OpenAPI schemas (structs, enums, aliases, generics, validators)
  8. Serializes with sorted keys for deterministic output

Known Limitations

These are inherent to static analysis — analyzing code without executing it:

LimitationExampleWhat Happens
Reflection-based routingRoutes registered via reflect.Value.CallNot visible in static analysis
Computed pathsr.GET("/api/" + version, handler)String concatenation not evaluated; only literal and variable-assigned paths resolved
Complex cross-function valuesfunc 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:

  1. Fork, branch, make changes
  2. Add tests (make test)
  3. Run lint (make lint)
  4. Open a PR

License

Apache License 2.0 — see LICENSE.

Originally forked from apispec by Ehab Terra.