Documentation
¶
Overview ¶
Package swaggerkit builds typed HTTP APIs on net/http: it binds and validates requests from Go types and generates the OpenAPI (Swagger) document from the same types, so the documentation always matches the code.
A handler takes a context and an input struct and returns an output value:
type GetHiveInput struct {
HiveID int `path:"hiveId" validate:"min=1"`
}
func GetHive(ctx context.Context, in GetHiveInput) (Hive, error) {
hive, ok := store.Get(in.HiveID)
if !ok {
return Hive{}, swaggerkit.NotFound("hive not found")
}
return hive, nil
}
api := swaggerkit.New(swaggerkit.Info{Title: "Apiary", Version: "1.0.0"},
swaggerkit.WithDocs("/docs"))
swaggerkit.Get(api, "/hives/{hiveId}", GetHive, swaggerkit.Tags("hives"))
http.ListenAndServe(":8080", api)
Input fields tagged path, query, header and cookie are parameters; a field named Body is the JSON request body. Fields are validated with the validate tag and documented with the doc and example tags. Errors are sent as RFC 9457 problem details.
The API serves Swagger UI and the OpenAPI 3.1 document (WithDocs), writes the document to a file (API.WriteOpenAPI) and reports design problems (API.Lint).
Guides: https://pluhsoft.github.io/swaggerkit/
Example ¶
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"github.com/pluhsoft/swaggerkit"
)
type Hive struct {
ID int `json:"id" doc:"Hive ID" example:"1"`
Name string `json:"name" doc:"Name painted on the hive" validate:"min=1,max=64" example:"Linden"`
Bees int `json:"bees" doc:"Bees in the hive" validate:"min=0"`
}
type GetHiveInput struct {
HiveID int `path:"hiveId" validate:"min=1"`
}
func GetHive(ctx context.Context, in GetHiveInput) (Hive, error) {
if in.HiveID != 1 {
return Hive{}, swaggerkit.NotFound("hive not found")
}
return Hive{ID: 1, Name: "Linden", Bees: 42000}, nil
}
func main() {
api := swaggerkit.New(swaggerkit.Info{Title: "Apiary", Version: "1.0.0"},
swaggerkit.WithDocs("/docs"))
swaggerkit.Get(api, "/hives/{hiveId}", GetHive, swaggerkit.Tags("hives"))
for _, path := range []string{"/hives/1", "/hives/2", "/hives/0"} {
rec := httptest.NewRecorder()
api.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil))
fmt.Print(rec.Code, " ", rec.Body.String())
}
}
Output: 200 {"id":1,"name":"Linden","bees":42000} 404 {"type":"about:blank","title":"Not Found","status":404,"detail":"hive not found"} 422 {"type":"about:blank","title":"Unprocessable Entity","status":422,"detail":"request validation failed","errors":[{"location":"path.hiveId","message":"must be greater than or equal to 1"}]}
Index ¶
- Constants
- func Delete[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
- func Get[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
- func Handle[In, Out any](r Router, method, path string, h HandlerFunc[In, Out], opts ...RouteOption)
- func Patch[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
- func Post[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
- func Put[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
- func Request(ctx context.Context) *http.Request
- func ResponseHeader(ctx context.Context) http.Header
- func SetStatus(ctx context.Context, code int)
- func WriteError(w http.ResponseWriter, err error)
- type API
- func (a *API) Document(f Format) ([]byte, error)
- func (a *API) DocumentHandler(f Format) http.Handler
- func (a *API) Group(prefix string, opts ...RouteOption) *Group
- func (a *API) Lint() []Issue
- func (a *API) OpenAPI() ([]byte, error)
- func (a *API) OpenAPIHandler() http.Handler
- func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (a *API) Use(mw ...Middleware)
- func (a *API) WriteOpenAPI(w io.Writer) error
- type Contact
- type Documented
- type Error
- type FieldError
- type File
- type Format
- type Group
- type HandlerFunc
- type Info
- type Issue
- type License
- type Middleware
- type NoContent
- type Option
- func WithBasePath(prefix string) Option
- func WithDocs(path string) Option
- func WithLogger(l *slog.Logger) Option
- func WithMaxBodyBytes(n int64) Option
- func WithSecurityScheme(name string, scheme SecurityScheme) Option
- func WithServer(url, description string) Option
- func WithTag(name, description string) Option
- func WithUnknownFields() Option
- type RouteOption
- func Deprecated() RouteOption
- func Description(s string) RouteOption
- func Errors(codes ...int) RouteOption
- func Header(name, description string) RouteOption
- func Hidden() RouteOption
- func MaxBodyBytes(n int64) RouteOption
- func Middlewares(mw ...Middleware) RouteOption
- func OperationID(id string) RouteOption
- func Produces(contentType string) RouteOption
- func Public() RouteOption
- func Security(schemes ...string) RouteOption
- func Status(code int) RouteOption
- func Statuses(codes ...int) RouteOption
- func Summary(s string) RouteOption
- func Tags(tags ...string) RouteOption
- type Router
- type Schema
- type SchemaProvider
- type SecurityScheme
- type Server
- type Severity
Examples ¶
Constants ¶
const OpenAPIVersion = "3.1.0"
OpenAPIVersion is the version of the generated documents.
Variables ¶
This section is empty.
Functions ¶
func Delete ¶
func Delete[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
Delete registers a DELETE route.
func Get ¶
func Get[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
Get registers a GET route.
func Handle ¶
func Handle[In, Out any](r Router, method, path string, h HandlerFunc[In, Out], opts ...RouteOption)
Handle registers a route. Path uses net/http patterns: "/hives/{hiveId}", "/files/{path...}". Mistakes in the route or its types, such as a path parameter without a matching field, panic, as http.ServeMux does.
func Patch ¶
func Patch[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
Patch registers a PATCH route.
func Post ¶
func Post[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
Post registers a POST route.
Example ¶
A handler whose input has no path, query, header or cookie fields takes the whole input as the JSON body.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/pluhsoft/swaggerkit"
)
type Hive struct {
ID int `json:"id" doc:"Hive ID" example:"1"`
Name string `json:"name" doc:"Name painted on the hive" validate:"min=1,max=64" example:"Linden"`
Bees int `json:"bees" doc:"Bees in the hive" validate:"min=0"`
}
type NewHive struct {
Name string `json:"name" validate:"min=1,max=64"`
}
func main() {
api := swaggerkit.New(swaggerkit.Info{Title: "Apiary", Version: "1.0.0"})
swaggerkit.Post(api, "/hives", func(ctx context.Context, in NewHive) (Hive, error) {
return Hive{ID: 2, Name: in.Name}, nil
}, swaggerkit.Status(http.StatusCreated))
req := httptest.NewRequest(http.MethodPost, "/hives", strings.NewReader(`{"name":"Clover"}`))
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
api.ServeHTTP(rec, req)
fmt.Print(rec.Code, " ", rec.Body.String())
}
Output: 201 {"id":2,"name":"Clover","bees":0}
func Put ¶
func Put[In, Out any](r Router, path string, h HandlerFunc[In, Out], opts ...RouteOption)
Put registers a PUT route.
func ResponseHeader ¶
ResponseHeader returns the response headers of a handler context. Set headers before the handler returns.
func SetStatus ¶ added in v1.1.0
SetStatus selects the success status of the response. The status must be the route's default or one documented with Statuses; otherwise the request fails with 500, so the documentation stays true.
func WriteError ¶ added in v1.1.0
func WriteError(w http.ResponseWriter, err error)
WriteError sends err as RFC 9457 problem details, in the same format as handler errors. Use it in middlewares, for example to reject a request with swaggerkit.Unauthorized("unknown token"). An error that is not an *Error is sent as 500 without details; log it yourself.
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
API is an http.Handler that serves typed handlers and generates their OpenAPI document. Create it with New and add routes with Get, Post, Put, Patch, Delete or Handle.
An API is safe for concurrent use.
func (*API) Document ¶ added in v1.1.0
Document returns the API description in the given format as indented JSON.
Converting to older formats loses what they cannot express: Swagger 2.0 has no cookie parameters, no OpenID Connect and one schema per response.
func (*API) DocumentHandler ¶ added in v1.1.0
DocumentHandler serves the document in the given format.
func (*API) Group ¶
func (a *API) Group(prefix string, opts ...RouteOption) *Group
Group returns a group of routes under prefix that share options, e.g. tags, security and middlewares.
func (*API) Lint ¶
Lint checks the API for common design and security problems. Mistakes that break the API, such as a path parameter without a field, panic at registration instead.
Example ¶
package main
import (
"context"
"fmt"
"github.com/pluhsoft/swaggerkit"
)
type GetHiveInput struct {
HiveID int `path:"hiveId" validate:"min=1"`
}
func main() {
api := swaggerkit.New(swaggerkit.Info{Title: "Apiary", Version: "1.0.0"})
swaggerkit.Post(api, "/hives/delete/{hiveId}", func(ctx context.Context, in GetHiveInput) (swaggerkit.NoContent, error) {
return swaggerkit.NoContent{}, nil
})
for _, issue := range api.Lint() {
fmt.Println(issue)
}
}
Output: warning: POST /hives/delete/{hiveId}: path segment "delete" is a verb; express the action with the HTTP method, e.g. DELETE /projects/{id} (verb-in-path) info: POST /hives/delete/{hiveId}: add Tags to group the operation in the documentation (operation-tags)
func (*API) OpenAPIHandler ¶
OpenAPIHandler returns a handler that serves the OpenAPI document.
func (*API) ServeHTTP ¶
func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
func (*API) Use ¶
func (a *API) Use(mw ...Middleware)
Use adds middlewares that run for every request, including unmatched routes and CORS preflight requests. The first middleware is the outermost.
func (*API) WriteOpenAPI ¶
WriteOpenAPI writes the OpenAPI document to w, for example to generate openapi.json during development.
Example ¶
package main
import (
"context"
"fmt"
"os"
"github.com/pluhsoft/swaggerkit"
)
type Hive struct {
ID int `json:"id" doc:"Hive ID" example:"1"`
Name string `json:"name" doc:"Name painted on the hive" validate:"min=1,max=64" example:"Linden"`
Bees int `json:"bees" doc:"Bees in the hive" validate:"min=0"`
}
type GetHiveInput struct {
HiveID int `path:"hiveId" validate:"min=1"`
}
func GetHive(ctx context.Context, in GetHiveInput) (Hive, error) {
if in.HiveID != 1 {
return Hive{}, swaggerkit.NotFound("hive not found")
}
return Hive{ID: 1, Name: "Linden", Bees: 42000}, nil
}
func main() {
api := swaggerkit.New(swaggerkit.Info{Title: "Apiary", Version: "1.0.0"})
swaggerkit.Get(api, "/hives/{hiveId}", GetHive)
f, err := os.CreateTemp("", "openapi-*.json")
if err != nil {
panic(err)
}
defer os.Remove(f.Name())
if err := api.WriteOpenAPI(f); err != nil {
panic(err)
}
fmt.Println(f.Close() == nil)
}
Output: true
type Contact ¶
type Contact struct {
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
Email string `json:"email,omitempty"`
}
Contact is the API contact in the OpenAPI document.
type Documented ¶ added in v1.1.0
type Documented interface {
Doc() string
}
Documented is implemented by types that describe themselves in the OpenAPI document:
func (Hive) Doc() string { return "A beehive in the apiary." }
type Error ¶
type Error struct {
Type string `json:"type"` // URI of the problem type; defaults to "about:blank"
Title string `json:"title"` // short summary; defaults to the status text
Status int `json:"status"` // HTTP status code
Detail string `json:"detail,omitempty"` // explanation for the client
Errors []FieldError `json:"errors,omitempty"` // invalid request values
// Err is the cause. It is logged for 5xx errors and never sent to the client.
Err error `json:"-"`
}
Error is an HTTP error sent to the client as RFC 9457 problem details (Content-Type: application/problem+json).
Return it from a handler to choose the status code. Any other error is logged and answered with 500 Internal Server Error without details.
func Errorf ¶
Errorf returns an error with the given status and a formatted detail message. The %w verb sets Err; the wrapped error text still becomes part of Detail.
type FieldError ¶
type FieldError struct {
// Location of the value: "body.frames[2].honeyKg", "query.limit", "path.hiveId".
Location string `json:"location"`
Message string `json:"message"`
}
FieldError describes one invalid value in a request.
type File ¶
type File struct {
Body io.Reader // if it is an io.ReadSeeker, range requests are supported
ContentType string // defaults to "application/octet-stream"
Name string // file name for Content-Disposition
Inline bool // show in the browser instead of downloading
ModTime time.Time // enables If-Modified-Since for io.ReadSeeker bodies
}
File is an output type for handlers that return a file or any other non-JSON body. Use *File as the output type.
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group is a set of routes with a common path prefix and options.
type HandlerFunc ¶
HandlerFunc is a typed handler. In describes the request: fields tagged path, query, header and cookie are parameters, a field named Body is the JSON body. If In has no such fields, the whole In is the body. Out is encoded as the JSON response; see also NoContent and File.
type Info ¶
type Info struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
TermsOfService string `json:"termsOfService,omitempty"`
Contact *Contact `json:"contact,omitempty"`
License *License `json:"license,omitempty"`
Version string `json:"version"`
}
Info describes the API in the OpenAPI document.
type Issue ¶
type Issue struct {
Severity Severity `json:"severity"`
Rule string `json:"rule"`
Location string `json:"location"` // "GET /hives/{hiveId}" or "Hive.Name"
Message string `json:"message"`
}
Issue is an API quality finding reported by API.Lint.
type License ¶
type License struct {
Name string `json:"name"`
Identifier string `json:"identifier,omitempty"` // SPDX identifier, e.g. "MIT"; use it or URL
URL string `json:"url,omitempty"`
}
License is the API license in the OpenAPI document.
type Middleware ¶
Middleware wraps an http.Handler. Standard net/http middlewares fit as is.
type NoContent ¶
type NoContent struct{}
NoContent is an output type for handlers without a response body. The route responds with 204 No Content.
type Option ¶
type Option func(*API)
Option configures an API.
func WithBasePath ¶
WithBasePath serves all routes under prefix, e.g. "/api/v1". Paths in the OpenAPI document stay relative to it; server URLs get the prefix appended.
func WithDocs ¶
WithDocs serves Swagger UI at path and the documents at path+"/openapi.json" (OpenAPI 3.1), path+"/openapi-3.0.json" and path+"/swagger.json" (Swagger 2.0).
func WithLogger ¶
WithLogger sets where server errors are logged: handler errors that become 500, and panics. Nothing else is logged. The default is slog.Default(); nil turns logging off. Records carry the request context, so a slog.Handler can add request fields such as a request ID.
func WithMaxBodyBytes ¶
WithMaxBodyBytes limits the size of request bodies. The default is 1 MiB.
func WithSecurityScheme ¶
func WithSecurityScheme(name string, scheme SecurityScheme) Option
WithSecurityScheme registers a security scheme for the OpenAPI document. Routes require it with the Security option.
func WithServer ¶
WithServer adds a server to the OpenAPI document. Without servers the document uses the base path, e.g. "/api/v1".
func WithTag ¶
WithTag adds a description to a tag in the OpenAPI document. Tags are listed in the order they are added.
func WithUnknownFields ¶
func WithUnknownFields() Option
WithUnknownFields accepts JSON fields that the Go types do not declare. By default they are rejected with 422.
type RouteOption ¶
type RouteOption func(*routeConfig)
RouteOption configures a route or a Group.
func Description ¶
func Description(s string) RouteOption
Description sets the description of the operation. Markdown is allowed.
func Errors ¶
func Errors(codes ...int) RouteOption
Errors documents error status codes that the handler returns, e.g. 404, 409. 401 for routes with Security, 422 for routes with input and a default error response are documented automatically.
func Header ¶ added in v1.1.0
func Header(name, description string) RouteOption
Header documents a header of the success response, e.g. Header("X-Total-Count", "Number of items in all pages"). Set it in the handler with ResponseHeader(ctx).Set.
func MaxBodyBytes ¶ added in v1.1.0
func MaxBodyBytes(n int64) RouteOption
MaxBodyBytes overrides WithMaxBodyBytes for the route, e.g. for uploads.
func Middlewares ¶
func Middlewares(mw ...Middleware) RouteOption
Middlewares adds middlewares for the route or group, e.g. authentication. They run after the API middlewares and before request validation.
func OperationID ¶
func OperationID(id string) RouteOption
OperationID sets the operation ID. By default it is derived from the handler name: ListHives → "listHives". Operation IDs must be unique.
func Produces ¶
func Produces(contentType string) RouteOption
Produces sets the content type of a File response in the documentation, e.g. "image/png". The default is "application/octet-stream".
func Public ¶
func Public() RouteOption
Public removes security requirements inherited from a group.
func Security ¶
func Security(schemes ...string) RouteOption
Security documents that the route requires one of the named security schemes, registered with WithSecurityScheme. Several names mean any of them is accepted. Check credentials in a middleware passed with Middlewares.
func Status ¶
func Status(code int) RouteOption
Status sets the success status code. The default is 200, or 204 for NoContent.
func Statuses ¶ added in v1.1.0
func Statuses(codes ...int) RouteOption
Statuses documents more success statuses of the route, e.g. 201 for a PUT that creates or replaces. The handler picks one with SetStatus.
func Summary ¶
func Summary(s string) RouteOption
Summary sets the short summary of the operation. By default it is derived from the handler name: ListHives → "List hives".
func Tags ¶
func Tags(tags ...string) RouteOption
Tags adds tags that group operations in the documentation.
type Schema ¶
type Schema struct {
Ref string `json:"$ref,omitempty"` // name of a component schema, e.g. "Hive"
Type string `json:"type,omitempty"` // "string", "integer", "number", "boolean", "array", "object"; empty means any value
Format string `json:"format,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Nullable bool `json:"-"` // rendered as type ["T", "null"]
Deprecated bool `json:"deprecated,omitempty"`
Enum []any `json:"enum,omitempty"`
Default any `json:"default,omitempty"`
Examples []any `json:"examples,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"`
MinLength *int `json:"minLength,omitempty"`
MaxLength *int `json:"maxLength,omitempty"`
Pattern string `json:"pattern,omitempty"`
Items *Schema `json:"items,omitempty"`
MinItems *int `json:"minItems,omitempty"`
MaxItems *int `json:"maxItems,omitempty"`
UniqueItems bool `json:"uniqueItems,omitempty"`
Properties map[string]*Schema `json:"properties,omitempty"`
Required []string `json:"required,omitempty"`
AdditionalProperties *Schema `json:"additionalProperties,omitempty"`
MinProperties *int `json:"minProperties,omitempty"`
MaxProperties *int `json:"maxProperties,omitempty"`
// contains filtered or unexported fields
}
Schema is a JSON Schema used for request validation and in the OpenAPI document.
Schemas are generated from Go types. Implement SchemaProvider to describe a type by hand.
func (*Schema) MarshalJSON ¶
MarshalJSON renders the schema as OpenAPI 3.1 expects it: references point to #/components/schemas and nullable schemas allow null.
type SchemaProvider ¶
type SchemaProvider interface {
JSONSchema() *Schema
}
SchemaProvider is implemented by types that describe their own JSON Schema, for example types with a custom MarshalJSON.
type SecurityScheme ¶
type SecurityScheme struct {
Type string `json:"type"` // "http", "apiKey" or "openIdConnect"
Description string `json:"description,omitempty"`
Scheme string `json:"scheme,omitempty"` // "bearer" or "basic" for Type "http"
BearerFormat string `json:"bearerFormat,omitempty"` // e.g. "JWT"
In string `json:"in,omitempty"` // "header", "query" or "cookie" for Type "apiKey"
Name string `json:"name,omitempty"` // header, query or cookie name for Type "apiKey"
OpenIDConnectURL string `json:"openIdConnectUrl,omitempty"`
}
SecurityScheme describes how clients authenticate, for the OpenAPI document. Create it with BearerAuth, BasicAuth, APIKeyAuth or OpenIDConnectAuth, register it with WithSecurityScheme and require it on routes with Security.
swaggerkit documents authentication; checking credentials is the job of your middleware, passed with Middlewares next to Security.
func APIKeyAuth ¶
func APIKeyAuth(in, name string) SecurityScheme
APIKeyAuth returns an API key scheme. in is "header", "query" or "cookie"; name is the header, query parameter or cookie name.
func BearerAuth ¶
func BearerAuth(format string) SecurityScheme
BearerAuth returns an HTTP bearer scheme ("Authorization: Bearer <token>"). format is a hint such as "JWT" and may be empty.
Example ¶
Security documents the scheme; your middleware checks the credentials.
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"github.com/pluhsoft/swaggerkit"
)
type GetHiveInput struct {
HiveID int `path:"hiveId" validate:"min=1"`
}
func main() {
requireToken := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer beekeeper" {
w.WriteHeader(http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
api := swaggerkit.New(swaggerkit.Info{Title: "Apiary", Version: "1.0.0"},
swaggerkit.WithSecurityScheme("beekeeper", swaggerkit.BearerAuth("")))
keeper := api.Group("/hives", swaggerkit.Security("beekeeper"), swaggerkit.Middlewares(requireToken))
swaggerkit.Delete(keeper, "/{hiveId}", func(ctx context.Context, in GetHiveInput) (swaggerkit.NoContent, error) {
return swaggerkit.NoContent{}, nil
})
req := httptest.NewRequest(http.MethodDelete, "/hives/1", nil)
rec := httptest.NewRecorder()
api.ServeHTTP(rec, req)
fmt.Println(rec.Code)
req.Header.Set("Authorization", "Bearer beekeeper")
rec = httptest.NewRecorder()
api.ServeHTTP(rec, req)
fmt.Println(rec.Code)
}
Output: 401 204
func OpenIDConnectAuth ¶
func OpenIDConnectAuth(discoveryURL string) SecurityScheme
OpenIDConnectAuth returns an OpenID Connect scheme. discoveryURL points to the provider's /.well-known/openid-configuration.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
apiary
command
Command apiary is an example API built with swaggerkit.
|
Command apiary is an example API built with swaggerkit. |
|
internal
|
|
|
release
command
Command release implements the release flow described in CONTRIBUTING.md.
|
Command release implements the release flow described in CONTRIBUTING.md. |