api

package
v1.33.1 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 28 Imported by: 0

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

View Source
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

func Accept(ctx context.Context) string

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.

func POST

func POST[Req, Res any, P interface {
	*Req
	requests.Body
}](g *Group, path, name string, fn func(context.Context, P) (Res, error), opts ...Option)

POST registers a typed POST operation.

func PUT

func PUT[Req, Res any, P interface {
	*Req
	requests.Body
}](g *Group, path, name string, fn func(context.Context, P) (Res, error), opts ...Option)

PUT registers a typed PUT operation.

Types

type AuthenticateFunc

type AuthenticateFunc func(request requests.Request) error

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

func (g *Group) Group(prefix string, opts ...Option) *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

func APIKeyHeader(name string) Option

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

func ContentType(mediaType string) Option

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 Deprecated

func Deprecated() Option

Deprecated marks the operation deprecated.

func Description

func Description(description string) Option

Description sets the document description. Registry option.

func Doc

func Doc(summary string, description ...string) Option

Doc sets the operation summary and, optionally, its description.

func Hidden

func Hidden() Option

Hidden registers the operation on Echo but omits it from the spec.

func Info

func Info(title, version string) Option

Info sets the document title and version. Registry option.

func MaxBodyBytes

func MaxBodyBytes(n int64) Option

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

func Namer(namer func(reflect.Type) string) Option

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.

func Server

func Server(url string) Option

Server adds a server URL to the document. Registry option.

func Timeout

func Timeout(d time.Duration) Option

Timeout sets the request context timeout (requests.WithTimeout). Panics when d is not positive.

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

func New(e *echo.Echo, opts ...Option) *Registry

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

func (r *Registry) Group(prefix string, opts ...Option) *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

func (r *Registry) Spec(v Version) ([]byte, error)

Spec returns the OpenAPI document (hidden operations excluded) as indented JSON with sorted keys. It calls Ready.

func (*Registry) StrictSpec

func (r *Registry) StrictSpec() []byte

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.

type Version

type Version int

Version selects the OpenAPI version produced by Spec.

const (
	V31 Version = iota // OpenAPI 3.1.0
	V30                // OpenAPI 3.0.3
)

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.

Jump to

Keyboard shortcuts

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