Documentation
¶
Overview ¶
Package api is compose's declarative REST route registry. Every operation is registered once with a typed handler, func(ctx, *Req) (Res, error); the registry mounts it on Echo, produces an OpenAPI document (3.1 and 3.0.3) and validates every request against that document before the handler sees it.
Registration is boot-time: invalid registrations (lint errors, path placeholders without a path:"x" field, duplicate operationIds, ...) panic, as do Handle after Ready and a Ready that cannot build the documents, finds a protected operation, hidden or not, without an authenticator, or finds an Echo error handler that does not answer an ez error with a 4xx (see below), or finds an operation whose route a later Echo route on the same method and path replaced. Call Ready once every route is registered, before serving traffic.
Each request is authenticated, then validated against the schema (a JSON body nested deeper than 64 levels is refused before that), decoded once and checked with Body.Validate, in that order, before exactly one handler runs: the Produces handler of the negotiated media type, else the operation's own. Authentication runs before any body byte is read, so an unauthenticated caller learns nothing about the request's shape. Authenticate sets the authenticator for api.New, a group or an operation, the nearest one winning, and marks those operations protected; Public skips authentication and drops the security requirement from the spec. The request context derives from the Echo request's; Echo middleware after next(c) reads the request the registry built, and the context the authenticator produced, through handler.Request (see also Accept).
Errors, the pipeline's and the handler's, are returned to Echo, so middleware after next(c) gets them from next(c), and are answered by the app's HTTPErrorHandler: rest.ErrorHandler answers them in the handler.ErrorResponse envelope the spec documents, listing the fields a request failed validation on under fields, and its WithErrorTranslator and WithErrorReporter options localize and report them. Echo's default handler would answer every registry error as a 500 {"message":"Internal Server Error"}, so Ready calls the installed handler once with an ez EINVALID error and panics unless it answers with a 4xx; that call reaches the handler's reporter and translator, with handler.Request nil.
Tags: an operation's tag is the last non-parameter segment of its group's full prefix (/runs -> runs, /runs/:id/issues -> issues, /v1/runs -> runs). Its operationId is "<tag>.<name>", or just "<name>" when the prefix has no non-parameter segment.
Index ¶
- Constants
- func Accept(ctx context.Context) string
- func DELETE[Req, Res any, P interface{ ... }](g *Group, path, name string, fn func(context.Context, P) (Res, error), ...)
- func GET[Req, Res any, P interface{ ... }](g *Group, path, name string, fn func(context.Context, P) (Res, error), ...)
- func Handle[Req, Res any, P interface{ ... }](g *Group, method, path, name string, fn func(context.Context, P) (Res, error), ...)
- func PATCH[Req, Res any, P interface{ ... }](g *Group, path, name string, fn func(context.Context, P) (Res, error), ...)
- func POST[Req, Res any, P interface{ ... }](g *Group, path, name string, fn func(context.Context, P) (Res, error), ...)
- func PUT[Req, Res any, P interface{ ... }](g *Group, path, name string, fn func(context.Context, P) (Res, error), ...)
- type AuthenticateFunc
- type Blob
- type Group
- type OperationInfo
- type Option
- func APIKeyHeader(name string) Option
- func Authenticate(fn AuthenticateFunc) Option
- func ContentType(mediaType string) Option
- func Deprecated() Option
- func Description(description string) Option
- func Doc(summary string, description ...string) Option
- func Hidden() Option
- func Info(title, version string) Option
- func MaxBodyBytes(n int64) Option
- func Middleware(m ...echo.MiddlewareFunc) Option
- func Namer(namer func(reflect.Type) string) Option
- func Produces[Req any, P interface{ ... }](mediaType string, fn func(context.Context, P) (Blob, error)) Option
- func Public() Option
- func Server(url string) Option
- func Timeout(d time.Duration) Option
- type Registry
- type Version
Constants ¶
const DefaultMaxBody = 1 << 20
DefaultMaxBody is the request body limit in bytes of an operation that no MaxBodyBytes applies to.
Variables ¶
This section is empty.
Functions ¶
func Accept ¶
Accept returns the response media type negotiated for the request whose context is ctx, the same value as request.GetAccept(), or "" when ctx does not come from a registered operation.
func DELETE ¶
func DELETE[Req, Res any, P interface { *Req requests.Body }](g *Group, path, name string, fn func(context.Context, P) (Res, error), opts ...Option)
DELETE registers a typed DELETE operation.
func GET ¶
func GET[Req, Res any, P interface { *Req requests.Body }](g *Group, path, name string, fn func(context.Context, P) (Res, error), opts ...Option)
GET registers a typed GET operation.
func Handle ¶
func Handle[Req, Res any, P interface { *Req requests.Body }](g *Group, method, path, name string, fn func(context.Context, P) (Res, error), opts ...Option)
Handle registers a typed operation. path uses Echo syntax (/:id) relative to the group; name is the operationId suffix. Requests are authenticated (see Authenticate), validated against the generated schema, decoded into a new Req whose embedded pointer structs are allocated first (see embeddedPointers), checked with Validate and passed to fn with the request context; fn's error is returned to Echo, and fn's Res is documented and written as the JSON 200 response. A Blob Res needs ContentType; Produces adds other representations. Res must not be an interface type.
func PATCH ¶
func PATCH[Req, Res any, P interface { *Req requests.Body }](g *Group, path, name string, fn func(context.Context, P) (Res, error), opts ...Option)
PATCH registers a typed PATCH operation.
Types ¶
type AuthenticateFunc ¶
AuthenticateFunc authenticates a request. It runs first, before any parameter is coerced or any body byte is read, so request.GetBody() is nil inside it; the headers, IP, method, cookies and context are there. It may change the request (request.SetContext, request.SetLocale); the handler receives request.GetContext() as it is after the call. An error rejects the request: it is returned to Echo, so the client never learns whether its body was valid.
type Blob ¶
type Blob struct {
// ContentType is written as the Content-Type header. Empty means the
// negotiated media type. It may carry parameters (text/plain;
// charset=utf-8).
ContentType string
Body []byte
// Filename, when set, is sent as Content-Disposition: attachment.
Filename string
}
Blob is a non-JSON response body (a PDF, CSV or ZIP file). A handler whose Res is Blob (registered with ContentType) returns it as the operation's primary representation; a Produces handler returns it as an alternate one.
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group is a set of operations sharing a path prefix, a tag, Echo middleware and options.
func (*Group) Group ¶
Group creates a nested group. It inherits the parent's options, and its tag is derived from the full prefix. Panics when an option is not a group option.
func (*Group) Use ¶
func (g *Group) Use(m ...echo.MiddlewareFunc)
Use adds Echo middleware to the group. It applies to the operations registered and the groups created with Group after the call: Echo copies a group's middleware into a route when the route is added, and into a sub-group when the sub-group is created.
type OperationInfo ¶
type OperationInfo struct {
Method string
Path string // Echo syntax, /runs/:id
SpecPath string // OpenAPI syntax, /runs/{id}
OperationID string
Tag string
Hidden bool
Public bool // no authentication (api.Public)
Produces []string // 200 response media types, the primary one first
}
OperationInfo describes a registered operation.
type Option ¶
type Option struct {
// contains filtered or unexported fields
}
Option configures the registry (api.New), a group or an operation. Each option supports some of these scopes; using it at another one panics at boot. Registry options (Info, Description, APIKeyHeader, Server, Namer) are only accepted by New. ContentType and Produces are only accepted by an operation. Every other option is accepted at every scope and applies to every operation below it: options of New first, then the group's, then the operation's, so the nearest one wins. Middleware adds up instead: the middleware of New, of the groups and of the operation all run, in that order.
func APIKeyHeader ¶
APIKeyHeader declares an API key security scheme read from the given header and requires it on every operation not marked Public. Registry option.
func Authenticate ¶
func Authenticate(fn AuthenticateFunc) Option
Authenticate sets the authenticator of every operation in its scope and marks them protected, undoing a Public set further away: the nearest of api.New, the groups and the operation wins. Ready panics when a protected operation has no authenticator. Panics when fn is nil.
func ContentType ¶
ContentType declares the media type of an operation whose handler returns Blob: required then, and rejected on any other Res. The media type is a lowercase type/subtype without parameters or wildcards, never application/json. Operation option.
func Description ¶
Description sets the document description. Registry option.
func Hidden ¶
func Hidden() Option
Hidden registers the operation on Echo but omits it from the spec.
func MaxBodyBytes ¶
MaxBodyBytes sets the request body limit in bytes (DefaultMaxBody when no MaxBodyBytes applies). Passed to New it sets the default of every operation, which a group's or the operation's own overrides. Panics when n is not positive. rest.RejectNUL does not inspect a body over 32 MB, so an operation with a higher limit can be sent a NUL byte that middleware refuses in a smaller body.
func Middleware ¶
func Middleware(m ...echo.MiddlewareFunc) Option
Middleware adds per-route Echo middleware.
func Namer ¶
Namer sets the component namer (openapi.WithNamer), for example openapi.SegmentsNamer(2) when two packages declare the same type name. Registry option.
func Produces ¶
func Produces[Req any, P interface { *Req requests.Body }](mediaType string, fn func(context.Context, P) (Blob, error)) Option
Produces adds an alternate 200 representation: when the client negotiates mediaType, fn runs instead of the operation's handler. Req must be the operation's request type. mediaType follows ContentType's rules, must not repeat and must differ from the primary media type (application/json for a JSON Res, the ContentType for a Blob Res).
The pipeline picks the first Accept entry, in header order, that matches a declared type (type/* and */* match the first declared type in that range), defaulting to the primary one; q-values are ignored. Exactly one handler runs. Every answer of the operation's pipeline, its errors included, carries Vary: Accept; an error raised by middleware in front of it does not. Operation option. Panics when fn is nil.
func Public ¶
func Public() Option
Public skips authentication and removes the security requirement from the spec, undoing an Authenticate set further away.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds every registered operation of an Echo instance.
A JSON operation reads a non-empty body only with a JSON Content-Type and refuses it without one, so a cross-site form post cannot hand it a body.
func New ¶
New creates a registry bound to an Echo instance. Its operations return their errors to Echo, so the app sets e.HTTPErrorHandler to rest.ErrorHandler for the handler.ErrorResponse envelope the spec documents; with Echo's default handler every registry error would be a 500 {"message":"Internal Server Error"}, so Ready panics while it is installed. Authentication runs before the body is read, and handler.Request returns the request an operation built. Operation options such as Authenticate, Timeout or MaxBodyBytes apply to every operation, before the options of its groups and its own. Panics when an option is an operation option (ContentType, Produces).
func (*Registry) Group ¶
Group creates a top-level group. Options apply to every operation in it. Panics when an option is not a group option.
func (*Registry) Operations ¶
func (r *Registry) Operations() []OperationInfo
Operations returns the registered operations in registration order.
func (*Registry) Ready ¶
func (r *Registry) Ready()
Ready finalizes the reflector, compiles every operation's validator and builds the documents. Like every other invalid registration, a failure here panics with the ez error, as does a protected operation (hidden ones included) without an authenticator: every operation needs Authenticate or Public, from api.New, a group or itself. Before any of that it calls the Echo instance's HTTPErrorHandler once with an ez EINVALID error on a made-up request and panics unless the handler answers it with a 4xx, since operations return every error to Echo: Echo's default handler (a 500), a nil one, or one that writes nothing fails, so the app sets e.HTTPErrorHandler = rest.ErrorHandler() (or its own) first. That call reaches the handler's reporter and translator like any error, with handler.Request nil. It also panics, naming the operation, when a route registered on Echo after an operation, on its method and path, replaced it (Echo's default router allows that); a replacement made after Ready goes unnoticed. Registering after Ready panics; requests that arrive before Ready fail with EINTERNAL. Idempotent once it succeeds: later calls are no-ops, while a call that panicked leaves the registry not ready and the next one checks everything again.
func (*Registry) Spec ¶
Spec returns the OpenAPI document (hidden operations excluded) as indented JSON with sorted keys. It calls Ready.
func (*Registry) StrictSpec ¶
StrictSpec returns the 3.1 document including hidden operations, so with the components only they use, with additionalProperties:false on every response object component and with the properties of hidden:"true" fields listed (optional), since a response still sends them. It is meant for contract tests (see apitest). It calls Ready.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
fixture
Package fixture mirrors auditor3's runs module for api registry tests.
|
Package fixture mirrors auditor3's runs module for api registry tests. |
|
Package openapi turns Go request and response types into OpenAPI 3.1 (JSON Schema draft 2020-12) component schemas and parameters.
|
Package openapi turns Go request and response types into OpenAPI 3.1 (JSON Schema draft 2020-12) component schemas and parameters. |
|
internal/fixture
Package fixture mirrors auditor3's runs module for openapi reflector tests.
|
Package fixture mirrors auditor3's runs module for openapi reflector tests. |
|
internal/fixture/a/units
Package units collides by last path segment with fixture/b/units.
|
Package units collides by last path segment with fixture/b/units. |
|
internal/fixture/b/units
Package units collides by last path segment with fixture/a/units.
|
Package units collides by last path segment with fixture/a/units. |
|
internal/fixture/other
Package other holds a type whose name collides with fixture.Run, to prove package-qualified component names keep them apart.
|
Package other holds a type whose name collides with fixture.Run, to prove package-qualified component names keep them apart. |
|
Package validate compiles OpenAPI 3.1 schemas with santhosh-tekuri/jsonschema and validates decoded requests against them.
|
Package validate compiles OpenAPI 3.1 schemas with santhosh-tekuri/jsonschema and validates decoded requests against them. |