u/Apprehensive-Ebb2263

I built a tool that generates OpenAPI specs from Go code — no annotations needed
🔥 Hot ▲ 59 r/golang

I built a tool that generates OpenAPI specs from Go code — no annotations needed

I've been working on go-apispec, a CLI tool that generates OpenAPI 3.1 specs from Go source code using static analysis. No // @Summary comments, no struct tags, no code changes. Just point it at your project:

go install github.com/antst/go-apispec/cmd/apispec@latest
apispec --dir ./your-project --output openapi.yaml

It detects your framework (Chi, Gin, Echo, Fiber, Gorilla Mux, net/http), builds a call graph from route registrations to handlers, and traces through your code to figure out what goes in and what comes out.

Concrete example. Given this handler:

func CreateUser(w http.ResponseWriter, r *http.Request) {
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(ErrorResponse{Error: "invalid body"})
        return
    }
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

It produces:

/users:
  post:
    requestBody:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/User'
    responses:
      "201":
        description: Created
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
      "400":
        description: Bad Request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ErrorResponse'

Both status codes, both response types, the request body — all inferred from the code. Fields without omitempty are marked required in the schema.

Some of the harder problems it solves:

  • switch r.Method { case "GET": ... case "POST": ... } → produces separate operations per method. This uses control-flow graph analysis via golang.org/x/tools/go/cfg to understand which code runs under which branch.
  • APIResponse[User] → instantiates the generic struct with concrete types in the schema.
  • Handlers registered via interfaces → traces to the concrete implementation to find response types.
  • w.Header().Set("Content-Type", "image/png") → that endpoint's response uses image/png, not the default application/json.
  • Multiple frameworks in one project (e.g., Chi on :8080, Gin on :9090) → all routes from all frameworks appear in the spec.

What it can't do (static analysis limitations): reflection-based routing, computed string concatenation for paths ("/api/" + version), complex arithmetic across functions for status codes. These require runtime information.

Output is deterministic (sorted keys), so you can commit the spec and diff it in CI.

Background: This started as a fork of apispec by Ehab Terra, which provided the foundational architecture — the AST traversal approach, call graph construction, and the pattern-matching concept for framework detection. I've since rewritten most of the internals: type resolution pipeline, schema generation, CFG integration, generic/interface support, deterministic output, and the test infrastructure. But the original design shaped where this ended up, and I'm grateful for that starting point.

GitHub: https://github.com/antst/go-apispec

Try it: go install github.com/antst/go-apispec/cmd/apispec@latest && apispec --dir . --output openapi.yaml

Would love to hear if you try it on a real project — especially cases where it gets something wrong or misses a pattern. That's the most useful feedback.

u/Apprehensive-Ebb2263 — 18 hours ago