Documentation
¶
Index ¶
- Constants
- func APIKeyFunc(r *http.Request) string
- func APIVersion(version string) func(next http.Handler) http.Handler
- func CORS(allowedOrigins []string) func(next http.Handler) http.Handler
- func ChainMiddleware(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler
- func CheckResponse(op Op, status int, body []byte) error
- func ContentTypeJSON(next http.Handler) http.Handler
- func Created(w http.ResponseWriter, data interface{}, opts ...ResponseOption) error
- func Describe(op Op, handler http.HandlerFunc) http.Handler
- func Error(w http.ResponseWriter, status int, code, message string, ...) error
- func ErrorHandler(debug bool) func(next http.Handler) http.Handler
- func Forbidden(w http.ResponseWriter, message string, opts ...ResponseOption) error
- func GetAPIVersion(ctx context.Context) string
- func GetClientIP(r *http.Request, trustedProxies []string) string
- func GetRequestStartTime(ctx context.Context) time.Time
- func Health() http.HandlerFunc
- func IPKeyFunc(r *http.Request) string
- func InternalServerError(w http.ResponseWriter, message string, opts ...ResponseOption) error
- func JSON(w http.ResponseWriter, status int, data interface{}, opts ...ResponseOption) error
- func JSONRequest(dst interface{}) func(next http.Handler) http.Handler
- func MethodNotAllowedHandler() http.HandlerFunc
- func NoContent(w http.ResponseWriter)
- func NotFound(w http.ResponseWriter, message string, opts ...ResponseOption) error
- func NotFoundHandler() http.HandlerFunc
- func OpenAPIHandler(routes chi.Routes, info Info) (http.HandlerFunc, error)
- func Param(r *http.Request, key string) string
- func Query(r *http.Request, key string) string
- func QueryBool(r *http.Request, key string, defaultValue bool) bool
- func QueryInt(r *http.Request, key string, defaultValue int) int
- func RateLimitMiddleware(limiter RateLimiter, keyFunc func(*http.Request) string) func(http.Handler) http.Handler
- func RawJSON(w http.ResponseWriter, status int, data interface{}) error
- func RequestTimer(next http.Handler) http.Handler
- func RequireAuth(authFunc func(r *http.Request) (bool, error)) func(next http.Handler) http.Handler
- func SecureHeaders(next http.Handler) http.Handler
- func TrustedProxyIPKeyFunc(trustedProxies []string) func(*http.Request) string
- func Unauthorized(w http.ResponseWriter, message string, opts ...ResponseOption) error
- func UserKeyFunc(userIDFunc func(*http.Request) string) func(*http.Request) string
- func ValidationErrors(w http.ResponseWriter, errors []ValidationError, opts ...ResponseOption) error
- func VersionMiddleware(config *VersionConfig) func(next http.Handler) http.Handler
- func XML(w http.ResponseWriter, status int, data interface{}, opts ...ResponseOption) error
- type API
- func (api *API) Group(pattern string, middlewares ...func(http.Handler) http.Handler) *Router
- func (api *API) Mount(pattern string, handler http.Handler)
- func (api *API) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (api *API) SetupRoutes()
- func (api *API) Use(middlewares ...func(http.Handler) http.Handler)
- type APIConfig
- type Components
- type DocInfo
- type Document
- type Envelope
- type ErrorInfo
- type ErrorResponse
- type Info
- type MediaType
- type Meta
- type Op
- type Operation
- type Parameter
- type RateLimitInfo
- type RateLimiter
- type RequestBody
- type ResourceController
- type Response
- type Response3
- type ResponseOption
- type Route
- type Router
- func (r *Router) Delete(pattern string, handler http.HandlerFunc)
- func (r *Router) Get(pattern string, handler http.HandlerFunc)
- func (r *Router) Patch(pattern string, handler http.HandlerFunc)
- func (r *Router) Post(pattern string, handler http.HandlerFunc)
- func (r *Router) Put(pattern string, handler http.HandlerFunc)
- func (r *Router) Resource(pattern string, controller ResourceController)
- func (r *Router) Route(pattern string, fn func(r chi.Router))
- type Schema
- type Server
- type SlidingWindow
- type TokenBucket
- type ValidationError
- type VersionConfig
- type VersionNegotiator
- type VersionRouter
- func (vr *VersionRouter) DeprecateVersion(version, sunsetDate string)
- func (vr *VersionRouter) GetVersion(r *http.Request) string
- func (vr *VersionRouter) Mount(pattern string, handler http.Handler)
- func (vr *VersionRouter) RegisterVersion(version string) *chi.Mux
- func (vr *VersionRouter) ServeHTTP(w http.ResponseWriter, r *http.Request)
Constants ¶
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 ¶
APIKeyFunc returns API key as rate limit key
func APIVersion ¶
APIVersion middleware adds API version to context
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
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 ¶
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 ¶
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 ¶
GetAPIVersion gets API version from context
func GetClientIP ¶
GetClientIP extracts the real client IP with optional trusted proxy validation
func GetRequestStartTime ¶
GetRequestStartTime gets request start time from context
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 ¶
JSONRequest parses JSON request body
func MethodNotAllowedHandler ¶
func MethodNotAllowedHandler() http.HandlerFunc
MethodNotAllowedHandler returns a 405 handler
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
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 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 ¶
RequestTimer adds timing information to responses
func RequireAuth ¶
RequireAuth checks for authentication
func SecureHeaders ¶
SecureHeaders adds security headers
func TrustedProxyIPKeyFunc ¶
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 ¶
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
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
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
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.
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.
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 ¶
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 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
Route is one registered method and pattern.
func Undescribed ¶ added in v0.14.0
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
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