api

package
v0.14.0 Latest Latest
Warning

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

Go to latest
Published: Aug 14, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// ContextKeyAPIVersion stores the API version in context
	ContextKeyAPIVersion contextKey = "api_version"
	// ContextKeyRequestID stores the request ID in context
	ContextKeyRequestID contextKey = "request_id"
	// ContextKeyStartTime stores the request start time
	ContextKeyStartTime contextKey = "start_time"
)

Variables

This section is empty.

Functions

func APIKeyFunc

func APIKeyFunc(r *http.Request) string

APIKeyFunc returns API key as rate limit key

func APIVersion

func APIVersion(version string) func(next http.Handler) http.Handler

APIVersion middleware adds API version to context

func CORS

func CORS(allowedOrigins []string) func(next http.Handler) http.Handler

CORS adds CORS headers

func ChainMiddleware

func ChainMiddleware(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler

ChainMiddleware chains multiple middleware functions

func CheckResponse added in v0.14.0

func CheckResponse(op Op, status int, body []byte) error

CheckResponse reports whether a response matches what an operation declared.

The drift check. A declaration next to a handler is checked by the compiler for being well-formed and by nothing at all for being true, so this closes the gap: give it what the handler actually wrote and it fails when the two have parted company.

rec := httptest.NewRecorder()
h.Create(rec, request)

if err := api.CheckResponse(createOp, rec.Code, rec.Body.Bytes()); err != nil {
    t.Fatal(err)
}

It takes bytes and returns an error rather than taking a *testing.T, so the package does not link `testing` into every application that imports it.

func ContentTypeJSON

func ContentTypeJSON(next http.Handler) http.Handler

ContentTypeJSON ensures JSON content type for requests

func Created

func Created(w http.ResponseWriter, data interface{}, opts ...ResponseOption) error

Created sends a 201 response

func Describe added in v0.14.0

func Describe(op Op, handler http.HandlerFunc) http.Handler

Describe attaches a description to a handler.

The result is an http.Handler rather than an http.HandlerFunc, so it is registered with chi's Method rather than its Get/Post helpers:

r.Method("POST", "/invoices", api.Describe(api.Op{
    Summary:  "Create an invoice",
    Request:  Invoice{},
    Response: api.Envelope[Invoice]{},
    Status:   http.StatusCreated,
}, h.CreateInvoice))

An undescribed route still works and simply does not appear in the document. Inventing an operation for it would be worse than leaving it out.

func Error

func Error(w http.ResponseWriter, status int, code, message string, details map[string]interface{}, opts ...ResponseOption) error

Error sends an error response

func ErrorHandler

func ErrorHandler(debug bool) func(next http.Handler) http.Handler

ErrorHandler handles panics and errors in API routes

func Forbidden

func Forbidden(w http.ResponseWriter, message string, opts ...ResponseOption) error

Forbidden sends a 403 response

func GetAPIVersion

func GetAPIVersion(ctx context.Context) string

GetAPIVersion gets API version from context

func GetClientIP

func GetClientIP(r *http.Request, trustedProxies []string) string

GetClientIP extracts the real client IP with optional trusted proxy validation

func GetRequestStartTime

func GetRequestStartTime(ctx context.Context) time.Time

GetRequestStartTime gets request start time from context

func Health

func Health() http.HandlerFunc

Health returns a health check handler

func IPKeyFunc

func IPKeyFunc(r *http.Request) string

IPKeyFunc returns client IP as rate limit key

func InternalServerError

func InternalServerError(w http.ResponseWriter, message string, opts ...ResponseOption) error

InternalServerError sends a 500 response

func JSON

func JSON(w http.ResponseWriter, status int, data interface{}, opts ...ResponseOption) error

JSON sends a JSON response

func JSONRequest

func JSONRequest(dst interface{}) func(next http.Handler) http.Handler

JSONRequest parses JSON request body

func MethodNotAllowedHandler

func MethodNotAllowedHandler() http.HandlerFunc

MethodNotAllowedHandler returns a 405 handler

func NoContent

func NoContent(w http.ResponseWriter)

NoContent sends a 204 response

func NotFound

func NotFound(w http.ResponseWriter, message string, opts ...ResponseOption) error

NotFound sends a 404 response

func NotFoundHandler

func NotFoundHandler() http.HandlerFunc

NotFoundHandler returns a 404 handler

func OpenAPIHandler added in v0.14.0

func OpenAPIHandler(routes chi.Routes, info Info) (http.HandlerFunc, error)

OpenAPIHandler serves the document.

Not registered by anything: an API description is a map of the attack surface, and publishing one is a decision. Mount it where the ops dashboard is mounted, behind the same authorisation, or serve it only in development.

The document is built once, at construction, because it cannot change after the routes are registered and rebuilding it per request would walk the whole tree to produce the same bytes.

func Param

func Param(r *http.Request, key string) string

Param gets a URL parameter value

func Query

func Query(r *http.Request, key string) string

Query gets a query parameter value

func QueryBool

func QueryBool(r *http.Request, key string, defaultValue bool) bool

QueryBool gets a query parameter as bool

func QueryInt

func QueryInt(r *http.Request, key string, defaultValue int) int

QueryInt gets a query parameter as int

func RateLimitMiddleware

func RateLimitMiddleware(limiter RateLimiter, keyFunc func(*http.Request) string) func(http.Handler) http.Handler

RateLimitMiddleware creates rate limiting middleware

func RawJSON

func RawJSON(w http.ResponseWriter, status int, data interface{}) error

RawJSON sends raw JSON without wrapper

func RequestTimer

func RequestTimer(next http.Handler) http.Handler

RequestTimer adds timing information to responses

func RequireAuth

func RequireAuth(authFunc func(r *http.Request) (bool, error)) func(next http.Handler) http.Handler

RequireAuth checks for authentication

func SecureHeaders

func SecureHeaders(next http.Handler) http.Handler

SecureHeaders adds security headers

func TrustedProxyIPKeyFunc

func TrustedProxyIPKeyFunc(trustedProxies []string) func(*http.Request) string

TrustedProxyIPKeyFunc returns a key function that validates proxy headers

func Unauthorized

func Unauthorized(w http.ResponseWriter, message string, opts ...ResponseOption) error

Unauthorized sends a 401 response

func UserKeyFunc

func UserKeyFunc(userIDFunc func(*http.Request) string) func(*http.Request) string

UserKeyFunc returns user ID as rate limit key (requires authentication)

func ValidationErrors

func ValidationErrors(w http.ResponseWriter, errors []ValidationError, opts ...ResponseOption) error

ValidationErrors sends validation error response

func VersionMiddleware

func VersionMiddleware(config *VersionConfig) func(next http.Handler) http.Handler

VersionMiddleware adds version to context and validates it

func XML

func XML(w http.ResponseWriter, status int, data interface{}, opts ...ResponseOption) error

XML sends an XML response

Types

type API

type API struct {
	Router        *chi.Mux
	Config        *APIConfig
	VersionRouter *VersionRouter
	RateLimiter   RateLimiter
	// contains filtered or unexported fields
}

API represents the main API structure

func New

func New(config *APIConfig) *API

New creates a new API instance

func (*API) Group

func (api *API) Group(pattern string, middlewares ...func(http.Handler) http.Handler) *Router

Group creates a route group with optional middleware

func (*API) Mount

func (api *API) Mount(pattern string, handler http.Handler)

Mount mounts a handler at the specified pattern

func (*API) ServeHTTP

func (api *API) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler

func (*API) SetupRoutes

func (api *API) SetupRoutes()

SetupRoutes sets up default API routes

func (*API) Use

func (api *API) Use(middlewares ...func(http.Handler) http.Handler)

Use adds middleware to the API

type APIConfig

type APIConfig struct {
	Version         string
	RateLimitPerMin int
	EnableCORS      bool
	AllowedOrigins  []string
	EnableMetrics   bool
	Debug           bool
}

APIConfig holds API configuration

type Components added in v0.14.0

type Components struct {
	Schemas map[string]*Schema `json:"schemas,omitempty"`
}

Components holds the reusable schemas.

type DocInfo added in v0.14.0

type DocInfo struct {
	Title       string `json:"title"`
	Version     string `json:"version"`
	Description string `json:"description,omitempty"`
}

DocInfo is the info object.

type Document added in v0.14.0

type Document struct {
	OpenAPI string   `json:"openapi"`
	Info    DocInfo  `json:"info"`
	Servers []Server `json:"servers,omitempty"`

	// Paths maps a path template to its operations. A map, so encoding/json
	// sorts the keys and the document is byte-identical between runs -- which
	// is what makes it reviewable in a diff.
	Paths map[string]map[string]*Operation `json:"paths"`

	Components Components `json:"components,omitempty"`
}

Document is an OpenAPI 3.1 document.

func OpenAPI added in v0.14.0

func OpenAPI(routes chi.Routes, info Info) (*Document, error)

OpenAPI builds a document from a router's described routes.

Routes with no description are omitted. Use Undescribed to find them, and assert in a test that the ones that matter are covered -- that is what keeps a document honest as routes are added, and it is cheaper than a linter.

func (*Document) JSON added in v0.14.0

func (d *Document) JSON() ([]byte, error)

JSON renders the document.

type Envelope added in v0.14.0

type Envelope[T any] struct {
	Success   bool       `json:"success"`
	Data      T          `json:"data,omitempty"`
	Error     *ErrorInfo `json:"error,omitempty"`
	Meta      *Meta      `json:"meta,omitempty"`
	Timestamp int64      `json:"timestamp"`
}

Envelope is what JSON writes: the standard response with a typed payload.

It exists so an operation can declare the body that is really on the wire without restating five fields. The JSON is identical to Response's; only the data field is typed.

type ErrorInfo

type ErrorInfo struct {
	Code    string                 `json:"code" xml:"code"`
	Message string                 `json:"message" xml:"message"`
	Details map[string]interface{} `json:"details,omitempty" xml:"details,omitempty"`
}

ErrorInfo contains error details

type ErrorResponse added in v0.14.0

type ErrorResponse struct {
	Success   bool       `json:"success"`
	Error     *ErrorInfo `json:"error,omitempty"`
	Meta      *Meta      `json:"meta,omitempty"`
	Timestamp int64      `json:"timestamp"`
}

ErrorResponse is what Error writes: the envelope with no data field.

A type of its own rather than Envelope[struct{}], because an error body genuinely has no data field and a component called "EnvelopeOfstruct" is not a name anybody wants in a generated client.

type Info added in v0.14.0

type Info struct {
	Title       string
	Version     string
	Description string

	// Servers are the base URLs the paths are relative to.
	Servers []string
}

Info is the document's metadata.

type MediaType added in v0.14.0

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

MediaType is one content type's schema.

type Meta

type Meta struct {
	Page       int    `json:"page,omitempty" xml:"page,omitempty"`
	PerPage    int    `json:"per_page,omitempty" xml:"per_page,omitempty"`
	Total      int    `json:"total,omitempty" xml:"total,omitempty"`
	TotalPages int    `json:"total_pages,omitempty" xml:"total_pages,omitempty"`
	Version    string `json:"version,omitempty" xml:"version,omitempty"`
	RequestID  string `json:"request_id,omitempty" xml:"request_id,omitempty"`
}

Meta contains pagination and other metadata

type Op added in v0.14.0

type Op struct {
	Summary     string
	Description string
	Tags        []string

	// OperationID is what client generators name the method. Left empty it is
	// derived from the method and pattern, which is stable as long as the route
	// is -- set it explicitly if a generated client's method names matter more
	// than the URL structure.
	OperationID string

	// Request is a zero value of the type the request body decodes into, or
	// nil for an operation with no body.
	Request any

	// Response is a zero value of what is written on success -- what is
	// actually on the wire, not the payload inside it.
	//
	// Handlers that answer through JSON get the standard envelope, so this is
	// usually Envelope[T] rather than T:
	//
	//	Response: api.Envelope[Invoice]{}       // api.JSON(w, 200, invoice)
	//	Response: api.Envelope[[]Invoice]{}     // a list
	//	Response: Invoice{}                     // api.RawJSON(w, 200, invoice)
	//
	// Declaring T where the handler writes Envelope[T] describes a body nobody
	// sends, so CheckResponse fails on it.
	Response any

	// Status is the success status code. Zero means 200, or 204 when there is
	// no response body.
	Status int

	// Params are the query and header parameters. Path parameters are read off
	// the route pattern, because the spec requires every path template
	// variable to be declared and declaring them by hand only creates the
	// chance to disagree with the URL.
	Params []Parameter

	// Errors are the other status codes this operation returns. They are
	// described with the error envelope this package writes.
	Errors []int

	Deprecated bool
}

Op describes one operation.

func DescriptionOf added in v0.14.0

func DescriptionOf(h http.Handler) (Op, bool)

DescriptionOf returns a handler's description, if it has one.

type Operation added in v0.14.0

type Operation struct {
	OperationID string   `json:"operationId"`
	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   map[string]*Response3 `json:"responses"`
}

Operation is one method on one path.

type Parameter added in v0.14.0

type Parameter struct {
	Name string `json:"name"`

	// In is "query" or "header". Empty means "query"; path parameters are
	// derived from the route pattern rather than declared.
	In string `json:"in"`

	Description string `json:"description,omitempty"`
	Required    bool   `json:"required,omitempty"`

	// Type is a zero value whose type gives the schema. Nil means string.
	// Not serialised: Schema is what the document carries.
	Type any `json:"-"`

	Schema  *Schema `json:"schema,omitempty"`
	Example any     `json:"example,omitempty"`
}

Parameter is a path, query or header parameter.

One type for the declaration and for the document, because two would differ only in that one holds a Go value and the other its schema -- and the generator fills Schema in from Type. `Param` was taken by this package's chi URL-parameter accessor, which applications call far more often than they write a description.

type RateLimitInfo

type RateLimitInfo struct {
	Limit      int
	Remaining  int
	Reset      time.Time
	RetryAfter int // seconds
}

RateLimitInfo contains rate limit information

type RateLimiter

type RateLimiter interface {
	Allow(key string) (bool, *RateLimitInfo)
	Reset(key string)
}

RateLimiter interface for different rate limiting strategies

type RequestBody added in v0.14.0

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

RequestBody is the body an operation accepts.

type ResourceController

type ResourceController interface {
	List(w http.ResponseWriter, r *http.Request)
	Create(w http.ResponseWriter, r *http.Request)
	Get(w http.ResponseWriter, r *http.Request)
	Update(w http.ResponseWriter, r *http.Request)
	Delete(w http.ResponseWriter, r *http.Request)
}

ResourceController defines methods for a RESTful resource

type Response

type Response struct {
	Success   bool        `json:"success" xml:"success"`
	Data      interface{} `json:"data,omitempty" xml:"data,omitempty"`
	Error     *ErrorInfo  `json:"error,omitempty" xml:"error,omitempty"`
	Meta      *Meta       `json:"meta,omitempty" xml:"meta,omitempty"`
	Timestamp int64       `json:"timestamp" xml:"timestamp"`
}

Response represents a standard API response

type Response3 added in v0.14.0

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

Response3 is one response. Named for the version rather than for what it is, because this package already has a Response and renaming that would break every application using it.

type ResponseOption

type ResponseOption func(*Response)

ResponseOption allows customizing responses

func WithMeta

func WithMeta(meta *Meta) ResponseOption

WithMeta adds metadata to the response

func WithPagination

func WithPagination(page, perPage, total int) ResponseOption

WithPagination adds pagination metadata

func WithRequestID

func WithRequestID(requestID string) ResponseOption

WithRequestID adds request ID to metadata

func WithVersion

func WithVersion(version string) ResponseOption

WithVersion adds API version to metadata

type Route added in v0.14.0

type Route struct {
	Method  string
	Pattern string
}

Route is one registered method and pattern.

func Undescribed added in v0.14.0

func Undescribed(routes chi.Routes) []Route

Undescribed returns the routes carrying no description, so a test can insist that a part of the API is fully documented.

type Router

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

Router represents an API route group

func (*Router) Delete

func (r *Router) Delete(pattern string, handler http.HandlerFunc)

Delete registers a DELETE route

func (*Router) Get

func (r *Router) Get(pattern string, handler http.HandlerFunc)

Get registers a GET route

func (*Router) Patch

func (r *Router) Patch(pattern string, handler http.HandlerFunc)

Patch registers a PATCH route

func (*Router) Post

func (r *Router) Post(pattern string, handler http.HandlerFunc)

Post registers a POST route

func (*Router) Put

func (r *Router) Put(pattern string, handler http.HandlerFunc)

Put registers a PUT route

func (*Router) Resource

func (r *Router) Resource(pattern string, controller ResourceController)

Resource creates RESTful routes for a resource

func (*Router) Route

func (r *Router) Route(pattern string, fn func(r chi.Router))

Route creates a new sub-router

type Schema added in v0.14.0

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

	// Type is a string, or a two-element array for a nullable type.
	Type any `json:"type,omitempty"`

	Format string `json:"format,omitempty"`

	// ContentEncoding is how 3.1 says "these bytes are base64", replacing
	// 3.0's `format: byte`.
	ContentEncoding string `json:"contentEncoding,omitempty"`

	Description string `json:"description,omitempty"`

	Items      *Schema            `json:"items,omitempty"`
	Properties map[string]*Schema `json:"properties,omitempty"`
	Required   []string           `json:"required,omitempty"`

	AdditionalProperties *Schema `json:"additionalProperties,omitempty"`

	Enum    []any `json:"enum,omitempty"`
	Example any   `json:"example,omitempty"`
}

Schema is a JSON Schema 2020-12 schema, in the subset this package emits.

type Server added in v0.14.0

type Server struct {
	URL string `json:"url"`
}

Server is one server entry.

type SlidingWindow

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

SlidingWindow implements sliding window rate limiting

func NewSlidingWindow

func NewSlidingWindow(limit int, duration time.Duration) *SlidingWindow

NewSlidingWindow creates a new sliding window rate limiter

func (*SlidingWindow) Allow

func (sw *SlidingWindow) Allow(key string) (bool, *RateLimitInfo)

Allow checks if request is allowed

func (*SlidingWindow) Reset

func (sw *SlidingWindow) Reset(key string)

Reset resets the window for a key

func (*SlidingWindow) Stop

func (sw *SlidingWindow) Stop()

Stop gracefully stops the sliding window and cleans up resources

type TokenBucket

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

TokenBucket implements token bucket rate limiting

func NewTokenBucket

func NewTokenBucket(rate, capacity int, interval time.Duration) *TokenBucket

NewTokenBucket creates a new token bucket rate limiter

func (*TokenBucket) Allow

func (tb *TokenBucket) Allow(key string) (bool, *RateLimitInfo)

Allow checks if request is allowed

func (*TokenBucket) Reset

func (tb *TokenBucket) Reset(key string)

Reset resets the bucket for a key

func (*TokenBucket) Stop

func (tb *TokenBucket) Stop()

Stop gracefully stops the token bucket and cleans up resources

type ValidationError

type ValidationError struct {
	Field   string `json:"field" xml:"field"`
	Message string `json:"message" xml:"message"`
	Value   string `json:"value,omitempty" xml:"value,omitempty"`
}

ValidationError represents field validation errors

type VersionConfig

type VersionConfig struct {
	DefaultVersion    string
	SupportedVersions []string
	VersionHeader     string
	VersionInPath     bool
	VersionInQuery    bool
	Deprecated        map[string]string // Maps deprecated versions to sunset dates
}

VersionConfig holds versioning configuration

func DefaultVersionConfig

func DefaultVersionConfig() *VersionConfig

DefaultVersionConfig returns a default version configuration

type VersionNegotiator

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

VersionNegotiator handles version negotiation

func NewVersionNegotiator

func NewVersionNegotiator(defaultVersion string) *VersionNegotiator

NewVersionNegotiator creates a new version negotiator

func (*VersionNegotiator) AddVersion

func (vn *VersionNegotiator) AddVersion(version string, handler http.HandlerFunc)

AddVersion adds a version handler

func (*VersionNegotiator) ServeHTTP

func (vn *VersionNegotiator) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP negotiates and serves the appropriate version

type VersionRouter

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

VersionRouter manages API versioning

func NewVersionRouter

func NewVersionRouter(config *VersionConfig) *VersionRouter

NewVersionRouter creates a new version router

func (*VersionRouter) DeprecateVersion

func (vr *VersionRouter) DeprecateVersion(version, sunsetDate string)

DeprecateVersion marks a version as deprecated

func (*VersionRouter) GetVersion

func (vr *VersionRouter) GetVersion(r *http.Request) string

GetVersion extracts API version from request

func (*VersionRouter) Mount

func (vr *VersionRouter) Mount(pattern string, handler http.Handler)

Mount mounts versioned API routes

func (*VersionRouter) RegisterVersion

func (vr *VersionRouter) RegisterVersion(version string) *chi.Mux

RegisterVersion registers a new API version

func (*VersionRouter) ServeHTTP

func (vr *VersionRouter) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler

Jump to

Keyboard shortcuts

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