Generate a complete OpenAPI document (3.1 and 3.0) for Go + Fiber services through static analysis.
No annotations, no // @Summary comments, no code generation, no changes to your handlers. Point fibgen at an existing project and get a spec with paths, parameters, request bodies, per-status responses, and fully resolved type schemas ($ref, enums, nullables).
Supports Fiber v2 and v3.
Every Fiber handler is just func(c fiber.Ctx) error (v3) or func(c *fiber.Ctx) error (v2). The request and response types exist only as local variables inside the body (c.Bind().Body(&dto), c.JSON(user)). The signature is opaque, so runtime reflection yields nothing useful.
The only way to get "fully automatic, on unmodified code" is to analyze the code itself with go/packages + go/types: find the route registrations, follow them into the handler body, and recover the types from the c.* calls.
go install github.com/nyawave/fibgen/cmd/fibgen@latestOr build from source:
git clone https://github.com/nyawave/fibgen
cd fibgen
./build.sh # -> ./bin/fibgen (build.bat on Windows)# from the root of the project you want to document
fibgen ./... > openapi.yaml
# explicit options
fibgen -dir ./myservice -o openapi.json --openapi-version 3.0 ./...| Flag | Default | Description |
|---|---|---|
-o |
stdout | output file; extension (.yaml / .json) selects the format |
-format |
from -o, else yaml |
output format: yaml | json |
-dir |
. |
project directory to analyze |
--openapi-version |
3.1 |
spec version: 3.1 or 3.0 |
--title |
module name | API title |
--doc-version |
0.1.0 |
API document version |
--quiet |
false |
suppress diagnostics on stderr |
Anything the analyzer cannot resolve statically is reported on stderr as note: ... (suppress with --quiet).
app.Get/Post/Put/Delete/Patch/Head/Options/Connect/Trace,All,Add(method, path, ...)Group(prefix)with arbitrary nesting; prefixes are resolved through variable assignments and through routers passed as function parameters- Fiber paths → OpenAPI paths:
:id→{id},:id?(optional),:id<int>(constraint maps to a type),*/+(wildcards)
- named functions, controller method values (
ctrl.Create), inline closures, function-typed variables, and statically resolvable handler factories (makeHandler(service)) - project helper functions that receive the active Fiber context; actual arguments are propagated so
respond(c, value)retains the concrete response type
- body:
c.BodyParser(&dto)(v2),c.Bind().Body(&dto)/.JSON(&dto)(v3) - query:
c.QueryParser(&s)/c.Bind().Query(&s)(struct expansion viaquery:tags), andc.Query/QueryInt/QueryBool/QueryFloat("name") - path: from the route template, with the type upgraded by
c.ParamsInt("id") - header:
c.ReqHeaderParser(&s),c.Bind().Header(&s),c.Get("X-...")
c.JSON(x)/c.XML(x)→ schema ofx, status 200c.Status(code).JSON(x)→ status fromcode(literal orfiber.StatusCreated)c.SendStatus(code),c.SendString(...),c.Status(code).Send...,c.Redirect(...)- multiple response shapes per status — when a handler returns different types depending on a flag (e.g. a short vs. full object on
?short=true), the alternatives are collected into aoneOf - ad-hoc JSON objects —
c.JSON(fiber.Map{...})/map[string]any{...}are expanded into anobjectwith concrete properties; value types are inferred from the expressions, and nested literals and$refs are supported (e.g.{"message": string, "data": SomeStruct})
- structs → reusable components (
$ref), honoringjson:tags,omitempty(drivesrequired), and embedded/promoted fields - pointers → nullable (
oneOf+nullin 3.1,nullable: truein 3.0) - slices/arrays →
array, maps →additionalProperties,[]byte→string/byte - named types backed by a group of constants →
enum - well-known types:
time.Time→date-time,time.Duration,encoding/json.RawMessage,uuid.UUID, MongoDBprimitive.ObjectID/DateTime,interface{}/any→ free-form,fiber.Map→ object - cross-package name collisions are disambiguated by package name (e.g.
models.Uservs.dto.User)
A handler like this:
func (h *UserHandler) Create(c fiber.Ctx) error {
var req CreateUserRequest
if err := c.Bind().Body(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid body"})
}
user := h.svc.Create(req)
return c.Status(fiber.StatusCreated).JSON(user)
}…with the route app.Group("/api/v1").Group("/users").Post("/", h.Create) produces:
/api/v1/users:
post:
tags: [users]
operationId: post_api_v1_users
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
"400":
description: Bad Request
content:
application/json:
schema:
type: object
properties:
error: { type: string }
required: [error]Two ready-to-run example projects (Fiber v2 and v3) live under examples/.
cmd/fibgen CLI entrypoint
internal/analyzer load packages, find routes, resolve group prefixes, analyze handler bodies
internal/schema go/types -> version-neutral JSON Schema (+ component registry)
internal/model framework-neutral API model (routes, params, responses)
internal/openapi render the model to OpenAPI 3.0/3.1, serialize to YAML/JSON
internal/oapi insertion-ordered map for deterministic, diff-friendly output
examples/v2, v3 example Fiber projects used by the test suite
Pipeline: analyzer.Analyze → model.API → openapi.Builder.Build → oapi.OM → openapi.Marshal.
./build.sh # current platform -> ./bin/fibgen
./build.sh all # cross-compile linux/macos/windows (amd64 + arm64)
./build.sh install # go install into $GOBIN / $GOPATH/binWindows: use build.bat with the same subcommands.
Static analysis does not execute code, so there are honest boundaries:
- Dynamic call targets. Helpers and handler factories are followed when their target can be resolved statically. Calls through interfaces, dependency-injection containers, or dynamically selected function values may retain only the information visible at the call site.
- Dynamic paths. A path built from a non-constant string is skipped (with a
note:on stderr). - Handlers behind DI/registries. If a handler cannot be resolved statically to a function (passed through an interface/container), its body is not analyzed and the route appears with a default 200 response.
- Generics in response types are handled in a limited way.
- Descriptions / summaries are not generated — there is no source for them without comments. This is a deliberate trade-off for zero-annotation operation.
go test ./...The internal/openapi test analyzes the bundled examples/v2 and examples/v3 projects and validates the generated documents with kin-openapi.
Issues and pull requests are welcome. Good first areas: interprocedural analysis (following response helpers), more binding patterns, and richer well-known type mappings.
MIT