Documentation
¶
Overview ¶
Package openapi generates a valid OpenAPI 3.1 document for a lagodev application using only the standard library and reflection.
It has three layers:
- Struct -> JSON Schema: SchemaOf[T] / SchemaFor reflect over a Go type, honouring json and validate struct tags (see schema.go, validate.go).
- Spec builder: New(title, version) returns a *Spec onto which operations are declared with Operation / a fluent OperationBuilder. Request and response bodies are described by Go types and registered as reusable component schemas referenced by $ref.
- Serve: Spec.JSON renders the document; Spec.Handler and Spec.DocsHandler return net/http handlers serving the JSON and a Swagger-UI page.
Example ¶
type CreateUser struct {
Name string `json:"name" validate:"required,max=64"`
Email string `json:"email" validate:"required,email"`
Role string `json:"role" validate:"required,in=admin|user"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
spec := openapi.New("Users API", "1.0.0")
spec.AddServer("https://api.example.com", "production")
spec.Operation(http.MethodPost, "/users", openapi.OperationConfig{
Summary: "Create a user",
Tags: []string{"users"},
RequestBody: openapi.BodyOf[CreateUser](),
Responses: map[int]openapi.Response{
201: openapi.JSONResponse[User]("created"),
422: openapi.TextResponse("validation failed"),
},
})
json, _ := spec.JSON()
http.Handle("/openapi.json", spec.Handler())
http.Handle("/docs", spec.DocsHandler())
Index ¶
- func NormalizePath(path string) string
- func PathParams(path string) []string
- func Register[T any](r *Registry) string
- func SwaggerHTML(title, specURL string) string
- type Body
- type OperationConfig
- type Param
- type Registry
- type Response
- type RouteInfo
- type Schema
- type Spec
- func (s *Spec) AddServer(url, description string) *Spec
- func (s *Spec) DocsHandler(specURL string) http.Handler
- func (s *Spec) Handler() http.Handler
- func (s *Spec) JSON() ([]byte, error)
- func (s *Spec) Map() (map[string]any, error)
- func (s *Spec) Operation(method, path string, cfg OperationConfig) *Spec
- func (s *Spec) Registry() *Registry
- func (s *Spec) Tags() []string
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NormalizePath ¶
NormalizePath rewrites a route path into OpenAPI's brace form. Colon params ("/users/:id") become brace params ("/users/{id}"); brace paths pass through.
func PathParams ¶
PathParams extracts the parameter names from a route path, accepting both the OpenAPI/stdlib brace form ("/users/{id}") and the gin/colon form ("/users/:id"). It returns the names without delimiters, in order.
func Register ¶
Register adds T to the registry, returning its component name. It is the exported way to pre-register a response/request DTO.
func SwaggerHTML ¶
SwaggerHTML returns a minimal, self-contained Swagger-UI page (CDN-backed) titled with title and loading the spec from specURL.
Types ¶
type Body ¶
type Body struct {
Description string
Required bool
MediaType string // default application/json
// contains filtered or unexported fields
}
Body describes a request body: the schema (usually via BodyOf[T]) plus its media type (defaults to application/json) and required flag.
type OperationConfig ¶
type OperationConfig struct {
Summary string
Description string
Tags []string
OperationID string
// PathParams documents path parameters. Any {id}/:id segment in the path
// that is not listed here is auto-added as a required string parameter.
PathParams []Param
Query []Param
RequestBody *Body
Responses map[int]Response
}
OperationConfig is the declarative form passed to Spec.Operation.
type Param ¶
type Param struct {
Name string
Description string
Required bool
Schema *Schema // defaults to {type:string} when nil
}
Param describes a single path or query parameter.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry collects named component schemas so struct types are emitted once under components/schemas and referenced elsewhere by $ref. It de-duplicates by the Go type's name, recursing into nested struct fields.
type Response ¶
type Response struct {
Description string
MediaType string // default application/json when a schema is present
// contains filtered or unexported fields
}
Response describes one HTTP response: a human description plus an optional body schema and media type.
func JSONResponse ¶
JSONResponse builds a JSON response whose body is described by Go type T.
func ListResponse ¶
ListResponse builds a JSON response whose body is an array of T.
func TextResponse ¶
TextResponse builds a bodyless response carrying only a description (e.g. for 204 or error statuses).
type RouteInfo ¶
RouteInfo is one harvested route: its HTTP method and OpenAPI-normalised path plus the derived path-parameter names. It is the skeleton a caller enriches with request/response types via Spec.Operation.
func Harvest ¶
Harvest reads the routes registered on a *web.App (or any value exposing Routes() []web.Route) and returns their method/path/params skeleton. web does not carry request/response type metadata, so Harvest fills only what the router knows; callers supply bodies and responses when declaring operations.
for _, r := range openapi.Harvest(app) {
spec.Operation(r.Method, r.Path, openapi.OperationConfig{...})
}
func HarvestRouter ¶
HarvestRouter is Harvest for a *web.Router (e.g. a sub-group) or anything else returning []web.Route.
type Schema ¶
type Schema struct {
Ref string `json:"$ref,omitempty"`
Type typeField `json:"type,omitempty"`
Format string `json:"format,omitempty"`
// Object.
Properties map[string]*Schema `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
AdditionalProperties *Schema `json:"additionalProperties,omitempty"`
// Array.
Items *Schema `json:"items,omitempty"`
MinItems *int `json:"minItems,omitempty"`
MaxItems *int `json:"maxItems,omitempty"`
// String bounds / numeric bounds (pointers so 0 is distinguishable from
// "unset").
MinLength *int `json:"minLength,omitempty"`
MaxLength *int `json:"maxLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
Minimum *float64 `json:"minimum,omitempty"`
Maximum *float64 `json:"maximum,omitempty"`
// OpenAPI 3.1 / JSON Schema Draft 2020-12 model exclusive bounds as
// numbers, but lagodev emits them as booleans paired with minimum/maximum
// for readability; both forms validate equivalently for our generated docs.
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
Enum []any `json:"enum,omitempty"`
Description string `json:"description,omitempty"`
}
Schema is an OpenAPI 3.1 / JSON Schema object. It is a thin, JSON-faithful representation: only the fields lagodev emits are modelled, and everything marshals with the canonical OpenAPI key names. Unset fields are omitted so a generated document stays minimal.
OpenAPI 3.1 aligns JSON Schema's "nullable" with a type union, so a nullable value is expressed as Type being a []string such as ["string","null"] rather than the 3.0-era `nullable: true`. The Type field therefore marshals as a bare string when single and as an array when it carries "null".
func SchemaFor ¶
SchemaFor builds a JSON Schema for an arbitrary Go type. It honours json tags (names, omitempty, "-"), validate tags (required + format/bounds/enum), and recurses through nested structs, slices, maps and pointers. time.Time is emitted as {type:string, format:date-time}. Pointer and omitempty fields become nullable (the 3.1 ["T","null"] union).
SchemaFor builds inline schemas only; use a Registry / Spec.Components to produce $ref-able named component schemas.
type Spec ¶
type Spec struct {
Title string
Version string
Description string
// contains filtered or unexported fields
}
Spec is an in-progress OpenAPI 3.1 document. Build it with New, attach operations, then render with JSON or serve with Handler.
Example ¶
ExampleSpec demonstrates building an OpenAPI 3.1 document from Go types and serving it. Request/response bodies are described by structs whose json and validate tags drive the generated JSON Schema.
package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/devituz/lagodev/openapi"
)
func main() {
type CreateUser struct {
Name string `json:"name" validate:"required,max=64"`
Email string `json:"email" validate:"required,email"`
Role string `json:"role" validate:"required,in=admin|user"`
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
spec := openapi.New("Users API", "1.0.0")
spec.AddServer("https://api.example.com", "production")
spec.Operation(http.MethodPost, "/users", openapi.OperationConfig{
Summary: "Create a user",
Tags: []string{"users"},
RequestBody: openapi.BodyOf[CreateUser](),
Responses: map[int]openapi.Response{
201: openapi.JSONResponse[User]("created"),
422: openapi.TextResponse("validation failed"),
},
})
spec.Operation(http.MethodGet, "/users/{id}", openapi.OperationConfig{
Summary: "Get a user",
Tags: []string{"users"},
Responses: map[int]openapi.Response{
200: openapi.JSONResponse[User]("the user"),
404: openapi.TextResponse("not found"),
},
})
// Mount the JSON document and a Swagger-UI docs page.
mux := http.NewServeMux()
mux.Handle("/openapi.json", spec.Handler())
mux.Handle("/docs", spec.DocsHandler("/openapi.json"))
b, _ := spec.JSON()
var doc map[string]any
_ = json.Unmarshal(b, &doc)
fmt.Println("openapi:", doc["openapi"])
comps := doc["components"].(map[string]any)["schemas"].(map[string]any)
_, hasUser := comps["User"]
_, hasCreate := comps["CreateUser"]
fmt.Println("has User component:", hasUser)
fmt.Println("has CreateUser component:", hasCreate)
}
Output: openapi: 3.1.0 has User component: true has CreateUser component: true
func FromApp ¶
FromApp builds a Spec pre-populated with a bare operation for every route on app: method, path and path parameters are filled, each with a single default 200 response. It is the fastest path to a serveable document; callers can then refine individual operations by calling Spec.Operation again (a repeat method+path replaces the prior operation).
func (*Spec) AddServer ¶
AddServer appends a server URL (with optional description) to the document.
func (*Spec) DocsHandler ¶
DocsHandler returns an http.Handler that serves a Swagger-UI HTML page pointing at specURL (the path where Handler is mounted, e.g. "/openapi.json"). The page loads Swagger UI assets from a public CDN; for offline use, host the assets yourself and adapt SwaggerHTML.
http.Handle("/openapi.json", spec.Handler())
http.Handle("/docs", spec.DocsHandler("/openapi.json"))
func (*Spec) Handler ¶
Handler returns an http.Handler that serves the OpenAPI document as JSON. It renders the spec on first request and caches the bytes; mount it at a path such as /openapi.json. Only GET/HEAD are answered (405 otherwise).
http.Handle("/openapi.json", spec.Handler())
func (*Spec) Map ¶
Map returns the document as a generic map (via a JSON round-trip), useful for callers that want to merge or post-process before serving.
func (*Spec) Operation ¶
func (s *Spec) Operation(method, path string, cfg OperationConfig) *Spec
Operation declares an operation on the spec. Path params are auto-derived from {id}/:id segments and merged with explicit PathParams docs. Request and response bodies are resolved to component $refs via the registry.