Skip to content
wiki edited this page Sep 4, 2026 · 1 revision

rextension-openapi

Generates an OpenAPI 3.1 document from your route table, at startup, with no external OpenAPI library.

go get github.com/kryovyx/rextension-openapi
import (
	"github.com/kryovyx/rex"
	openapi "github.com/kryovyx/rextension-openapi"
)

app := rex.New(openapi.WithOpenAPI(openapi.NewConfig(
	openapi.WithTitle("Orders API"),
	openapi.WithVersion("2.1.0"),
)))

Served at /openapi.json on the default router.

A route opts in

type CreateUser struct{ rxroute.Route }

func (r *CreateUser) OperationID() string { return "createUser" }
func (r *CreateUser) Summary() string     { return "Create a user" }
func (r *CreateUser) Description() string { return "Creates a user and returns it." }
func (r *CreateUser) Tags() []string      { return []string{"users"} }

Routes that do not implement OpenAPIRoute are excluded. That is the opt-in switch — an operational endpoint stays out of the document by simply not declaring itself.

Everything else is picked up from interfaces the route may already implement for other reasons:

The route implements The document gains
RequestBody() / Responses() — for validation request and response schemas
RequiredSchemes() — for security the security block
RequiredRoles() x-required-roles
RequiredScopes() scopes in the security requirement

One declaration, used by everything that cares. There is nothing to keep in sync, because the contracts live in rextension rather than in any one extension.

Generated at startup, not on request

The document is built in ValidateRoutes — the moment the framework hands every extension the complete route table, after all configuration and before any listener binds.

Two consequences worth naming:

  • A generation failure is a startup error. It used to be a 500 on every request to the spec path, discoverable only by making one.
  • The document is written once, from one goroutine. The lazy version wrote it from whichever request arrived first, unsynchronised.

Only the default router, by default

IncludeRouters is empty by default, which means the default router only.

The generator used to collect from every router, so a route on an internal-only listener was published in the document served on the public one. A router existing on a separate port is usually a statement that its routes are not for the same audience; the document should not contradict that.

See Router Selection.

With Swagger UI

app := rex.New(
	openapi.WithOpenAPI(cfg),
	swagger.WithSwagger(nil),
)

rextension-swagger serves a UI against the generated document, including a Required Authorization panel built from x-required-roles.

Pages

Clone this wiki locally