Documentation
¶
Overview ¶
Package rest's collector extracts an OpenAPI document from the registered HTTP handlers + their reflected I/O types.
Package rest provides the kit's HTTP/REST server, client, and route-registration primitives. It is opinionated about how services describe their HTTP surface and how that surface maps to the application layer:
- Routes are declared by Controllers via Routes(Router) — a grouped-routing API on top of stdlib net/http.ServeMux that supports prefix groups (Router.Route), scoped middleware (Router.Use and Router.With), and per-method handler registration.
- Handler factories take usecase-shaped inputs directly: usecase.Creator[R], usecase.Lister[R], usecase.Getter[R], and so on. There is no intermediate "controller" adapter; the kit translates HTTP query opts into search.Option internally.
- The JSON:API wire format is the default. Plain-JSON variants live in handlers_convenience.go for the rare endpoint that genuinely needs one.
Route registration ¶
A Controller implements one method:
func (c *workspaceController) Routes(r rest.Router) {
r.Route("/v1/workspaces", func(r rest.Router) {
r.Use(c.auth, c.actor)
r.Post("/", rest.NewJsonApiCreateHandler(c.uc, api.WorkspaceFromDTO, api.WorkspaceToDTO))
r.Route("/{id}", func(r rest.Router) {
r.Use(c.tenancy)
r.Get("/", rest.NewJsonApiGetHandler(c.uc, api.WorkspaceToDTO, nil))
r.Delete("/", rest.NewJsonApiDeleteHandler(c.uc, repository.DeleteTypeSoft))
r.Post("/members", rest.NewJsonApiCommandHandler(c.uc.Invite, decodeInvite, api.MemberToDTO))
})
})
}
Middleware ordering is first-in-list-is-outermost — r.Use(a, b, c) produces a(b(c(handler))). The same applies to global middleware registered via WithMiddlewares.
Handler factories ¶
Resource CRUD (body equals the resource):
- NewJsonApiCreateHandler — POST /resources
- NewJsonApiGetHandler — GET /resources/{id} (or singleton via HandlerWithGetDecoderOpts(DecodeGetSkipURLPathID()))
- NewJsonApiListHandler — GET /resources
- NewJsonApiUpdateHandler — PUT /resources/{id}
- NewJsonApiPatchHandler — PATCH /resources/{id}
- NewJsonApiDeleteHandler — DELETE /resources/{id}
DDD command shape (usecase takes a typed Command, input split across body + path):
- NewJsonApiCommandHandler — POST/PATCH /resources/{id}/action
Bulk:
- NewJsonApiBulkCreateHandler — primary-data array
- NewJsonApiBulkUpdateHandler — filter + patch envelope
- NewJsonApiBulkDeleteHandler — filter envelope
- NewJsonApiAtomicOperationsHandler — mixed add/update/remove in one transaction
Streaming:
- NewMultipartUploadHandler — multipart/form-data with one file part
- NewStreamingDownloadHandler — raw bytes with Range support
- NewServerSentEventsHandler — text/event-stream
Escape hatch for the rare handler that doesn't fit any shape:
- WriteJSONAPI — single-resource document with buffered marshal
- WriteJSONAPIError — jsonapi error document via the kit encoder
Server bootstrap ¶
server := rest.NewServer(
rest.WithAddress(":8080"),
rest.WithMiddlewares(
rest.RequestIDMiddleware(),
rest.RecoveryMiddleware(log),
rest.AccessLogMiddleware(log),
),
rest.WithControllers(workspaceCtrl, memberCtrl),
)
_ = server.ListenAndServe()
With fx, the wiring is:
fx.New(
rest.FxModule(),
rest.FxAuthenticator(),
rest.NewFxController(NewWorkspaceController),
rest.NewFxMiddleware(NewActorMiddleware),
// ...
).Run()
Authentication ¶
Auth is a regular Middleware. Wire it via rest.NewAuthMiddleware:
r.Use(rest.NewAuthMiddleware(authenticator))
Or scoped to a single registration:
r.With(rest.NewAuthMiddleware(authenticator)).Post("/", handler)
Client ¶
rest.NewClient and rest.NewDefaultHTTPClient cover the service-to-service call side. NewTracingTransport wraps an http.RoundTripper with OpenTelemetry trace propagation.
Index ¶
- Constants
- Variables
- func BuildHandler(controllers ...Controller) http.Handler
- func DELETE[DO, EI any](ctx context.Context, c Client, path string, req EI) (DO, error)
- func DecodeEmptyReq(ctx context.Context, r *http.Request) (interface{}, error)
- func DecodeGetReq(parseOpts []query.ParseOpt, opts ...GetDecoderOpt) func(_ context.Context, req *http.Request) ([]query.Option, error)
- func DecodeTokenFromCtx(ctx context.Context, r *http.Request) (interface{}, error)
- func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter)
- func EncoderAllowsEmptyRes(emptyResAllowed bool) encoderOpt
- func FxAuthenticator() fx.Option
- func FxModule(opts ...serverOption) fx.Option
- func GET[DO, EI any](ctx context.Context, c Client, path string, req EI) (DO, error)
- func GetJSONAPIIncludes(ctx context.Context) []string
- func JSONErrorEncoder(_ context.Context, err error, w http.ResponseWriter)
- func JsonApiErrorEncoder(ctx context.Context, err error, w http.ResponseWriter)
- func NewClient(clientName string, baseURL *url.URL, endpoints []ClientEndpoint, ...) (*client, error)
- func NewCreateHandler[R resource.Resource, C usecase.Creator[R], O any](creator C, decoder func(O) R, encoder func(res R) O, opts ...HandlerOpt) http.Handler
- func NewDefaultHTTPClient() *http.Client
- func NewDeleteHandler(deleter usecase.Deleter, deleteType repository.DeleteType, opts ...HandlerOpt) http.Handler
- func NewFxController(controller any) fx.Option
- func NewFxMiddleware(middleware any) fx.Option
- func NewGetHandler[R resource.Resource, C usecase.Getter[R], O any](getter C, encoder func(res R) O, parseOpts []query.ParseOpt, ...) http.Handler
- func NewHTTPClientDecoder[O any](ctx context.Context, r *http.Response) (response O, err error)
- func NewHTTPDecoder[O any](f func(context.Context, *http.Request) (request O, err error)) func(context.Context, *http.Request) (request any, err error)
- func NewHandler[I, O any](e transport.Endpoint[I, O], reqDecoder DecodeRequestFunc, ...) handler[I, O]
- func NewJsonApiAtomicOperationsHandler(...) http.Handler
- func NewJsonApiBulkCreateHandler[R, DTO resource.Resource, C usecase.CreatorBatch[R]](creator C, decoder func(DTO) R, encoder func(res R) DTO, opts ...HandlerOpt) http.Handler
- func NewJsonApiBulkDeleteHandler[F any, Cmd any](cmdFn func(ctx context.Context, cmd Cmd) (int, error), ...) http.Handler
- func NewJsonApiBulkUpdateHandler[F any, P any, Cmd any](cmdFn func(ctx context.Context, cmd Cmd) (int, error), ...) http.Handler
- func NewJsonApiCommandHandler[Cmd any, R resource.Resource, DTO resource.Resource](cmdFn func(ctx context.Context, cmd Cmd) (R, error), ...) http.Handler
- func NewJsonApiCreateHandler[R, DTO resource.Resource, C usecase.Creator[R]](creator C, decoder func(DTO) R, encoder func(res R) DTO, opts ...HandlerOpt) http.Handler
- func NewJsonApiDeleteHandler(deleter usecase.Deleter, deleteType repository.DeleteType, opts ...HandlerOpt) http.Handler
- func NewJsonApiGetHandler[R, DTO resource.Resource, C usecase.Getter[R]](getter C, encoder func(res R) DTO, parseOpts []query.ParseOpt, ...) http.Handler
- func NewJsonApiListHandler[R, DTO resource.Resource, C usecase.Lister[R]](lister C, resItemMapper func(res R) DTO, opts ...HandlerOpt) http.Handler
- func NewJsonApiPatchHandler[T, R, DTO resource.Resource, C usecase.Patcher[R]](patcher C, kind resource.Type, decoder func(T) []repository.PatchOption, ...) http.Handler
- func NewJsonApiUpdateHandler[R, DTO resource.Resource, C usecase.Updater[R]](updater C, decoder func(DTO) R, encoder func(res R) DTO, opts ...HandlerOpt) http.Handler
- func NewListHandler[R resource.Resource, C usecase.Lister[R], O any](lister C, resItemMapper func(res R) O, opts ...HandlerOpt) http.Handler
- func NewMultipartUploadHandler[Cmd any, R resource.Resource, DTO resource.Resource](cmdFn func(ctx context.Context, file io.Reader, cmd Cmd) (R, error), ...) http.Handler
- func NewPatchHandler[T, R resource.Resource, C usecase.Patcher[R], O any](patcher C, kind resource.Type, decoder func(T) []repository.PatchOption, ...) http.Handler
- func NewResourceHandler[R resource.Resource, O any](e transport.Endpoint[R, R], decoder func(O) R, encoder func(res R) O, ...) http.Handler
- func NewServer(opts ...serverOption) *http.Server
- func NewServerSentEventsHandler[Cmd any](decoder func(*http.Request) (Cmd, error), ...) http.Handler
- func NewStreamingDownloadHandler[Cmd any](cmdFn func(ctx context.Context, cmd Cmd) (*DownloadResponse, error), ...) http.Handler
- func NewTracingTransport(monitor monitoring.Monitor, base http.RoundTripper) http.RoundTripper
- func NewUpdateHandler[R resource.Resource, C usecase.Updater[R], O any](updater C, decoder func(O) R, encoder func(res R) O, opts ...HandlerOpt) http.Handler
- func PATCH[DO, EI any](ctx context.Context, c Client, path string, req EI) (DO, error)
- func POST[DO, EI any](ctx context.Context, c Client, path string, req EI) (DO, error)
- func PUT[DO, EI any](ctx context.Context, c Client, path string, req EI) (DO, error)
- func QueryOptsFromReq(parseOpts ...query.ParseOpt) func(ctx context.Context, r *http.Request) ([]query.Option, error)
- func RequestIDFromContext(ctx context.Context) string
- func RestJSONEncoder[I, O any](itemMapper func(in I) O, successCode int) func(context.Context, http.ResponseWriter, any) error
- func UnmarshalPayloadFromRequest[DTO any](req *http.Request) (DTO, error)
- func WithAddress(address string) serverOption
- func WithClientReqInterceptors(reqInterceptors ...ClientRequestInterceptor) clientOption
- func WithClientReqTimeout(timeout time.Duration) clientOption
- func WithClientResInterceptors(resInterceptors ...ClientResponseInterceptor) clientOption
- func WithControllers(controllers ...Controller) serverOption
- func WithEndpoints(endpoints ...Endpoint) serverOption
- func WithErrorEncoder(encoder ErrorEncoder) authMiddlewareOption
- func WithHTTPClient(cli *http.Client) clientOption
- func WithIdleTimeout(d time.Duration) serverOption
- func WithJSONAPIIncludes(next http.Handler) http.Handler
- func WithMaxConnections(n int) serverOption
- func WithMaxHeaderBytes(n int) serverOption
- func WithMiddlewares(middlewares ...Middleware) serverOption
- func WithRateLimitIdleTTL(d time.Duration) rateLimitOption
- func WithRateLimitKeyFunc(fn KeyFunc) rateLimitOption
- func WithReadHeaderTimeout(d time.Duration) serverOption
- func WithReadTimeout(d time.Duration) serverOption
- func WithShutdownTimeout(shutdownTimeout time.Duration) serverOption
- func WithTLSConfig(config *tls.Config) serverOption
- func WithTimeoutMessage(msg string) timeoutOption
- func WithWriteTimeout(d time.Duration) serverOption
- func WriteJSONAPI(w http.ResponseWriter, status int, model any) error
- func WriteJSONAPIError(w http.ResponseWriter, err error)
- type AccessLogMiddleware
- type AnyEndpoint
- type AtomicOperation
- type AtomicOperationResult
- type AtomicOperationsRequest
- type AtomicOperationsResponse
- type AtomicRef
- type AuthMiddleware
- type BulkDeleteRequest
- type BulkDeleteResponse
- type BulkUpdateRequest
- type BulkUpdateResponse
- type CORSMiddleware
- type Checker
- type Client
- type ClientEndpoint
- func NewDELETE[EI, DO any](path string, enc func(context.Context, *http.Request, EI) error, ...) (ClientEndpoint, error)
- func NewGET[EI, DO any](path string, enc func(context.Context, *http.Request, EI) error, ...) (ClientEndpoint, error)
- func NewPATCH[EI, DO any](path string, enc func(context.Context, *http.Request, EI) error, ...) (ClientEndpoint, error)
- func NewPOST[EI, DO any](path string, enc func(context.Context, *http.Request, EI) error, ...) (ClientEndpoint, error)
- func NewPUT[EI, DO any](path string, enc func(context.Context, *http.Request, EI) error, ...) (ClientEndpoint, error)
- type ClientEndpointOpt
- func WithClientEndpointErrInterceptors(errInterceptors ...ClientErrorInterceptor) ClientEndpointOpt
- func WithClientEndpointHttpClient(client *http.Client) ClientEndpointOpt
- func WithClientEndpointReqInterceptors(reqInterceptors ...ClientRequestInterceptor) ClientEndpointOpt
- func WithClientEndpointResInterceptors(resInterceptors ...ClientResponseInterceptor) ClientEndpointOpt
- func WithClientEndpointTarget(target *url.URL) ClientEndpointOpt
- type ClientErrorInterceptor
- type ClientRequestInterceptor
- type ClientResponseInterceptor
- type Collector
- func (c *Collector) AnnotateOperation(method, pattern string, setup ...func(oc openapi.OperationContext) error)
- func (c *Collector) CollectOperation(method, pattern string, annotations ...func(oc openapi.OperationContext) error) (err error)
- func (c *Collector) HasAnnotation(method, pattern string) bool
- func (c *Collector) Refl() openapi.Reflector
- func (c *Collector) Reflector() *openapi3.Reflector
- func (c *Collector) ServeHTTP(rw http.ResponseWriter, _ *http.Request)
- func (c *Collector) SpecSchema() openapi.SpecSchema
- type ContextTimeoutMiddleware
- type Controller
- type DecodeRequestFunc
- type DownloadResponse
- type EncodeResponseFunc
- type Endpoint
- type ErrorEncoder
- type FxConfig
- type GetDecoderOpt
- type HTTPAuthenticator
- type HandlerOpt
- type Headerer
- type KeyFunc
- type Middleware
- type MiddlewareFunc
- type MultipartFile
- type OpenAPIPreparer
- type ProbeOption
- type Probes
- func (p *Probes) AddLiveness(name string, c Checker)
- func (p *Probes) AddReadiness(name string, c Checker)
- func (p *Probes) LivenessEndpoint() Endpoint
- func (p *Probes) LivenessHandler() http.HandlerFunc
- func (p *Probes) ReadinessEndpoint() Endpoint
- func (p *Probes) ReadinessHandler() http.HandlerFunc
- type RateLimitMiddleware
- type Readiness
- type RecoveryMiddleware
- type RequestIDMiddleware
- type Router
- type SSEEvent
- type SSEHandlerOpt
- type StatusCoder
- type TimeoutMiddleware
- type TracingMiddleware
- type UploadHandlerOpt
Constants ¶
const HeaderRequestID = "X-Request-ID"
HeaderRequestID is the canonical request-id header read and written by the REST middleware stack.
const IDPath = "/{id}"
IDPath is the conventional URL segment for a resource id, used in Router registrations like r.Get(IDPath, h) when callers want the pattern to match the kit's "/{id}" convention exactly.
Variables ¶
var ErrResponseWrongType = errors.New("response is not of the expected type")
Functions ¶
func BuildHandler ¶
func BuildHandler(controllers ...Controller) http.Handler
BuildHandler is a convenience for tests and embedded uses that want the same routing semantics as NewServer without the http.Server wrapper: it runs each Controller's Routes() against a fresh root Router, flattens to an *http.ServeMux, and returns it. Global middleware should be applied by the caller if needed.
func DecodeEmptyReq ¶
func DecodeGetReq ¶
func DecodeTokenFromCtx ¶
func DefaultErrorEncoder ¶
func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter)
DefaultErrorEncoder encodes errors to HTTP responses. It does not log; an error-logging middleware in the chain is the right place to attribute errors to a request_id / span.
func EncoderAllowsEmptyRes ¶
func EncoderAllowsEmptyRes(emptyResAllowed bool) encoderOpt
func FxAuthenticator ¶
func GetJSONAPIIncludes ¶
GetJSONAPIIncludes extracts the includes from the context
func JSONErrorEncoder ¶
func JSONErrorEncoder(_ context.Context, err error, w http.ResponseWriter)
JSONErrorEncoder encodes errors as simple JSON.
func JsonApiErrorEncoder ¶
func JsonApiErrorEncoder(ctx context.Context, err error, w http.ResponseWriter)
func NewClient ¶
func NewClient( clientName string, baseURL *url.URL, endpoints []ClientEndpoint, opts ...clientOption, ) (*client, error)
func NewCreateHandler ¶
func NewDefaultHTTPClient ¶
NewDefaultHTTPClient returns an *http.Client with a tuned transport suitable for service-to-service calls. Use it as the http.Client passed to WithHTTPClient when the caller does not have a project-specific transport. The returned client uses Timeout=0 — pair with WithClientReqTimeout or per-request ctx deadlines.
func NewDeleteHandler ¶
func NewDeleteHandler( deleter usecase.Deleter, deleteType repository.DeleteType, opts ...HandlerOpt, ) http.Handler
func NewFxController ¶
NewFxController registers a Controller constructor into the fx graph. The Controller is collected via the `restControllers` fx group and its Routes(Router) method is invoked at NewServer time against the server's root router.
func NewFxMiddleware ¶
NewFxMiddleware is a helper function that given a middleware constructor builds a compatible and annotated fx module.
func NewGetHandler ¶
func NewHTTPClientDecoder ¶
func NewHTTPDecoder ¶
func NewHandler ¶
func NewHandler[I, O any]( e transport.Endpoint[I, O], reqDecoder DecodeRequestFunc, resEncoder EncodeResponseFunc, opts ...HandlerOpt, ) handler[I, O]
func NewJsonApiAtomicOperationsHandler ¶
func NewJsonApiAtomicOperationsHandler( dispatcher func(ctx context.Context, ops []AtomicOperation) ([]AtomicOperationResult, error), opts ...HandlerOpt, ) http.Handler
NewJsonApiAtomicOperationsHandler wires the JSON:API Atomic Operations endpoint. The dispatcher receives the parsed operations array; it's the dispatcher's job to run them inside a single transaction and produce results in the same order.
This is the most flexible (and most dangerous) bulk shape — any mix of add/update/remove across any resource type in one request. Use only when true cross-resource atomicity is required.
func NewJsonApiBulkCreateHandler ¶
func NewJsonApiBulkCreateHandler[R, DTO resource.Resource, C usecase.CreatorBatch[R]]( creator C, decoder func(DTO) R, encoder func(res R) DTO, opts ...HandlerOpt, ) http.Handler
NewJsonApiBulkCreateHandler wires `POST /resources` whose body is `{ "data": [ {...}, {...} ] }`. The usecase implements usecase.CreatorBatch[R] (one transactional batch insert); the response is a primary-data array with the created resources, status 201.
func NewJsonApiBulkDeleteHandler ¶
func NewJsonApiBulkDeleteHandler[F any, Cmd any]( cmdFn func(ctx context.Context, cmd Cmd) (int, error), cmdFromReq func(filter F) Cmd, opts ...HandlerOpt, ) http.Handler
NewJsonApiBulkDeleteHandler wires `POST /resources:batchDelete`.
func NewJsonApiBulkUpdateHandler ¶
func NewJsonApiBulkUpdateHandler[F any, P any, Cmd any]( cmdFn func(ctx context.Context, cmd Cmd) (int, error), cmdFromReq func(filter F, patch P) Cmd, opts ...HandlerOpt, ) http.Handler
NewJsonApiBulkUpdateHandler wires `POST /resources:batchUpdate`. The cmdFn signature is open enough to fit any (filter, patch) command shape; the kit provides the wire-envelope contract via BulkUpdateRequest. Returns 200 + a BulkUpdateResponse with the affected count.
type BulkMarkPaidCommand struct {
Filter InvoiceFilter
Patch InvoicePatch
}
func (uc *invoiceUsecase) MarkPaid(ctx context.Context, cmd BulkMarkPaidCommand) (int, error)
r.Post("/invoices:batchUpdate",
kitrest.NewJsonApiBulkUpdateHandler[InvoiceFilter, InvoicePatch](
uc.MarkPaid,
func(f InvoiceFilter, p InvoicePatch) BulkMarkPaidCommand {
return BulkMarkPaidCommand{Filter: f, Patch: p}
},
),
)
func NewJsonApiCommandHandler ¶
func NewJsonApiCommandHandler[Cmd any, R resource.Resource, DTO resource.Resource]( cmdFn func(ctx context.Context, cmd Cmd) (R, error), decoder func(*http.Request) (Cmd, error), encoder func(R) DTO, opts ...HandlerOpt, ) http.Handler
NewJsonApiCommandHandler wires a write endpoint whose usecase method takes a typed Command struct rather than the resource directly. Fits the DDD pattern where the function signature carries the intent of the operation alongside its inputs:
func (uc *workspaceUsecase) Invite(ctx context.Context, cmd InviteMemberCommand) (Member, error) func (uc *orderUsecase) Cancel(ctx context.Context, cmd CancelOrderCommand) (Order, error)
The decoder receives the *http.Request so it can read URL path params (req.PathValue("id")), query params, and body. Identity (actor, tenancy) is NEVER extracted by the decoder — middleware puts those on ctx and the usecase reads them when applying business rules.
The default success status is 201 Created. Override with HandlerWithCreatedStatus(http.StatusOK) for a Command that doesn't produce a new resource (e.g. POST /orders/{id}/cancel returns the updated order, not a new one).
func NewJsonApiCreateHandler ¶
func NewJsonApiCreateHandler[R, DTO resource.Resource, C usecase.Creator[R]]( creator C, decoder func(DTO) R, encoder func(res R) DTO, opts ...HandlerOpt, ) http.Handler
NewJsonApiCreateHandler builds the POST handler for a jsonapi resource. When HandlerAllowsEmptyReq(true) is passed, the body decoder short-circuits on a zero-length body and feeds the creator a zero R — useful for token-driven creates (e.g. POST /v1/users bootstrapping from a Firebase token) where all inputs come from ctx and there is no meaningful body to send.
func NewJsonApiDeleteHandler ¶
func NewJsonApiDeleteHandler( deleter usecase.Deleter, deleteType repository.DeleteType, opts ...HandlerOpt, ) http.Handler
NewJsonApiDeleteHandler wires a DELETE endpoint. The deleteType (soft vs hard) is fixed per handler at registration time — usually soft, since hard-deletes are a privileged operation that goes through a separate code path.
func NewJsonApiGetHandler ¶
func NewJsonApiGetHandler[R, DTO resource.Resource, C usecase.Getter[R]]( getter C, encoder func(res R) DTO, parseOpts []query.ParseOpt, opts ...HandlerOpt, ) http.Handler
NewJsonApiGetHandler serves both the canonical "/{id}" Get and the singleton variant (e.g. /me, /config). Pass HandlerWithGetDecoderOpts(DecodeGetSkipURLPathID()) to skip the {id} path-segment requirement when identity comes from context instead.
func NewJsonApiListHandler ¶
func NewJsonApiPatchHandler ¶
func NewJsonApiPatchHandler[T, R, DTO resource.Resource, C usecase.Patcher[R]]( patcher C, kind resource.Type, decoder func(T) []repository.PatchOption, encoder func(res R) DTO, opts ...HandlerOpt, ) http.Handler
func NewJsonApiUpdateHandler ¶
func NewListHandler ¶
func NewMultipartUploadHandler ¶
func NewMultipartUploadHandler[Cmd any, R resource.Resource, DTO resource.Resource]( cmdFn func(ctx context.Context, file io.Reader, cmd Cmd) (R, error), decoder func(file MultipartFile, req *http.Request) (Cmd, error), encoder func(R) DTO, opts ...UploadHandlerOpt, ) http.Handler
NewMultipartUploadHandler wires `POST /resources` whose body is a multipart/form-data document with one file part plus an arbitrary number of metadata fields. The decoder receives the parsed file + the *http.Request (path params, form values via req.FormValue) and returns a typed Command.
func decodeDocumentUpload(file rest.MultipartFile, req *http.Request) (UploadDocumentCommand, error) {
return UploadDocumentCommand{
WorkspaceID: req.PathValue("workspaceId"),
FileName: file.FileName,
ContentType: file.ContentType,
}, nil
}
r.Post("/workspaces/{workspaceId}/documents",
kitrest.NewMultipartUploadHandler(
uc.Upload, decodeDocumentUpload, api.DocumentToDTO))
The cmdFn receives the file reader alongside the Command; it streams bytes directly to storage without buffering. fileField is the form-field name for the file part (default "file" if empty).
func NewPatchHandler ¶
func NewPatchHandler[T, R resource.Resource, C usecase.Patcher[R], O any]( patcher C, kind resource.Type, decoder func(T) []repository.PatchOption, encoder func(res R) O, opts ...HandlerOpt, ) http.Handler
func NewResourceHandler ¶
func NewServer ¶
NewServer creates a new HTTP server with the given options.
Route registration is unified through a single root Router:
- WithControllers(...) — each Controller's Routes(Router) method registers grouped routes + scoped middleware on the root router.
- WithEndpoints(...) — singleton handlers (health, readiness, anything truly route-less). Registered at the root.
Global cfg.middlewares wrap the entire mux as the outermost layer, so they run before routing — important for RequestID, Tracing, Recovery which need to see 404s and 405s too. Ordering matches the Router's Use(): the first middleware in the slice is the outermost runtime layer (it runs first, returns last).
func NewServerSentEventsHandler ¶
func NewServerSentEventsHandler[Cmd any]( decoder func(*http.Request) (Cmd, error), producer func(ctx context.Context, cmd Cmd, out chan<- SSEEvent) error, opts ...SSEHandlerOpt, ) http.Handler
NewServerSentEventsHandler wires a GET endpoint that streams events to the client. The producer receives a context (cancelled when the client disconnects) and a channel to write events into; the handler frames each event per the SSE spec and writes a keep-alive comment every heartbeat interval.
r.Get("/signals/stream",
kitrest.NewServerSentEventsHandler(
func(req *http.Request) (StreamCommand, error) {
return StreamCommand{Topic: req.URL.Query().Get("topic")}, nil
},
uc.Stream,
),
)
// usecase
func (uc *signalsUsecase) Stream(ctx context.Context, cmd StreamCommand, out chan<- rest.SSEEvent) error {
sub := uc.bus.Subscribe(cmd.Topic)
defer sub.Close()
for {
select {
case <-ctx.Done(): return nil
case sig := <-sub.C: out <- rest.SSEEvent{Event: "signal", Data: sig}
}
}
}
The handler closes the events channel when the producer returns or the client disconnects; the producer should not close it. Note that the http.Server's WriteTimeout must be 0 (disabled) for the route — otherwise long-lived streams are cut off. Wire with WithWriteTimeout(0) at server construction or behind a per-route opt-out if support for that lands.
func NewStreamingDownloadHandler ¶
func NewStreamingDownloadHandler[Cmd any]( cmdFn func(ctx context.Context, cmd Cmd) (*DownloadResponse, error), decoder func(*http.Request) (Cmd, error), opts ...HandlerOpt, ) http.Handler
NewStreamingDownloadHandler wires a GET endpoint whose response body is a raw byte stream (a document, an export, a generated report) — not a JSON:API document.
The usecase decodes path/query params into a typed Command, opens the underlying reader, and returns it; the handler is responsible for streaming, range support (`Accept-Ranges: bytes`, parsing the client's `Range:` header), and content headers.
r.Get("/documents/{id}/content",
kitrest.NewStreamingDownloadHandler(
uc.Download,
func(req *http.Request) (DownloadCommand, error) {
return DownloadCommand{ID: req.PathValue("id")}, nil
},
),
)
func NewTracingTransport ¶
func NewTracingTransport(monitor monitoring.Monitor, base http.RoundTripper) http.RoundTripper
NewTracingTransport returns an http.RoundTripper that injects the current span's trace context into outbound request headers. Wrap an existing transport (default http.DefaultTransport) with WithTransport. Use it via WithHTTPClient(&http.Client{Transport: NewTracingTransport(...)}).
func NewUpdateHandler ¶
func QueryOptsFromReq ¶
func RequestIDFromContext ¶
RequestIDFromContext returns the request id installed by the RequestIDMiddleware, or "" if no middleware ran.
func RestJSONEncoder ¶
func UnmarshalPayloadFromRequest ¶
UnmarshalPayloadFromRequest decodes a JSON:API document from the request body into DTO. Convenient one-liner for Command-handler decoders so they don't need to plumb `bytes.NewReader(io.ReadAll(req.Body))` or remember the kit error shape on a malformed body. Returns the decoded primary-data DTO; an InvalidArgument kit error on parse failure.
func decodeInviteMember(req *http.Request) (app.InviteMemberCommand, error) {
body, err := kitrest.UnmarshalPayloadFromRequest[api.InviteMemberRequest](req)
if err != nil {
return app.InviteMemberCommand{}, err
}
return app.InviteMemberCommand{
WorkspaceID: req.PathValue("id"),
AccountID: body.AccountID(),
Role: body.Role(),
}, nil
}
func WithAddress ¶
func WithAddress(address string) serverOption
WithAddress sets the address the inner *http.Server will listen to
func WithClientReqInterceptors ¶
func WithClientReqInterceptors(reqInterceptors ...ClientRequestInterceptor) clientOption
func WithClientReqTimeout ¶
func WithClientResInterceptors ¶
func WithClientResInterceptors(resInterceptors ...ClientResponseInterceptor) clientOption
func WithControllers ¶
func WithControllers(controllers ...Controller) serverOption
WithControllers adds Controllers whose Routes(Router) methods register grouped routes + scoped middleware on the server's root router.
func WithEndpoints ¶
func WithEndpoints(endpoints ...Endpoint) serverOption
WithEndpoints adds the provided endpoints to the endpoint list
func WithErrorEncoder ¶
func WithErrorEncoder(encoder ErrorEncoder) authMiddlewareOption
func WithHTTPClient ¶
func WithIdleTimeout ¶
WithIdleTimeout caps how long an idle keep-alive connection can sit before the server closes it.
func WithJSONAPIIncludes ¶
WithJSONAPIIncludes is middleware that extracts the 'include' query parameter from the HTTP request and adds it to the context for later use by JSON:API encoders
func WithMaxConnections ¶
func WithMaxConnections(n int) serverOption
WithMaxConnections caps the number of concurrent accepted connections. 0 disables the cap. Read by the fx module wrapper which wraps the listener with netutil.LimitListener.
func WithMaxHeaderBytes ¶
func WithMaxHeaderBytes(n int) serverOption
WithMaxHeaderBytes overrides the maximum size of request headers.
func WithMiddlewares ¶
func WithMiddlewares(middlewares ...Middleware) serverOption
WithMiddlewares adds the provided rest middlewares to the middleware list
func WithRateLimitIdleTTL ¶ added in v0.1.1
func WithRateLimitKeyFunc ¶ added in v0.1.1
func WithRateLimitKeyFunc(fn KeyFunc) rateLimitOption
func WithReadHeaderTimeout ¶
WithReadHeaderTimeout caps the time a client may take to send the request line + headers. Defaults to defaultReadHeaderTimeout.
func WithReadTimeout ¶
WithReadTimeout caps the time taken to read the full request body. Defaults to defaultReadTimeout. Set 0 to disable.
func WithShutdownTimeout ¶
WithShutdownTimeout sets the shutdown deadline
func WithTLSConfig ¶
WithTLSConfig sets the TLS configuration of the inner *http.Server
func WithTimeoutMessage ¶ added in v0.1.1
func WithTimeoutMessage(msg string) timeoutOption
WithTimeoutMessage overrides the body returned when the deadline fires.
func WithWriteTimeout ¶
WithWriteTimeout caps the time taken to write the response. Defaults to defaultWriteTimeout. Set 0 to disable — required for SSE, long-poll or any other streaming response.
func WriteJSONAPI ¶
func WriteJSONAPI(w http.ResponseWriter, status int, model any) error
WriteJSONAPI marshals model as a single-resource jsonapi document and writes it to w with the given status. Uses the same buffer-first-then-flush pattern the kit handlers use so a marshal failure can still produce a clean JSON:API error (no torn response).
Exported as an escape hatch for handlers that don't fit Create/Get/List/Patch/Update/Delete or Command/Bulk shapes — rare, but real for things like multi-resource composite endpoints or webhook receivers that need to return a small ack.
func WriteJSONAPIError ¶
func WriteJSONAPIError(w http.ResponseWriter, err error)
WriteJSONAPIError encodes err as a jsonapi error document and writes it to w with the appropriate status. Thin wrapper around JsonApiErrorEncoder for handlers that need to emit an error from outside the kit handler factories.
Types ¶
type AccessLogMiddleware ¶
type AccessLogMiddleware struct {
// contains filtered or unexported fields
}
AccessLogMiddleware logs one line per request after the response is written. Place it close to the outside of the chain so duration covers the whole pipeline including auth + decoders.
func NewAccessLogMiddleware ¶
func NewAccessLogMiddleware(monitor monitoring.Monitor) *AccessLogMiddleware
type AnyEndpoint ¶
AnyEndpoint is a function that takes a request and returns a response
type AtomicOperation ¶
type AtomicOperation struct {
Op string `json:"op"`
Ref *AtomicRef `json:"ref,omitempty"`
Data map[string]any `json:"data,omitempty"`
}
AtomicOperation is one entry in a JSON:API Atomic Operations request body. Services that need true cross-resource atomicity wire a NewJsonApiAtomicOperationsHandler with a dispatcher that maps each operation to the right usecase call inside a single transaction.
See https://jsonapi.org/ext/atomic/ for the wire spec. We follow the spec's `op` field (add | update | remove); `data` carries the resource payload (for add / update) and the `ref` field can be used by callers to address an existing row by id.
type AtomicOperationResult ¶
AtomicOperationResult is the per-operation result returned to the client. The spec says the response is `atomic:results` with the same length and order as the request's `atomic:operations`.
type AtomicOperationsRequest ¶
type AtomicOperationsRequest struct {
Operations []AtomicOperation `json:"atomic:operations"`
}
AtomicOperationsRequest is the top-level envelope under the `urn:ietf:params:jsonapi:ext:atomic` extension namespace. The wire field is literally `atomic:operations`.
type AtomicOperationsResponse ¶
type AtomicOperationsResponse struct {
Results []AtomicOperationResult `json:"atomic:results"`
}
AtomicOperationsResponse is the top-level response envelope.
type AtomicRef ¶
AtomicRef points at an existing resource by type + id, used by the `update` and `remove` ops.
type AuthMiddleware ¶
type AuthMiddleware struct {
// contains filtered or unexported fields
}
func NewAuthMiddleware ¶
func NewAuthMiddleware(authenticator HTTPAuthenticator, opts ...authMiddlewareOption) *AuthMiddleware
NewAuthMiddleware creates a Middleware that runs the supplied authenticator against every incoming request. On authentication failure the configured errorEncoder writes the error response and the inner handler is not invoked.
Wire it into a Controller's Routes via Router.Use:
r.Use(kitrest.NewAuthMiddleware(authenticator))
or scope it to a single registration with With():
r.With(kitrest.NewAuthMiddleware(authenticator)).Post("/", h)
type BulkDeleteRequest ¶
type BulkDeleteRequest[F any] struct { Filter F `json:"filter"` }
BulkDeleteRequest is the canonical wire shape for `POST /resources:batchDelete`. Filter-only; no patch.
type BulkDeleteResponse ¶
type BulkDeleteResponse struct {
resource.RestDTO
RAffectedCount int `jsonapi:"attr,affectedCount"`
}
BulkDeleteResponse mirrors BulkUpdateResponse.
type BulkUpdateRequest ¶
BulkUpdateRequest is the canonical wire shape for `POST /resources:batchUpdate`: a filter (server-sided selector) plus a patch to apply. Services consume `BulkUpdateRequest[F, P]` via a Command-style decoder that builds the usecase command.
The kit doesn't impose a Filter or Patch struct shape — that lives in each service's app layer. F and P are whatever the service defines; this struct just nails down the envelope on the wire.
type BulkUpdateResponse ¶
type BulkUpdateResponse struct {
resource.RestDTO
RAffectedCount int `jsonapi:"attr,affectedCount"`
}
BulkUpdateResponse reports how many rows the bulk update touched. Services that need richer responses (per-row results, IDs of updated rows) should compose a custom Command handler instead.
type CORSMiddleware ¶
type CORSMiddleware struct {
// contains filtered or unexported fields
}
CORSMiddleware provides Cross-Origin Resource Sharing support.
Per the Fetch spec the response must include `Vary: Origin` on every cross-origin response so caches keyed on the request URL alone do not serve the wrong Access-Control-Allow-Origin to a different client. The header is emitted on every response from this middleware regardless of whether the request itself was cross-origin — a cached non-cross-origin response that later gets reused by a cross-origin client would otherwise inherit the wrong CORS headers.
type Checker ¶ added in v0.1.1
Checker reports whether a subsystem is healthy. Implementations should respect ctx (which the aggregator gives a short timeout) and return a descriptive error on failure.
type ClientEndpoint ¶
type ClientEndpoint interface {
HttpClient() *http.Client
Target() *url.URL
Path() string
Method() string
Encode(context.Context, *http.Request, any) error
Decode(context.Context, *http.Response) (response any, err error)
ReqInterceptors() []ClientRequestInterceptor
ResInterceptors() []ClientResponseInterceptor
ErrInterceptors() []ClientErrorInterceptor
Endpoint() AnyEndpoint
}
type ClientEndpointOpt ¶
type ClientEndpointOpt func(c *clientEndpoint)
func WithClientEndpointErrInterceptors ¶
func WithClientEndpointErrInterceptors(errInterceptors ...ClientErrorInterceptor) ClientEndpointOpt
func WithClientEndpointHttpClient ¶
func WithClientEndpointHttpClient(client *http.Client) ClientEndpointOpt
func WithClientEndpointReqInterceptors ¶
func WithClientEndpointReqInterceptors(reqInterceptors ...ClientRequestInterceptor) ClientEndpointOpt
func WithClientEndpointResInterceptors ¶
func WithClientEndpointResInterceptors(resInterceptors ...ClientResponseInterceptor) ClientEndpointOpt
func WithClientEndpointTarget ¶
func WithClientEndpointTarget(target *url.URL) ClientEndpointOpt
type ClientErrorInterceptor ¶
type ClientRequestInterceptor ¶
func ClientRequestWithBearer ¶
func ClientRequestWithBearer(token string) ClientRequestInterceptor
func ClientRequestWithContentType ¶
func ClientRequestWithContentType(mime string) ClientRequestInterceptor
func ClientRequestWithHeaders ¶
func ClientRequestWithHeaders(kvs ...string) ClientRequestInterceptor
func ClientRequestWithQueryParams ¶
func ClientRequestWithQueryParams(qParams url.Values) ClientRequestInterceptor
type Collector ¶
type Collector struct {
BasePath string // URL path to docs, default "/docs".
// CombineErrors can take a value of "oneOf" or "anyOf",
// if not empty it enables logical schema grouping in case
// of multiple responses with same HTTP status code.
CombineErrors string
// DefaultSuccessResponseContentType is a default success response content type.
// If empty, "application/json" is used.
DefaultSuccessResponseContentType string
// DefaultErrorResponseContentType is a default error response content type.
// If empty, "application/json" is used.
DefaultErrorResponseContentType string
// DefaultMethods list is used when handler serves all methods.
DefaultMethods []string
// OperationExtractor allows flexible extraction of OpenAPI information.
OperationExtractor func(h http.Handler) func(oc openapi.OperationContext) error
// Host filters routes by host, gorilla/mux can serve different handlers at
// same method, paths with different hosts. This can not be expressed with a single
// OpenAPI document.
Host string
// contains filtered or unexported fields
}
Collector extracts OpenAPI documentation from HTTP handler and underlying use case interactor.
func NewCollector ¶
NewCollector creates an instance of OpenAPI Collector.
func (*Collector) AnnotateOperation ¶
func (c *Collector) AnnotateOperation(method, pattern string, setup ...func(oc openapi.OperationContext) error)
AnnotateOperation adds OpenAPI operation configuration that is applied during collection, method can be empty to indicate any method.
func (*Collector) CollectOperation ¶
func (c *Collector) CollectOperation( method, pattern string, annotations ...func(oc openapi.OperationContext) error, ) (err error)
CollectOperation prepares and adds OpenAPI operation.
func (*Collector) HasAnnotation ¶
HasAnnotation indicates if there is at least one annotation registered for this operation.
func (*Collector) ServeHTTP ¶
func (c *Collector) ServeHTTP(rw http.ResponseWriter, _ *http.Request)
func (*Collector) SpecSchema ¶
func (c *Collector) SpecSchema() openapi.SpecSchema
SpecSchema returns OpenAPI specification schema.
type ContextTimeoutMiddleware ¶ added in v0.1.1
type ContextTimeoutMiddleware struct {
// contains filtered or unexported fields
}
ContextTimeoutMiddleware is a lighter alternative: it cancels the request context after the timeout but does NOT block the response writer. Handlers that respect ctx.Done() will return early; others run to completion. Use this when you want timeouts to control downstream calls (DB, RPC) but don't want a blocked response writer.
func NewContextTimeoutMiddleware ¶ added in v0.1.1
func NewContextTimeoutMiddleware(timeout time.Duration) *ContextTimeoutMiddleware
type Controller ¶
type Controller interface {
Routes(r Router)
}
Controller declares HTTP routes for an aggregate. The Routes method receives a Router and registers grouped routes + scoped middleware on it. fx-wired controllers are collected via NewFxController and invoked at NewServer time against the server's root router.
func (c *workspaceController) Routes(r rest.Router) {
r.Route("/v1/workspaces", func(r rest.Router) {
r.Use(c.auth, c.actor)
r.Post("/", c.create)
r.Route("/{id}", func(r rest.Router) {
r.Use(c.tenancy)
r.Delete("/", c.delete)
})
})
}
type DecodeRequestFunc ¶
type DownloadResponse ¶
type DownloadResponse struct {
ContentType string
Size int64
// Filename, when non-empty, populates the
// `Content-Disposition: attachment; filename="..."` header so
// browsers offer a save-as dialog.
Filename string
// Body is the reader for the response bytes. Closed by the
// handler after the body is fully written (or on range error).
Body io.ReadCloser
}
DownloadResponse is what a streaming-download usecase returns. The kit handler writes Content-Type / Content-Length / Content-Disposition off this struct, sets Accept-Ranges, parses Range: requests, and streams the body. Size is required so HEAD requests and Range validation can do their thing.
type EncodeResponseFunc ¶
type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
func NewEmptyHTTPEncoder ¶
func NewEmptyHTTPEncoder(statusCode int) EncodeResponseFunc
NewEmptyHTTPEncoder writes only the status code, no body. Safe without the writeBuffered() guard that the data-bearing encoders use — there's no marshalling step that could fail half-way and leave a torn response.
func NewHTTPEncoder ¶
func NewHTTPEncoder[I any]( f func(context.Context, http.ResponseWriter, I) error, opts ...encoderOpt, ) EncodeResponseFunc
type Endpoint ¶
Endpoint is a (method, path, handler) triple used by WithEndpoints to register singleton routes that don't belong to a Controller — /healthz, /readyz, or any handler attached at the server level.
func NewHealthEndpoint ¶
func NewHealthEndpoint() Endpoint
func ReadinessEndpoint ¶
ReadinessEndpoint constructs the /readyz Endpoint to register on the REST server.
type ErrorEncoder ¶
type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
type FxConfig ¶
type FxConfig struct {
HTTPAddress string `required:"true" envconfig:"HTTP_ADDRESS"`
}
type GetDecoderOpt ¶
type GetDecoderOpt func(c *getEncoderConfig)
GetDecoderOpt configures the Get-request decoder used by NewJsonApiGetHandler. Plumbed through HandlerWithGetDecoderOpts so callers can compose options at the handler-factory level.
func DecodeGetSkipURLPathID ¶
func DecodeGetSkipURLPathID() GetDecoderOpt
DecodeGetSkipURLPathID disables the "{id}" path-segment requirement in the Get-request decoder. Used by singleton endpoints (/me, /config, /session) where identity is resolved from the context rather than passed as a path id.
type HTTPAuthenticator ¶
type HandlerOpt ¶
type HandlerOpt func(c *handlerConfig)
func HandlerAllowsEmptyReq ¶
func HandlerAllowsEmptyReq(emptyReqAllowed bool) HandlerOpt
func HandlerWithErrorEncoder ¶
func HandlerWithErrorEncoder(ee ErrorEncoder) HandlerOpt
func HandlerWithGetDecoderOpts ¶
func HandlerWithGetDecoderOpts(opts ...GetDecoderOpt) HandlerOpt
HandlerWithGetDecoderOpts threads decoder options through the jsonapi Get-handler factory. The canonical use case is opting a singleton endpoint (/me, /config) out of the {id} path-segment requirement:
r.Get("/me",
kitrest.NewJsonApiGetHandler(uc, api.UserToDTO, nil,
kitrest.HandlerWithGetDecoderOpts(kitrest.DecodeGetSkipURLPathID()),
),
)
func HandlerWithSuccessStatus ¶
func HandlerWithSuccessStatus(code int) HandlerOpt
HandlerWithSuccessStatus overrides the success status code for handlers that support it (currently Command). Defaults vary per factory (Create = 201, Get/List/Update/Patch = 200, Delete = 204); Command defaults to 201 but a "operation on existing resource" command (POST /orders/{id}/cancel) often wants 200.
type Headerer ¶
Headerer is checked by DefaultErrorEncoder. If an error value implements Headerer, the provided headers will be applied to the response writer, after the Content-Type is set.
type KeyFunc ¶ added in v0.1.1
KeyFunc derives the rate-limit bucket key from a request. Defaults to the client IP (X-Forwarded-For first, then RemoteAddr).
type Middleware ¶
Middleware wraps an http.Handler with cross-cutting behaviour (authentication, logging, recovery, tracing, ...).
func DefaultObservabilityMiddlewares ¶
func DefaultObservabilityMiddlewares(monitor monitoring.Monitor) []Middleware
DefaultObservabilityMiddlewares returns the recommended middleware stack (outermost first): RequestID → AccessLog → Tracing → Recovery. Apply with WithMiddlewares(...).
func NewCORSMiddleware ¶
func NewCORSMiddleware() Middleware
NewCORSMiddleware creates a new CORS middleware that accepts any origin.
func NewCORSMiddlewareWithOrigins ¶
func NewCORSMiddlewareWithOrigins(origins []string) Middleware
NewCORSMiddlewareWithOrigins creates a CORS middleware with specific allowed origins. Cross-origin requests from origins not in the list receive a response with NO Access-Control-Allow-Origin header — the browser then blocks the request. The previous implementation echoed `m.allowedOrigins[0]` for disallowed origins, which silently granted CORS access to the wrong domain.
type MiddlewareFunc ¶
MiddlewareFunc is the adapter that lets a plain function satisfy the Middleware interface.
type MultipartFile ¶
type MultipartFile struct {
FileName string
ContentType string
Size int64 // -1 when the multipart part doesn't declare a length
Reader io.Reader
}
MultipartFile holds the file part extracted from a multipart/form-data request. The reader is owned by the multipart.Reader; the handler closes it after the usecase returns.
type OpenAPIPreparer ¶
type OpenAPIPreparer interface {
SetupOpenAPIOperation(oc openapi.OperationContext) error
}
OpenAPIPreparer defines http.Handler with OpenAPI information.
type ProbeOption ¶ added in v0.1.1
type ProbeOption func(*Probes)
func WithManualReadinessToggle ¶ added in v0.1.1
func WithManualReadinessToggle(r *Readiness) ProbeOption
WithManualReadinessToggle wires in an existing Readiness toggle. When set, readiness reports "not ready" while the toggle is off, regardless of the registered checks. Use this for graceful shutdown draining.
func WithProbeCheckTimeout ¶ added in v0.1.1
func WithProbeCheckTimeout(d time.Duration) ProbeOption
WithProbeCheckTimeout sets the per-check deadline. Default: 1s.
func WithProbeOverallTimeout ¶ added in v0.1.1
func WithProbeOverallTimeout(d time.Duration) ProbeOption
WithProbeOverallTimeout sets the total budget for a single probe request. Default: 3s.
type Probes ¶ added in v0.1.1
type Probes struct {
// contains filtered or unexported fields
}
Probes aggregates named liveness + readiness checks into HTTP handlers. Mount the handlers at /healthz (liveness) and /readyz (readiness) — a failed readiness check stops a load balancer from sending new traffic, a failed liveness check tells the orchestrator to restart the process.
probes := rest.NewProbes()
probes.AddReadiness("db", func(ctx context.Context) error { return db.PingContext(ctx) })
probes.AddLiveness("memory", checkers.MemoryHealthy)
router.Mount("/healthz", probes.LivenessHandler())
router.Mount("/readyz", probes.ReadinessHandler())
func NewProbes ¶ added in v0.1.1
func NewProbes(opts ...ProbeOption) *Probes
func (*Probes) AddLiveness ¶ added in v0.1.1
func (*Probes) AddReadiness ¶ added in v0.1.1
func (*Probes) LivenessEndpoint ¶ added in v0.1.1
LivenessEndpoint constructs the /healthz Endpoint for the REST server.
func (*Probes) LivenessHandler ¶ added in v0.1.1
func (p *Probes) LivenessHandler() http.HandlerFunc
LivenessHandler runs registered liveness checks. 200 when all pass, 503 when any fail or time out.
func (*Probes) ReadinessEndpoint ¶ added in v0.1.1
ReadinessEndpoint constructs the /readyz Endpoint for the REST server.
func (*Probes) ReadinessHandler ¶ added in v0.1.1
func (p *Probes) ReadinessHandler() http.HandlerFunc
ReadinessHandler runs registered readiness checks. 200 when all pass, 503 when any fail, time out, or the manual toggle is off.
type RateLimitMiddleware ¶ added in v0.1.1
type RateLimitMiddleware struct {
// contains filtered or unexported fields
}
RateLimitMiddleware caps requests per key using a token-bucket per bucket. Buckets are GC'd after idle to keep the map bounded.
func NewRateLimitMiddleware ¶ added in v0.1.1
func NewRateLimitMiddleware(rps float64, burst int, opts ...rateLimitOption) *RateLimitMiddleware
NewRateLimitMiddleware builds a per-key token bucket. `rps` is the steady refill rate; `burst` is the max queued tokens. Defaults: key by IP, GC idle buckets after 10 minutes.
type Readiness ¶
type Readiness struct {
// contains filtered or unexported fields
}
Readiness is a thread-safe ready/not-ready toggle backed by an HTTP handler. The fx module installs one as /readyz, registers it for /readyz, starts it as ready, then flips it to not-ready at the top of the shutdown hook so load balancers stop sending new traffic before the in-flight requests finish draining.
func NewReadiness ¶
func NewReadiness() *Readiness
NewReadiness returns a Readiness initialised to ready=true.
func (*Readiness) Handler ¶
func (r *Readiness) Handler() http.HandlerFunc
Handler returns the HTTP handler — 200 when ready, 503 when not.
type RecoveryMiddleware ¶
type RecoveryMiddleware struct {
// contains filtered or unexported fields
}
RecoveryMiddleware turns handler panics into 500 responses and records the panic on the active span. Without it a panicking handler kills only the goroutine but leaves the client hanging until ReadTimeout.
func NewRecoveryMiddleware ¶
func NewRecoveryMiddleware(monitor monitoring.Monitor) *RecoveryMiddleware
type RequestIDMiddleware ¶
type RequestIDMiddleware struct{}
RequestIDMiddleware ensures every request has an id, propagates it through ctx and into the response header. Pairs with the logger and tracer middlewares so log lines and spans share the same correlation id.
func NewRequestIDMiddleware ¶
func NewRequestIDMiddleware() *RequestIDMiddleware
type Router ¶
type Router interface {
// Route mounts a sub-router at prefix. The fn receives the
// sub-router and registers its own routes / nested groups /
// middleware against it; nothing leaks out to the parent except
// the registered routes themselves.
Route(prefix string, fn func(Router))
// With returns a sub-router whose subsequent registrations are
// wrapped with the given middleware, scoped just to the returned
// router. Doesn't mutate the parent. Idiom: r.With(authMW).Post(...).
With(mw ...Middleware) Router
// Use adds middleware cumulatively to the current router's scope.
// All subsequent registrations on this router (and any nested
// Route()) inherit it.
Use(mw ...Middleware)
// Per-method handler registration. Path is joined onto the
// current scope's prefix.
Get(path string, h http.Handler)
Post(path string, h http.Handler)
Put(path string, h http.Handler)
Patch(path string, h http.Handler)
Delete(path string, h http.Handler)
// Method is the generic registration helper. Use for non-standard
// verbs (HEAD, OPTIONS) when you really need them.
Method(method, path string, h http.Handler)
// Mount attaches an arbitrary http.Handler at prefix. The handler
// receives requests with the prefix stripped (via
// http.StripPrefix). Useful for sub-services, file servers, or a
// third-party router we want to delegate to.
Mount(prefix string, h http.Handler)
}
Router is the registration surface for HTTP routes. A Controller receives a Router and declares endpoints via Get/Post/Put/Patch/ Delete/Method, grouping them by URL prefix with Route(), stacking middleware with Use() (cumulative for the scope) or With() (one-off for a single registration chain).
The Router collects registrations during the boot phase; at server build time the tree is flattened into a single http.ServeMux. There is no runtime routing layer beyond what stdlib provides — the Router is just an accumulator that knows how to compose path prefixes and middleware stacks before handing off to mux.Handle.
Path conventions:
- prefix on Route() and path on Get/Post/... are joined naively with "/"; leading/trailing slashes are normalised so callers don't have to think about it
- stdlib mux path params ({id}, {key...}) flow through unchanged
- the empty path "" on a leaf method (e.g. r.Get("", h)) means "the bare prefix" — handy inside a Route("/{id}", ...) block
Middleware ordering: the first middleware in a Use(a, b, c) call is the OUTERMOST (runs first, returns last). Chained scopes concatenate root-to-leaf: a route registered inside Route("/a", func(r){ r.Use(mwA); r.Route("/b", func(r){ r.Use(mwB); r.Get("/c", h) }) }) is wrapped as mwA(mwB(h)) — outer scope's middleware is outermost.
type SSEEvent ¶
SSEEvent is one frame sent to the client. ID is optional; clients reuse the last seen id when reconnecting via the `Last-Event-ID` header. Event is the optional event name (clients listen via `eventSource.addEventListener("name", ...)`); Data is the payload.
type SSEHandlerOpt ¶
type SSEHandlerOpt func(*sseConfig)
SSEHandlerOpt configures NewServerSentEventsHandler.
func SSEWithHeartbeatInterval ¶
func SSEWithHeartbeatInterval(d time.Duration) SSEHandlerOpt
SSEWithHeartbeatInterval overrides the keep-alive comment cadence (default 25s). Set 0 to disable heartbeats entirely.
type StatusCoder ¶
type StatusCoder interface {
StatusCode() int
}
StatusCoder is checked by DefaultErrorEncoder. If an error value implements StatusCoder, the StatusCode will be used when encoding the error. By default, StatusInternalServerError (500) is used.
type TimeoutMiddleware ¶ added in v0.1.1
type TimeoutMiddleware struct {
// contains filtered or unexported fields
}
TimeoutMiddleware enforces a per-request deadline. When the handler hasn't finished by the time limit, the request context is cancelled — handlers respecting ctx.Done() will return, and the client sees 503 Service Unavailable.
func NewTimeoutMiddleware ¶ added in v0.1.1
func NewTimeoutMiddleware(timeout time.Duration, opts ...timeoutOption) *TimeoutMiddleware
type TracingMiddleware ¶
type TracingMiddleware struct {
// contains filtered or unexported fields
}
TracingMiddleware extracts trace context from request headers, starts a server span around the handler, and records status + error attributes.
func NewTracingMiddleware ¶
func NewTracingMiddleware(monitor monitoring.Monitor) *TracingMiddleware
type UploadHandlerOpt ¶
type UploadHandlerOpt func(*uploadConfig)
UploadHandlerOpt configures NewMultipartUploadHandler. Options cover the field name + the multipart memory cap.
func UploadWithFileField ¶
func UploadWithFileField(name string) UploadHandlerOpt
UploadWithFileField overrides the form-field name for the file part (default "file"). Useful when integrating with a client that uses a non-standard field name.
func UploadWithMaxMemory ¶
func UploadWithMaxMemory(bytes int64) UploadHandlerOpt
UploadWithMaxMemory caps the in-memory portion of the multipart parser. Set higher for endpoints that accept many small fields, lower to push spooling to /tmp sooner.
Source Files
¶
- auth_middleware.go
- bulk_handler.go
- client.go
- collector.go
- command_handler.go
- controller.go
- cors_middleware.go
- decoder.go
- doc.go
- encoder.go
- error.go
- handler.go
- handlers_convenience.go
- health.go
- jsonapi_error.go
- jsonapi_handler.go
- jsonapi_helpers.go
- jsonapi_middleware.go
- middleware.go
- module.go
- observability.go
- probes.go
- ratelimit_middleware.go
- readiness.go
- router.go
- server.go
- sse_handler.go
- timeout_middleware.go
- upload_download_handler.go