openapi

package module
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 11 Imported by: 0

README

kori/openapi

OpenAPI 3.1 spec generation via kori.Option — zero-reflection at runtime, full type safety at build time.

kori/openapi generates your OpenAPI 3.1 spec from the same Go structs you use for binding and validation. It hooks into Kori's Option system, so spec registration lives next to route registration — no CLI step, no YAML files, no duplication.


Installation

go get github.com/douglasmai4/kori/openapi

Requires github.com/douglasmai4/kori (v0.1.0+). Go 1.22+.


Quick start

package main

import (
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/douglasmai4/kori"
    kopenapi "github.com/douglasmai4/kori/openapi"
)

type Todo struct {
    ID    int    `json:"id"`
    Title string `json:"title"`
    Done  bool   `json:"done"`
}

func main() {
    spec := kopenapi.NewSpec(kopenapi.Config{
        Title:   "Todo API",
        Version: "1.0.0",
    })

    r := chi.NewRouter()

    kori.GET(r, "/todos", listTodos,
        spec.Route(kopenapi.RouteConfig{
            Summary: "List todos",
            Tags:    []string{"todos"},
            Params:  ListParams{},
            Responses: map[int]any{
                200: []Todo{},
            },
        }),
    )

    // Serve the spec
    r.Get("/openapi.json", spec.JSONHandler())
    r.Get("/docs", spec.ScalarHandler("/openapi.json"))

    http.ListenAndServe(":8080", r)
}

Config

spec := kopenapi.NewSpec(kopenapi.Config{
    Title:       "Todo API",
    Version:     "1.0.0",
    Description: "A simple todo API.",
    Servers: []kopenapi.Server{
        {URL: "http://localhost:8080", Description: "Local"},
    },
})

Route registration

spec.Route(cfg) returns a kori.Option. Pass it as the last argument to any kori.GET, kori.POST, etc.

type ListParams struct {
    Page  int    `query:"page" validate:"min=0" doc:"Page number"`
    Limit int    `query:"limit" validate:"min=1,max=100"`
    Done  string `query:"done" validate:"omitempty,oneof=true false"`
}

kori.GET(r, "/todos", listTodos,
    spec.Route(kopenapi.RouteConfig{
        OperationID: "listTodos",        // optional — auto-generated if empty
        Summary:     "List todos",
        Description: "Returns all todos, optionally filtered.",
        Tags:        []string{"todos"},
        Deprecated:  false,
        Params:      ListParams{},       // path/query/header params from struct tags
        Body:        CreateTodoBody{},   // request body schema
        Responses: map[int]any{          // status → response type (nil = no body)
            200: []Todo{},
            400: kori.HTTPError{},
            404: kori.HTTPError{},
        },
    }),
)
  • Params — a struct with path, query, header tags. Path params are automatically required.
  • Body — a struct. Skipped for GET, HEAD, DELETE, OPTIONS, TRACE.
  • Responses — if empty, generates a default response with no schema.
  • Security — per-route security requirements (overrides global SetGlobalSecurity).
  • NoSecurity — set to true to mark an endpoint as unauthenticated (empty security: []).

Security schemes

spec := kopenapi.NewSpec(kopenapi.Config{Title: "My API", Version: "1.0.0"})

// Register schemes
spec.AddSecurityScheme("bearer", kopenapi.BearerAuth("JWT"))
spec.AddSecurityScheme("apiKey", kopenapi.APIKeyAuth("X-Api-Key", kopenapi.InHeader))

// Global security — applied to all endpoints by default
spec.SetGlobalSecurity(kopenapi.Require("bearer"))
Scheme constructors
Constructor HTTP Security OpenAPI type
BearerAuth(format...) Authorization: Bearer <token> http / bearer
BasicAuth() Authorization: Basic <base64> http / basic
APIKeyAuth(name, in) Header, query, or cookie key apiKey
OAuth2(flows) OAuth 2.0 authorization oauth2
OpenIDConnect(discoveryURL) OpenID Connect openIdConnect
Per-route overrides
// Override global security for a specific endpoint
kori.DELETE(api, "/projects/{id}", deleteProject,
    spec.Route(kopenapi.RouteConfig{
        Security: []kopenapi.SecurityRequirement{kopenapi.Require("apiKey")},
        // ...
    }),
)

// Mark an endpoint as public (no security)
kori.GET(api, "/projects", listProjects,
    spec.Route(kopenapi.RouteConfig{
        NoSecurity: true,
        // ...
    }),
)
Security requirements
kopenapi.Require("bearer")                // bearer with no scopes
kopenapi.Require("bearer", "apiKey")      // ANY of the listed schemes (OR)
kopenapi.RequireScopes("oauth", "read")   // OAuth2 with required scopes
Full example

See examples/security/main.go — a Projects API with:

  • Global bearer auth with a public endpoint (NoSecurity)
  • Per-route security override (API key for DELETE)
  • Bearer and API key scheme definitions
  • Auto-generated security fields in the spec
cd openapi/examples/security && go run .

curl http://localhost:8080/openapi.json  # see securitySchemes + security in each operation

Schema generation

Structs passed to Body and Responses are automatically converted to OpenAPI schemas and registered under #/components/schemas/.

Struct tags
type Todo struct {
    ID        int       `json:"id"         doc:"Unique identifier"    example:"1"`
    Title     string    `json:"title"      doc:"Title of the task"   example:"Buy milk"`
    Done      bool      `json:"done"       doc:"Whether completed"   example:"false"`
    Priority  string    `json:"priority"   validate:"oneof=low medium high"`
    CreatedAt time.Time `json:"created_at"`
}
Tag Effect
json Field name in the schema
validate Enriches schema with minLength, maxLength, minimum, maximum, format, enum
doc Sets description on the schema property
example Sets example value (auto-parsed to int/bool when applicable)
Validation → schema mapping
validate tag Schema output
required Added to required array (unless field is a pointer)
min=3 (string) minLength: 3
max=100 (string) maxLength: 100
min=1 (numeric) minimum: 1
max=100 (numeric) maximum: 100
gt=0 exclusiveMinimum: 0
lt=100 exclusiveMaximum: 100
len=10 minLength: 10, maxLength: 10
email format: email
uuid, uuid4 format: uuid
url, uri format: uri
datetime format: date-time
oneof=a b c enum: ["a","b","c"]

Pointer fields are treated as nullable (["type", "null"]) and excluded from required.

Embedded (anonymous) structs are inlined.


Serving the spec

// JSON endpoint
r.Get("/openapi.json", spec.JSONHandler())

// YAML endpoint
r.Get("/openapi.yaml", spec.YAMLHandler())

Scalar UI

Scalar is an API reference UI — like Swagger UI, but faster and cleaner.

r.Get("/docs", spec.ScalarHandler("/openapi.json"))

// With options
r.Get("/docs", spec.ScalarHandler("/openapi.json", kopenapi.ScalarOptions{
    Theme:              "purple",
    DarkMode:           true,
    HideModels:         false,
    HideDownloadButton: false,
    DefaultOpenAllTags: true,
    CustomCSS:          "body { background: #111; }",
}))

Auto operation IDs

If OperationID is empty, it is generated from the method and path pattern:

Pattern Method Operation ID
/todos GET list-todos
/todos/{id} GET get-todo
/todos POST create-todo
/todos/{id} PUT replace-todo
/todos/{id} PATCH update-todo
/todos/{id} DELETE delete-todo
/todos HEAD head-todos
/todos OPTIONS options-todos

Version segments (e.g. v1, v2) are stripped. The last noun is singularized.


Coexistence with plain Chi

kori/openapi works with any chi.Router. Routes without spec.Route(...) are simply not documented:

r.Get("/health", healthHandler)                      // undocumented
kori.GET(r, "/todos", listTodos, spec.Route(cfg))    // documented

Full examples

TODOs API — examples/openapi/main.go
  • Spec config with title, version, servers
  • RouteConfig with params, body, and responses for each endpoint
  • Struct tags: doc, example, validate
  • JSON and YAML spec endpoints
  • Scalar API reference UI
  • Auto-generated operation IDs
cd openapi/examples/openapi && go run .

curl http://localhost:8080/openapi.json
curl http://localhost:8080/openapi.yaml
open http://localhost:8080/docs
Security schemes — examples/security/main.go
  • Global bearer auth with per-route overrides
  • Public endpoints via NoSecurity
  • API key auth scoped to a single endpoint
  • Bearer, API Key, and OAuth2 scheme definitions
cd openapi/examples/security && go run .

curl http://localhost:8080/openapi.json
open http://localhost:8080/docs

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIKeyLocation added in v0.3.0

type APIKeyLocation string

APIKeyLocation specifies where an API key is sent (header, query, or cookie).

const (
	InHeader APIKeyLocation = "header"
	InQuery  APIKeyLocation = "query"
	InCookie APIKeyLocation = "cookie"
)

type Config

type Config struct {
	Title       string
	Version     string
	Description string
	Servers     []Server
}

Config holds the OpenAPI specification metadata.

type MediaType

type MediaType struct {
	Schema  *Schema `json:"schema,omitempty"`
	Example any     `json:"example,omitempty"`
}

MediaType describes the schema and example for a request/response body.

type OAuthFlow added in v0.3.0

type OAuthFlow struct {
	AuthorizationURL string            `json:"authorizationUrl,omitempty"`
	TokenURL         string            `json:"tokenUrl,omitempty"`
	RefreshURL       string            `json:"refreshUrl,omitempty"`
	Scopes           map[string]string `json:"scopes"`
}

OAuthFlow represents a single OAuth 2.0 flow.

type OAuthFlows added in v0.3.0

type OAuthFlows struct {
	Implicit          *OAuthFlow `json:"implicit,omitempty"`
	Password          *OAuthFlow `json:"password,omitempty"`
	ClientCredentials *OAuthFlow `json:"clientCredentials,omitempty"`
	AuthorizationCode *OAuthFlow `json:"authorizationCode,omitempty"`
}

OAuthFlows holds the four OAuth 2.0 flow types.

type Operation

type Operation struct {
	OperationID string                `json:"operationId,omitempty"`
	Summary     string                `json:"summary,omitempty"`
	Description string                `json:"description,omitempty"`
	Tags        []string              `json:"tags,omitempty"`
	Deprecated  bool                  `json:"deprecated,omitempty"`
	Parameters  []Parameter           `json:"parameters,omitempty"`
	RequestBody *RequestBody          `json:"requestBody,omitempty"`
	Responses   Responses             `json:"responses"`
	Security    []SecurityRequirement `json:"security,omitempty"`
}

Operation represents an OpenAPI path operation (GET, POST, etc.).

type Parameter

type Parameter struct {
	Name        string  `json:"name"`
	In          string  `json:"in"` // "path", "query", "header"
	Required    bool    `json:"required,omitempty"`
	Description string  `json:"description,omitempty"`
	Deprecated  bool    `json:"deprecated,omitempty"`
	Schema      *Schema `json:"schema,omitempty"`
	Example     any     `json:"example,omitempty"`
}

Parameter represents an OpenAPI parameter (path, query, or header).

type RequestBody

type RequestBody struct {
	Required    bool                 `json:"required,omitempty"`
	Description string               `json:"description,omitempty"`
	Content     map[string]MediaType `json:"content"`
}

RequestBody represents an OpenAPI request body.

type Response

type Response struct {
	Description string               `json:"description"`
	Content     map[string]MediaType `json:"content,omitempty"`
}

Response represents an OpenAPI response.

type Responses

type Responses map[string]*Response

Responses maps status codes to response definitions.

type RouteConfig

type RouteConfig struct {
	OperationID string
	Summary     string
	Description string
	Tags        []string
	Deprecated  bool
	Params      any
	Body        any
	Responses   map[int]any
	Security    []SecurityRequirement
	NoSecurity  bool
}

RouteConfig configures the OpenAPI operation for a route.

type ScalarOptions

type ScalarOptions struct {
	Theme              string
	DarkMode           bool
	HideModels         bool
	HideDownloadButton bool
	DefaultOpenAllTags bool
	CustomCSS          string
}

ScalarOptions customizes the Scalar API reference UI.

type Schema

type Schema struct {
	Ref string `json:"$ref,omitempty"`

	Type        any    `json:"type,omitempty"`   // string or []string (OpenAPI 3.1 nullable)
	Format      string `json:"format,omitempty"` // "int32","int64","float","double","email","uuid","uri","date-time"
	Description string `json:"description,omitempty"`
	Example     any    `json:"example,omitempty"`
	Deprecated  bool   `json:"deprecated,omitempty"`

	Properties           map[string]*Schema `json:"properties,omitempty"`
	Required             []string           `json:"required,omitempty"`
	AdditionalProperties any                `json:"additionalProperties,omitempty"`

	Items    *Schema `json:"items,omitempty"`
	MinItems *int    `json:"minItems,omitempty"`
	MaxItems *int    `json:"maxItems,omitempty"`

	MinLength *int   `json:"minLength,omitempty"`
	MaxLength *int   `json:"maxLength,omitempty"`
	Pattern   string `json:"pattern,omitempty"`
	Enum      []any  `json:"enum,omitempty"`

	Minimum          *float64 `json:"minimum,omitempty"`
	Maximum          *float64 `json:"maximum,omitempty"`
	ExclusiveMinimum *float64 `json:"exclusiveMinimum,omitempty"`
	ExclusiveMaximum *float64 `json:"exclusiveMaximum,omitempty"`
	MultipleOf       *float64 `json:"multipleOf,omitempty"`
}

Schema represents an OpenAPI schema object. Generated automatically from Go types via the Spec builder.

type SecurityRequirement added in v0.3.0

type SecurityRequirement map[string][]string

SecurityRequirement maps scheme names to required scopes. Use Require or RequireScopes to build one.

func Require added in v0.3.0

func Require(schemes ...string) SecurityRequirement

Require creates a security requirement for the given scheme names (no scopes).

func RequireScopes added in v0.3.0

func RequireScopes(scheme string, scopes ...string) SecurityRequirement

RequireScopes creates a security requirement with specific OAuth scopes.

type SecurityScheme added in v0.3.0

type SecurityScheme struct {
	Type             string      `json:"type"`
	Description      string      `json:"description,omitempty"`
	Scheme           string      `json:"scheme,omitempty"`
	BearerFormat     string      `json:"bearerFormat,omitempty"`
	In               string      `json:"in,omitempty"`
	Name             string      `json:"name,omitempty"`
	Flows            *OAuthFlows `json:"flows,omitempty"`
	OpenIDConnectURL string      `json:"openIdConnectUrl,omitempty"`
}

SecurityScheme represents an OpenAPI security scheme. Use the constructor functions (BearerAuth, BasicAuth, etc.) to create one.

func APIKeyAuth added in v0.3.0

func APIKeyAuth(name string, in APIKeyLocation) SecurityScheme

APIKeyAuth creates an API key security scheme.

name := openapi.APIKeyAuth("X-API-Key", openapi.InHeader)

func BasicAuth added in v0.3.0

func BasicAuth() SecurityScheme

BasicAuth creates an HTTP Basic authentication security scheme.

func BearerAuth added in v0.3.0

func BearerAuth(bearerFormat ...string) SecurityScheme

BearerAuth creates an HTTP Bearer token security scheme. Optionally specify the bearer format (e.g. "JWT").

func OAuth2 added in v0.3.0

func OAuth2(flows OAuthFlows) SecurityScheme

OAuth2 creates an OAuth 2.0 security scheme with the given flows.

func OpenIDConnect added in v0.3.0

func OpenIDConnect(discoveryURL string) SecurityScheme

OpenIDConnect creates an OpenID Connect security scheme.

type Server

type Server struct {
	URL         string `json:"url"`
	Description string `json:"description,omitempty"`
}

Server represents an OpenAPI server object.

type Spec

type Spec struct {
	// contains filtered or unexported fields
}

Spec builds an OpenAPI 3.1 specification from registered routes and types.

func NewSpec

func NewSpec(cfg Config) *Spec

NewSpec creates a new OpenAPI spec builder.

doc := openapi.NewSpec(openapi.Config{
    Title:   "My API",
    Version: "1.0.0",
})

func (*Spec) AddSecurityScheme added in v0.3.0

func (s *Spec) AddSecurityScheme(name string, scheme SecurityScheme)

AddSecurityScheme registers a security scheme for use by routes.

func (*Spec) JSONHandler

func (s *Spec) JSONHandler() http.HandlerFunc

JSONHandler returns an HTTP handler that serves the OpenAPI spec as JSON.

func (*Spec) Route

func (s *Spec) Route(cfg RouteConfig) kori.Option

Route returns an Option that registers the route in the OpenAPI spec.

kori.GET(r, "/todos", listTodos, doc.Route(openapi.RouteConfig{
    Summary: "List all todos",
    Params:  &ListParams{},
    Responses: map[int]any{200: &TodoList{}},
}))

func (*Spec) ScalarHandler

func (s *Spec) ScalarHandler(specURL string, opts ...ScalarOptions) http.HandlerFunc

ScalarHandler returns an HTTP handler that serves the Scalar API reference UI. specURL is the URL where the spec JSON is served (e.g. "/openapi.json").

func (*Spec) SetGlobalSecurity added in v0.3.0

func (s *Spec) SetGlobalSecurity(reqs ...SecurityRequirement)

SetGlobalSecurity sets security requirements that apply to all routes. Individual routes can override this via RouteConfig.Security.

func (*Spec) YAMLHandler

func (s *Spec) YAMLHandler() http.HandlerFunc

YAMLHandler returns an HTTP handler that serves the OpenAPI spec as YAML.

Directories

Path Synopsis
examples
openapi command
security command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL