web

package
v0.47.0 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package web provides JSON HTTP adapter composition for net/http handlers.

Handlers return rslt.Result[Response] instead of writing to ResponseWriter, making them testable expressions that return values. Adapt bridges handlers to http.HandlerFunc at the registration boundary.

This package helps build HTTP adapters — the transport boundary layer. Domain logic should live in separate functions that handlers call. JSON request/response only — not a general web framework.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Adapt

func Adapt(h Handler, opts ...AdaptOption) http.HandlerFunc

Adapt converts a Handler into an http.HandlerFunc.

Rendering: marshals Body to buffer via json.Marshal before writing any response bytes. If marshaling fails, returns 500 (no partial writes).

Error rendering flow (uses errors.As, not type assertion):

  1. errors.As(err, &webErr) → render *Error directly (with Headers, Details)
  2. Else if ErrorMapper set → call mapper
  3. If mapper returns (*Error, true) → render that
  4. Else → 500 with generic "internal error"

Panics if h is nil.

func BadRequest

func BadRequest(msg string) error

BadRequest returns a 400 error.

func Conflict

func Conflict(msg string) error

Conflict returns a 409 error.

func DecodeJSON

func DecodeJSON[T any](req *http.Request) rslt.Result[T]

DecodeJSON reads and JSON-decodes the request body into T.

Policy:

  • Content-Type: accepts application/json, application/json;charset=utf-8, and application/*+json variants. Missing Content-Type is accepted (lenient for ad hoc clients). Wrong Content-Type returns 415.
  • Max body size: 1MB (returns 413 if exceeded)
  • Disallows unknown fields (returns 400)
  • Empty body returns 400
  • Malformed JSON returns 400
  • Trailing garbage after valid JSON returns 400

func DecodeJSONWith

func DecodeJSONWith[T any](req *http.Request, opts DecodeOpts) rslt.Result[T]

DecodeJSONWith overrides default decode policy.

func Forbidden

func Forbidden(msg string) error

Forbidden returns a 403 error.

func NotFound

func NotFound(msg string) error

NotFound returns a 404 error.

func StatusError

func StatusError(status int, code, msg string) error

StatusError returns an error with the given status, code, and message.

func Steps

func Steps[T any](fns ...func(T) rslt.Result[T]) func(T) rslt.Result[T]

Steps chains functions in order, short-circuiting on first error. Each step may validate, normalize, or transform the value. Zero steps returns identity (rslt.Ok). Panics if any fn is nil. For aggregated validation (collecting all errors), write a single step that runs checks and returns a structured *Error with Details.

Types

type AdaptOption

type AdaptOption func(*adaptConfig)

AdaptOption configures Adapt behavior. Scope is strictly response/error rendering. Cross-cutting concerns (logging, metrics, tracing, auth) belong in standard func(http.Handler) http.Handler middleware.

func WithErrorMapper

func WithErrorMapper(fn func(error) (*Error, bool)) AdaptOption

WithErrorMapper maps domain errors to *Error at the adapter boundary. Only called for errors that are NOT already *Error (transport errors from DecodeJSON, validation errors that are already *Error bypass this). Return (*Error, true) to handle, or (nil, false) to fall through to 500. At most one mapper per Adapt call; last wins if called multiple times. Panics if fn is nil.

type ClientError

type ClientError struct {
	Error   string `json:"error"`
	Code    string `json:"code,omitempty"`
	Details any    `json:"details,omitempty"`
}

ClientError is the JSON shape written to the client for all errors. Extensible via the Details field for field-level validation, rate-limit metadata, or any structured payload.

type DecodeOpts

type DecodeOpts struct {
	MaxBytes     int64 // 0 = use default (1MB)
	AllowUnknown bool  // false = reject unknown fields (default)
}

DecodeOpts provides per-call overrides for decode policy.

type Error

type Error struct {
	Status  int
	Message string
	Code    string
	Details any
	Headers http.Header // optional response headers (WWW-Authenticate, Retry-After)
	Err     error       // internal cause, never sent to client
}

Error carries an HTTP status code, client-safe message, and optional structured details. Message is what the client sees. Err (if set) is for logs and errors.Is/As only — never sent to clients.

func (*Error) Error

func (e *Error) Error() string

Error returns the client-safe message. If an internal cause is set, it is included for logging but should not be exposed to clients.

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap returns the internal cause for errors.Is/As.

type Handler

type Handler = func(*http.Request) rslt.Result[Response]

Handler is a function from request to result. Not pure in the FP sense (*http.Request has mutable state), but more functional than ResponseWriter mutation — handlers are testable expressions that return values.

type Response

type Response struct {
	Status  int
	Headers http.Header // nil means no extra headers; copied in Adapt
	Body    any         // JSON-serialized by Adapt; nil means no body
}

Response is the return value of a handler — status + headers + body. Body is any because a generic Response[T] would infect the entire Handler signature. This is a boundary escape hatch, not full type safety. Constructors are generic to preserve call-site type intent.

func Created

func Created[T any](body T) Response

Created returns a 201 Response with the given body.

func JSON

func JSON[T any](status int, body T) Response

JSON returns a Response with the given status and body.

func NoContent

func NoContent() Response

NoContent returns a 204 Response with no body.

func OK

func OK[T any](body T) Response

OK returns a 200 Response with the given body.

Jump to

Keyboard shortcuts

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