Help us shape the future of Go! Take the 2026 Go Developer Survey

vial

package module
v1.0.0-rc.1 Latest Latest
Warning

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

Go to latest
Published: Sep 6, 2026 License: MIT Imports: 26 Imported by: 0

README

Vial

CI

Vial is a Flask-inspired framework for Go's standard net/http package. It adds error-returning handlers, route groups, application lifecycle, background tasks, test helpers, and a rebuild loop without replacing Go's HTTP types.

Use Vial when an application needs more structure than raw net/http but must still work with standard handlers, middleware, contexts, response writers, and httptest. The core HTTP package uses only the standard library; optional batteries may use focused dependencies.

Capabilities

  • Error-returning handlers: func(*vial.Context) error
  • Standard Go middleware composition
  • Named, method-aware routes and path parameters through http.ServeMux
  • Route groups with scoped middleware
  • Named application modules with isolated route registrars
  • Collision-safe typed context values and trusted-proxy client IPs
  • Supervised startup, shutdown, background tasks, liveness, and readiness
  • RFC 7240 asynchronous operations with polling and cancellation
  • Bounded Server-Sent Event fan-out with heartbeats and slow-consumer policy
  • Lifecycle-aware coder/websocket handlers with origin and message limits
  • grpc-go lifecycle modules with message limits, h2c, and graceful shutdown
  • Encrypted cookie sessions with secure defaults, flash values, and key rotation
  • Provider-neutral request identities and authentication/grant guards
  • Restrictive browser security headers and bounded local rate limiting
  • Route-bounded OpenMetrics, native HTTP tracing integration, and correlated IDs
  • database/sql transactions, readiness wiring, and embedded forward migrations
  • OpenAPI 3.1 generation from routes and Go request and response types
  • JSON, text, redirects, and empty responses
  • Cached path, query, header, cookie, form, multipart, and JSON binding
  • Centralized HTTP errors and transport-neutral application faults
  • Request IDs, structured logging, panic recovery, and configurable CORS
  • Graceful HTTP shutdown
  • Raw http.Handler mounting
  • Standard httptest compatibility
  • Project creation, route/config diagnostics, and OpenAPI export through vial
  • vial dev automatic build-and-restart loop
  • vial load bounded HTTP load checks and CI thresholds
  • Last-known-good process remains online after compilation failures
  • No runtime dependencies in the core HTTP package

Examples

  1. examples/hello is the smallest runnable app and introduces the development loop.
  2. examples/json-api demonstrates JSON binding and errors. The module, task, upload, SSE, config, secure-cookie, security, CSRF, and template examples each cover one concern.
  3. examples/async demonstrates submission, Prefer: wait, polling, cancellation, ownership, idempotency, readiness, and metrics with the bounded in-memory executor.
  4. examples/sse uses the bounded sse.Hub with a standard EventSource response.
  5. examples/websocket uses coder/websocket through a standard handler with Vial middleware, limits, and graceful shutdown.
  6. examples/grpc shares one h2c listener with HTTP routes and demonstrates standard interceptors, TLS, streaming, and graceful shutdown.
  7. examples/observability exposes HTTP metrics and correlates request and W3C trace IDs in structured logs.
  8. examples/database wires PostgreSQL lifecycle, readiness, transactions, and embedded migrations.
  9. examples/openapi generates and serves an OpenAPI 3.1 document from named routes and Go types.
  10. vial-gateway and vialboard are complete applications.

Project status

Vial 1.0.0-rc.1 is a release candidate. Its public API is frozen except for correctness and security fixes; stable 1.0 still requires downstream validation and production burn-in. Vial began as a learning project and accepts AI-assisted contributions. Those changes go through the same tests, review, security reporting, and compatibility policy as any other contribution. Read the release notes before using a pre-1.0 version in production.

Run it

The module lives at github.com/jrgf/go-vial.

git clone https://github.com/jrgf/go-vial.git
cd go-vial

make install
vial version

go test ./...
vial dev ./examples/hello

make install places vial in Go's binary directory (GOBIN, or $GOPATH/bin by default), which must be on PATH.

Open:

http://localhost:8080
http://localhost:8080/users/42

Change the response in examples/hello/main.go and save. The development runner will build a candidate binary, gracefully stop the previous application only after compilation succeeds, and start the replacement.

A failed build behaves like this:

[vial] source change detected; rebuilding
[vial] building ./examples/hello
examples/hello/main.go:20:2: undefined: broken
[vial] build failed; last successful application remains running

Minimal application

package main

import (
    "context"
    "log"
    "net/http"

    "github.com/jrgf/go-vial"
    "github.com/jrgf/go-vial/middleware"
)

func main() {
    app := vial.New(
        vial.WithDisallowUnknownJSONFields(true),
    )

    app.Use(
        middleware.RequestID(),
        middleware.Logger(),
        middleware.Recover(),
    )

    app.Get("/", func(c *vial.Context) error {
        return c.JSON(http.StatusOK, map[string]string{
            "message": "Hello from vial",
        })
    })

    app.Get("/users/{id}", func(c *vial.Context) error {
        return c.JSON(http.StatusOK, map[string]string{
            "id": c.Param("id"),
        })
    })

    if err := app.Run(context.Background(), ":8080"); err != nil {
        log.Fatal(err)
    }
}

App implements http.Handler, so it also works without the framework server helper:

server := &http.Server{
    Addr:    ":8080",
    Handler: app,
}

Route groups

api := app.Group("/api")
api.Use(authenticationMiddleware)

api.Get("/users/{id}", getUser, vial.RouteName("users.get"))
api.Post("/users", createUser)

Application middleware wraps all requests, including 404 and 405 responses. Group middleware wraps only endpoints registered through that group.

Use a route name to build a path for a redirect, link, or Location header:

location, err := app.URL("users.get", map[string]string{"id": "A/B"})
// location is /api/users/A%2FB

App.URL validates and freezes registration on its first call. Call it after registering all routes, or inside a handler through context.App().URL. Lookups reuse the immutable named-route index and support concurrent callers. Pass raw parameter values. Missing or extra parameters, empty or slash-only single segments, and dot segments return errors. {path...} preserves slashes, permits an empty tail or a trailing slash, and rejects leading or repeated slashes. Paths include group prefixes and omit hosts, schemes, queries, and fragments. Add query strings with net/url.Values.

Modules

A route group organizes HTTP endpoints. A module organizes one named business capability, such as users, billing, inventory, or administration.

type UserModule struct {
    service *UserService
}

func NewModule(service *UserService) *UserModule { return &UserModule{service: service} }

func (module *UserModule) Name() string { return "users" }

func (module *UserModule) Register(registrar *vial.Registrar) error {
    users := registrar.Group("/users")
    users.Get("/", module.list, vial.RouteName("users.list"))
    users.Post("/", module.create, vial.RouteName("users.create"))
    users.Get("/{id}", module.get, vial.RouteName("users.get"))
    return nil
}

Register modules before building or serving the application:

if err := app.Register(
    users.NewModule(userService),
    orders.NewModule(orderService),
); err != nil {
    return err
}

Registration is atomic. Each module gets an isolated registrar for routes, middleware, lifecycle hooks, supervised tasks, and health endpoints. See the runnable examples/modules.

Modules contain application functionality. Extensions provide technical infrastructure, such as sessions, databases, telemetry, authentication, task supervision, or a gRPC server. Vial does not require an extension interface; ordinary constructors and application options remain sufficient.

Realtime

The sse package writes events and provides bounded process-local fan-out. vialws closes coder/websocket connections with the Vial request lifecycle. vialgrpc mounts grpc-go with message limits and bounded graceful shutdown.

JSON binding

type CreateUserRequest struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

app.Post("/users", func(c *vial.Context) error {
    var request CreateUserRequest
    if err := c.BindJSON(&request); err != nil {
        return err
    }

    return c.JSON(http.StatusCreated, request)
})

The default body limit is 16 MiB. Applications can override it and reject unknown JSON fields:

app := vial.New(
	vial.WithMaxBodySize(10 << 20),
    vial.WithDisallowUnknownJSONFields(true),
)

Context.Bind combines fields tagged with path, query, header, cookie, and form with JSON or form bodies. Uploaded files use Context.FormFile; see the runnable examples/upload.

HTML templates

Keep parsing in html/template; render buffers the result before committing the response:

parsed, err := template.ParseFS(webFS, "templates/*.html")
if err != nil {
    return err
}
views := render.New(parsed)

app.Get("/", func(c *vial.Context) error {
    return views.HTML(c, http.StatusOK, "home", page)
})

The runnable examples/web adds a validated, CSRF-protected form and uses embed.FS with the native http.FileServerFS for static assets:

VIAL_ALLOW_INSECURE_COOKIE=1 vial dev ./examples/web

The insecure-cookie flag is only for local HTTP.

The session package provides authenticated and encrypted client-side sessions, one-time flash values, secure cookie defaults, and live key rotation. The runnable examples/securecookie module demonstrates the complete flow:

umask 077
openssl rand -hex 32 > /tmp/vial-session-keys
SESSION_KEYS_FILE=/tmp/vial-session-keys VIAL_ALLOW_INSECURE_COOKIE=1 vial dev ./examples/securecookie

The example reloads this file every minute and expires sessions after five minutes. Rotate with NEW_KEY,OLD_KEY, newest first; remove the old key after the longest configured session lifetime.

VIAL_ALLOW_INSECURE_COOKIE=1 is only for local HTTP. Sessions are encrypted but remain client-side and limited to one cookie. SameSite is defense in depth, not a replacement for CSRF protection.

Authentication and authorization

The auth package resolves an optional identity once per request and provides route guards for authentication and application-defined grants. It does not own users, passwords, tokens, or an authorization database. The examples/securecookie application demonstrates session-backed identity resolution with protected /me and /admin routes.

Security middleware

middleware.SecurityHeaders() adds a restrictive Content Security Policy, referrer and framing controls, MIME sniffing protection, and HSTS on direct TLS requests. middleware.RateLimit(...) provides bounded in-process token buckets keyed by Context.ClientIP() by default. The runnable examples/security application demonstrates both.

Use a gateway or shared store when a limit must span replicas.

Observability

middleware.HTTPMetrics records request counts, active requests, and a fixed duration histogram using registered route patterns instead of raw paths. App.UseHTTP accepts standard net/http middleware, including tracing libraries. middleware.TraceContext adds a validated trace ID to request state and structured logs alongside X-Request-ID.

The runnable examples/observability application exposes /metrics and demonstrates tracing and log correlation.

Database

The sqlkit package adds transaction and embedded migration helpers to database/sql. Existing application hooks own the pool lifecycle:

app.OnStart(db.PingContext, migrator.Migrate)
app.OnStop(func(context.Context) error { return db.Close() })
app.Readiness("/ready", db.PingContext)

sqlkit.InTx commits on success and rolls back on errors or panics. Migrator applies sorted .sql files once, stores SHA-256 checksums, and rejects edited history. See the runnable examples/database PostgreSQL application.

OpenAPI 3.1

The openapi package generates deterministic OpenAPI 3.1 JSON from App.Routes, RouteName, and Go request and response types. It understands Vial's path, query, header, cookie, form, and json binding tags.

err := openapi.Mount(app, "/openapi.json", openapi.Config{
    Title:   "Notes API",
    Version: "1.0.0",
    Operations: map[string]openapi.Operation{
        "notes.create": {
            Request: createNoteRequest{},
            Responses: map[int]openapi.Response{
                http.StatusCreated: {Body: note{}},
            },
        },
    },
})

openapi:"required" marks documented fields as required; application validation remains authoritative. Operation.RequestSchema and Response.Schema accept explicit JSON Schema for custom JSON types, constraints, and examples. Embedded fields and json:",string" follow Go's JSON encoding rules. See the runnable examples/openapi application.

Testing

Public API suites live in tests/, grouped by package. Private unit tests remain beside their source files. go test ./... includes both, and make coverage instruments production packages across the test tree.

testkit runs the full application lifecycle and cleans up automatically:

server := testkit.Start(t, app)
response := server.JSON(http.MethodPost, "/users", request)
response.RequireStatus(http.StatusCreated)

var created User
response.Decode(&created)

Raw net/http requests and httptest remain supported.

Async operations

Register a bounded executor once; Vial starts it before serving HTTP and drains accepted work during graceful shutdown:

executor := async.NewMemoryExecutor(
    async.WithWorkers(8),
    async.WithQueueSize(256),
    async.WithTaskTimeout(5*time.Minute),
)
executor.Handle("reports.generate", func(ctx context.Context, job async.Job) (any, error) {
    var request GenerateReportRequest
    if err := job.Decode(&request); err != nil {
        return nil, err
    }
    return reportService.Generate(ctx, request)
})
app.Async(executor)

app.Post("/reports", func(c *vial.Context) error {
    preference, err := vial.ParsePrefer(c.Header("Prefer"))
    if err != nil {
        return err
    }
    if !preference.RespondAsync {
        return vial.BadRequest("respond_async_required", "Use Prefer: respond-async")
    }
    operation, err := c.Async().Submit(c.Request().Context(), vial.SubmitRequest{
        Name:             "reports.generate",
        Payload:          GenerateReportRequest{ReportID: "report_123"},
        IdempotencyKey:   c.Header("Idempotency-Key"),
        IdempotencyScope: currentUserID(c),
        Metadata:         map[string]string{"user_id": currentUserID(c)},
    })
    if err != nil {
        return err
    }

    operation, completed, err := c.Await(operation, 3*time.Second)
    if err != nil {
        return err
    }
    if completed {
        if operation.Error != nil {
            return operation.Error
        }
        if operation.Status == vial.OperationCancelled {
            return vial.Conflict("operation_cancelled", "The operation was cancelled")
        }
        return c.JSON(http.StatusCreated, operation.Result)
    }
    return c.Accepted(operation)
})

authorize := func(c *vial.Context, operation *vial.Operation) error {
    if operation.Metadata["user_id"] != currentUserID(c) {
        return vial.NotFound("operation_not_found", "Operation not found")
    }
    return nil
}
app.Get("/operations/{id}", vial.OperationStatusHandler(executor, authorize))
app.Delete("/operations/{id}", vial.OperationCancelHandler(executor, authorize))
app.Get("/metrics/async", vial.AsyncMetricsHandler(executor))
app.Readiness("/ready", executor.Ready)

Accepted returns 202 Accepted with Location, Retry-After, and a status URL. A full queue returns 503 Service Unavailable; the same operation name, authenticated-owner scope, and idempotency key returns the existing operation. Prefer: respond-async, wait=3 waits up to the smaller client preference and server maximum before falling back to 202. Return resource URLs instead of embedding large results.

The memory executor is not durable. Every pending, running, completed, and idempotency record is lost when the process exits. Use it only when losing work is acceptable.

For durable work, use the PostgreSQL adapter with any database/sql PostgreSQL driver already selected by the application:

executor := asyncpostgres.New(db,
    asyncpostgres.WithWorkers(16),
    asyncpostgres.WithLeaseDuration(30*time.Second),
)
executor.Handle("reports.generate", generateReport)
app.Async(executor)

operation, err := executor.Submit(ctx, vial.SubmitRequest{
    Name:             "reports.generate",
    Payload:          request,
    IdempotencyKey:   idempotencyKey,
    IdempotencyScope: authenticatedUserID,
    Retry: vial.RetryPolicy{
        MaxAttempts:    5,
        InitialBackoff: time.Second,
        MaxBackoff:     time.Minute,
    },
})

The adapter creates its schema idempotently by default. It persists operations and results, leases rows with FOR UPDATE SKIP LOCKED, renews visibility while handlers run, recovers expired leases after crashes, supports multiple replicas, and leaves exhausted deliveries as inspectable failed operations. Disable automatic schema creation with asyncpostgres.WithAutoMigrate(false) after applying contrib/asyncpostgres/schema.sql through the application's migration system.

Background tasks

Keep shared queues application-owned; use contexts only for cancellation:

type Event struct {
    Name string
}

events := make(chan Event, 128)

app.Post("/events", func(c *vial.Context) error {
    select {
    case events <- Event{Name: "user.created"}:
        return c.NoContent(http.StatusAccepted)
    case <-c.Request().Context().Done():
        return c.Request().Context().Err()
    default:
        return vial.NewHTTPError(503, "queue_full", "Event queue is full")
    }
})

app.Go("event-consumer", func(ctx context.Context) error {
    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case event := <-events:
            if err := handleEvent(ctx, event); err != nil {
                return err
            }
        }
    }
})

This queue is in-memory and loses pending events when the process stops. Use a database outbox or external broker when events must be durable. See the runnable examples/events.

Configuration

Use config.Load with an initialized struct. Existing values are defaults, JSON files are applied in order, and environment variables win:

type AppConfig struct {
    Environment string `json:"environment" env:"VIAL_ENV"`
    HTTP        config.HTTP `json:"http"`
}

func (configuration *AppConfig) Validate() error {
    if configuration.Environment == "" {
        return errors.New("environment is required")
    }
    return nil
}

configuration := AppConfig{Environment: "development"}

if err := config.Load(
    &configuration,
    config.OptionalFile("config.json"),
); err != nil {
    log.Fatal(err)
}

if err := app.Run(context.Background(), configuration.HTTP.Address()); err != nil {
    log.Fatal(err)
}

An optional config.json can override the defaults:

{
  "http": {
    "port": 9000
  }
}

Environment variables take final precedence:

VIAL_HTTP_PORT=9090 go run .

Use config.File when a file is required. Configuration types may implement config.Validator; validation runs after all sources are loaded.

config.HTTP.Address() defaults to 127.0.0.1:8080. Production deployments that must accept external traffic can set VIAL_HTTP_HOST=0.0.0.0 explicitly. See the runnable examples/config application.

Errors

Handlers return an error. Business failures use transport-neutral faults:

return fault.New(
    fault.NotFound,
    "user_not_found",
    "The requested user was not found",
)

Use HTTPError only when an HTTP-specific status or response header is needed.

Default response:

{
  "error": {
    "code": "user_not_found",
    "message": "The requested user was not found"
  }
}

Unexpected errors are rendered as a generic 500 response. Their internal details remain in server logs. Unmatched routes and unsupported methods use the same error handler and return not_found and method_not_allowed codes; 405 responses include Allow.

Enable RFC 9457 Problem Details before building the application:

app.SetErrorHandler(vial.ProblemDetailsErrorHandler)

Errors then use application/problem+json with type: "about:blank", the HTTP status and title, a public detail, and the existing code and optional validation fields extensions. The renderer preserves Allow, Retry-After, and authentication challenges. Internal causes, fault metadata, and request URLs stay out of the response. Explicit HTTPError.Message values remain public. Applications using the default renderer retain the existing JSON envelope. See examples/openapi for named Location headers, Problem Details, and matching response schemas.

Mount standard handlers

app.HandleHTTP("GET /health", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
    w.WriteHeader(http.StatusNoContent)
}))

Mount a raw handler for existing middleware, metrics endpoints, profilers, and other net/http integrations.

Command-line workflow

Build the CLI:

go install ./cmd/vial

Create a minimal service:

vial new --module example.com/service ./service
cd ./service
go mod tidy
go run .

The generated service includes liveness, readiness, request IDs, logging, panic recovery, security headers, and graceful shutdown.

Development runner

Usage:

vial dev [flags] [package] [-- application arguments]

Examples:

vial dev ./examples/hello
vial dev --verbose ./cmd/server
vial dev --debounce 500ms ./cmd/server
vial dev --exclude generated ./cmd/server
vial dev --watch '*.html' --watch '*.css' ./examples/web
vial dev ./cmd/server -- --config ./config/dev.json

Flags:

Flag Default Meaning
--root current directory Directory to watch and use as the build working directory
--debounce 250ms Quiet period before rebuilding
--restart-timeout 3s Time allowed for graceful child shutdown
--exclude none Additional ignored name or path; repeatable
--watch none Additional filename or root-relative path pattern; repeatable
--verbose false Print each relevant changed path

The watcher recursively scans these files by default:

  • *.go
  • go.mod
  • go.sum
  • go.work
  • go.work.sum

Use --watch '*.html', --watch '*.css', or --watch '*.sql' to rebuild after embedded assets change. Patterns without / match filenames at any depth; patterns with /, such as static/*.css, match paths relative to --root. Patterns use Go's path.Match syntax: * does not cross /, and ** has no special meaning. Use forward slashes on every platform and quote patterns to prevent shell expansion. Added, modified, and deleted files trigger rebuilds; exclusions still take precedence.

It ignores:

  • .git
  • .vial
  • vendor
  • node_modules
  • tmp
  • dist

Generated binaries are written under .vial/bin and are ignored by the watcher.

Inspect routes without starting the server:

vial routes ./examples/hello
vial routes --json ./examples/hello

This command supports applications that call App.Run; applications using a custom http.Server can call App.Routes directly.

Validate configuration and application build setup without starting the server:

vial doctor ./examples/config
vial doctor --json ./examples/config
vial config --json ./examples/config

config reports validation only. It never prints application-owned values or secrets.

Export the OpenAPI document served by an application without binding a port:

vial openapi ./examples/openapi
vial openapi --output openapi.json ./examples/openapi

The default endpoint is /openapi.json; override it with --path. Export builds the application but does not run startup or shutdown hooks.

Run a bounded load check against a deployed endpoint:

vial load --workers 2000 --duration 30s --timeout 5s http://localhost:8080/
vial load --max-error-rate 1 --max-p95 250ms http://localhost:8080/
CLI contract
  • vial, help, --help, -h, and subcommand help exit with status 0.
  • Unknown commands, invalid arguments, runtime failures, and failed load thresholds exit with status 1.
  • vial new --json writes directory, module, go, and vial fields.
  • vial routes --json writes an indented JSON array of vial.Route values to standard output.
  • vial doctor --json writes ok, routes, named_routes, and go fields.
  • vial config --json writes {"valid": true} after application construction and route validation succeed.
  • vial openapi writes an OpenAPI 3.1 JSON document to standard output or the file selected by --output.
  • vial version --verbose writes stable version=, commit=, and go= lines to standard output.
  • vial version --json writes version, commit, and go fields.
  • vial load writes its final summary to standard output and progress to standard error, keeping redirected summaries clean.

Human-readable help, tables, progress text, and error wording may improve within 1.x. Exit behavior and the machine-readable formats above are compatibility contracts.

Replacement sequence
source change
    ↓
debounce
    ↓
build unique candidate binary
    ├── failure → retain current process
    └── success
            ↓
       interrupt current process
            ↓
       wait up to restart timeout
            ↓
       start candidate

Builds never run concurrently. Changes detected during a build remain queued for the next pass.

Repository layout

.
├── app.go                 # application, registration, and net/http adapter
├── context.go             # request and response helpers
├── binding.go             # query, form, multipart, and JSON body limits
├── errors.go              # HTTP error model and renderer
├── server.go              # server lifecycle and graceful shutdown
├── auth/                  # request identities and authorization guards
├── session/               # encrypted client-side cookie sessions
├── sqlkit/                # database/sql transactions and migrations
├── openapi/               # OpenAPI 3.1 generation and document handler
├── sse/                   # bounded Server-Sent Event fan-out
├── vialws/                # coder/websocket lifecycle adapter
├── vialgrpc/              # grpc-go lifecycle module
├── middleware/            # request ID, logging, recovery, browser policy, and rate limits
├── internal/dev/          # watcher, builder, runner, and process control
├── cmd/vial/              # development and load-check CLI
├── tests/                 # public API tests, grouped by package
└── examples/              # runnable applications

Verification

go test ./...
go test -race ./...
go vet ./...

Use the existing protocol client at the integration boundary. Extra Vial wrappers would hide behavior that applications need to verify.

Battery Supported test path
HTTP, sessions, authentication, security, observability testkit.Start, Server.JSON, Server.Multipart, and RequireRoute
SSE sse.Hub, testkit.Start, http.Client, and bufio.Reader
WebSocket vialws.NewHandler, testkit.Start, and coder/websocket
gRPC vialgrpc.New, testkit.Start, and the generated grpc-go client
SQL database/sql with a test driver; run database integration tests for the selected production driver
Async operations In-memory executor tests plus the selected persistent adapter's integration tests
OpenAPI Generate or fetch JSON, decode it, and assert the documented operation and schema

The maintained examples under examples/ are runnable reference tests for each row.

CI runs tests on Linux, Windows, and macOS with both supported Go versions.

Known limitations

  • Development change detection uses recursive polling rather than native filesystem events.
  • Non-Go assets require explicit --watch patterns; go:embed directives are not discovered automatically.
  • Windows child replacement uses direct termination; graceful console-event delivery is a later enhancement.
  • Route registration becomes immutable after the application is built or first served.

Go support

Vial requires Go 1.26.6 or newer and currently tests Go 1.26 and Go 1.27.

Contributing

See CONTRIBUTING.md for development setup, quality requirements, and pull request guidance. Report vulnerabilities privately through SECURITY.md.

License

MIT

Documentation

Overview

Package vial is a small web application framework built on net/http. It adds error-returning handlers, middleware, request helpers, and graceful shutdown.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrAsyncUnavailable   = errors.New("async executor is unavailable")
	ErrAsyncQueueFull     = errors.New("async executor queue is full")
	ErrInvalidOperation   = errors.New("invalid async operation")
	ErrOperationNotFound  = errors.New("operation not found")
	ErrOperationFinished  = errors.New("operation is already finished")
	ErrRetriesUnsupported = errors.New("async executor does not support retries")
	ErrInvalidPreference  = errors.New("invalid Prefer header")
)

Functions

func PrefersRespondAsync added in v0.16.0

func PrefersRespondAsync(header string) bool

PrefersRespondAsync reports whether Prefer requests RFC 7240 respond-async.

func ProblemDetailsErrorHandler

func ProblemDetailsErrorHandler(context *Context, err error)

ProblemDetailsErrorHandler renders RFC 9457 application/problem+json errors. Enable it with App.SetErrorHandler before building the application. It uses the default status and safe-message mapping, with code and fields extensions. Error causes and request URLs are not included. Already committed responses are left intact, and failures are logged like the default error handler.

func StatusCode

func StatusCode(err error) int

StatusCode returns the HTTP status represented by err, defaulting to 500.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App is the framework application and implements http.Handler.

func New

func New(options ...Option) *App

New creates an application with the supplied options.

func (*App) Async added in v0.16.0

func (app *App) Async(executor AsyncExecutor)

Async registers the application's executor. Lifecycle executors are started before HTTP serving and drained during application shutdown.

func (*App) AsyncExecutor added in v0.16.0

func (app *App) AsyncExecutor() AsyncExecutor

AsyncExecutor returns the registered executor, or nil when none is configured.

func (*App) Build

func (app *App) Build() error

Build validates and freezes application registrations.

func (*App) Delete

func (app *App) Delete(path string, handler Handler, options ...RouteOption)

Delete registers a DELETE route.

func (*App) Get

func (app *App) Get(path string, handler Handler, options ...RouteOption)

Get registers a GET route.

func (*App) Go added in v0.7.0

func (app *App) Go(name string, task Task, options ...TaskOption)

Go registers a named background task. Tasks are critical by default and begin running when the application starts.

func (*App) Group

func (app *App) Group(prefix string) *Group

Group creates a route group with a shared path prefix.

func (*App) Handle

func (app *App) Handle(method, path string, handler Handler, options ...RouteOption)

Handle registers a route for an HTTP method and path.

func (*App) HandleHTTP

func (app *App) HandleHTTP(pattern string, handler http.Handler, options ...RouteOption)

HandleHTTP mounts a standard-library handler using a native ServeMux pattern. Examples include "/metrics", "GET /health", and "api.example.com/".

func (*App) Health added in v0.15.0

func (app *App) Health(path string)

Health registers a dependency-free liveness endpoint.

func (*App) OnStart added in v0.4.0

func (app *App) OnStart(hooks ...LifecycleHook)

OnStart registers hooks that run in registration order before components start.

func (*App) OnStop added in v0.4.0

func (app *App) OnStop(hooks ...LifecycleHook)

OnStop registers hooks that run in reverse registration order after components stop.

func (*App) Options

func (app *App) Options(path string, handler Handler, options ...RouteOption)

Options registers an OPTIONS route.

func (*App) Patch

func (app *App) Patch(path string, handler Handler, options ...RouteOption)

Patch registers a PATCH route.

func (*App) Post

func (app *App) Post(path string, handler Handler, options ...RouteOption)

Post registers a POST route.

func (*App) Put

func (app *App) Put(path string, handler Handler, options ...RouteOption)

Put registers a PUT route.

func (*App) Readiness added in v0.15.0

func (app *App) Readiness(path string, checks ...HealthCheck)

Readiness registers an endpoint that is ready only while the application is running and every check succeeds within its configured timeout.

func (*App) Register added in v0.4.0

func (app *App) Register(modules ...Module) error

Register stages modules and commits their routes atomically.

func (*App) Routes added in v0.2.0

func (app *App) Routes() ([]Route, error)

Routes validates the application and returns registered route metadata in registration order. The returned slice does not share state with the app.

func (*App) Run

func (app *App) Run(contextValue context.Context, address string) error

Run listens on address and serves until the parent context or a supported OS shutdown signal is received.

func (*App) Serve

func (app *App) Serve(contextValue context.Context, listener net.Listener) (serveErr error)

Serve runs the application on an existing listener. This is useful for tests and for callers that need custom listener configuration.

func (*App) ServeHTTP

func (app *App) ServeHTTP(writer http.ResponseWriter, request *http.Request)

ServeHTTP implements http.Handler and builds the application on first use.

func (*App) SetErrorHandler

func (app *App) SetErrorHandler(handler ErrorHandler)

SetErrorHandler replaces the application's error renderer.

func (*App) URL

func (app *App) URL(name string, parameters map[string]string) (string, error)

URL returns a root-relative, escaped path for a named route. Group prefixes are included; host patterns do not add an authority. Calling URL validates and freezes registration, like Routes. It is safe for concurrent use.

Parameters are raw, unescaped values and must match the route's names exactly. A {name} parameter is one segment; {name...} splits on slash and may be empty or end with a slash. Empty interior segments, dot segments, and a slash-only single parameter are rejected because ServeMux cannot match them as intended. Add query parameters separately with net/url.Values.

func (*App) Use

func (app *App) Use(middleware ...Middleware)

Use adds middleware to the application-wide chain.

func (*App) UseHTTP

func (app *App) UseHTTP(middleware ...HTTPMiddleware)

UseHTTP adds standard net/http middleware around the complete application. It is intended for tracing and instrumentation libraries that use the native middleware contract.

type AsyncExecutor added in v0.16.0

type AsyncExecutor interface {
	Submit(context.Context, SubmitRequest) (*Operation, error)
	Get(context.Context, string) (*Operation, error)
	Cancel(context.Context, string) error
}

AsyncExecutor submits, retrieves, and cancels asynchronous operations.

type AsyncHandler added in v0.16.0

type AsyncHandler func(context.Context, AsyncJob) (any, error)

AsyncHandler processes one operation delivery.

type AsyncJob added in v0.16.0

type AsyncJob interface {
	ID() string
	Name() string
	Decode(any) error
	Metadata() map[string]string
	Progress(context.Context, int) error
}

AsyncJob is immutable application-level input supplied to an operation handler.

type AsyncLifecycleExecutor added in v0.16.0

type AsyncLifecycleExecutor interface {
	AsyncExecutor
	Start(context.Context) error
	Done() <-chan error
	Shutdown(context.Context) error
}

AsyncLifecycleExecutor is an executor managed by the Vial application lifecycle. External durable executors may implement only AsyncExecutor.

type AsyncMetrics added in v0.16.0

type AsyncMetrics struct {
	SubmittedTotal        uint64
	CompletedTotal        uint64
	FailedTotal           uint64
	CancelledTotal        uint64
	RetriedTotal          uint64
	QueueDepth            int64
	Running               int64
	DurationSecondsTotal  float64
	DurationCount         uint64
	QueueWaitSecondsTotal float64
	QueueWaitCount        uint64
}

AsyncMetrics is a point-in-time executor metrics snapshot.

type AsyncMetricsProvider added in v0.16.0

type AsyncMetricsProvider interface {
	Metrics(context.Context) (AsyncMetrics, error)
}

AsyncMetricsProvider supplies executor metrics.

type AsyncPreference added in v0.16.0

type AsyncPreference struct {
	RespondAsync  bool
	Wait          time.Duration
	WaitSpecified bool
}

AsyncPreference is the RFC 7240 asynchronous preference requested by a client.

func ParsePrefer added in v0.16.0

func ParsePrefer(header string) (AsyncPreference, error)

ParsePrefer parses respond-async and wait=n while ignoring unknown preferences.

type AsyncWaiter added in v0.16.0

type AsyncWaiter interface {
	Wait(context.Context, string) (*Operation, error)
}

AsyncWaiter efficiently waits for an operation state change or completion.

type BindingError added in v0.4.0

type BindingError struct {
	Source string
	Field  string
	Cause  error
}

BindingError identifies the request field that could not be bound.

func (*BindingError) Error added in v0.4.0

func (err *BindingError) Error() string

Error describes the binding source, field, and cause.

func (*BindingError) Unwrap added in v0.4.0

func (err *BindingError) Unwrap() error

Unwrap returns the binding failure cause.

type Context

type Context struct {
	// contains filtered or unexported fields
}

Context contains request-scoped framework state while retaining direct access to the standard net/http request and response writer.

func ContextFromRequest added in v0.15.0

func ContextFromRequest(request *http.Request) (*Context, bool)

ContextFromRequest returns the Vial context attached by the application. It lets standard net/http handlers access route metadata without exported context keys that could collide with application values.

func (*Context) Accepted added in v0.16.0

func (contextValue *Context) Accepted(operation *Operation) error

Accepted writes the conventional 202 response for an asynchronous operation.

func (*Context) AcceptedAt added in v0.16.0

func (contextValue *Context) AcceptedAt(operation *Operation, statusURL string) error

AcceptedAt writes a 202 response whose Location points at statusURL.

func (*Context) AfterResponse

func (context *Context) AfterResponse(hook func()) error

AfterResponse registers work that runs synchronously after the handler and error renderer finish, but before request-scoped resources are cleaned up. Hooks must not block.

func (*Context) App

func (context *Context) App() *App

App returns the application serving the request.

func (*Context) Async added in v0.16.0

func (contextValue *Context) Async() AsyncExecutor

Async returns the application's registered executor.

func (*Context) Await added in v0.16.0

func (contextValue *Context) Await(operation *Operation, maximum time.Duration) (current *Operation, completed bool, err error)

Await waits up to the client preference and server maximum. completed is true only when the returned operation reached a terminal state.

func (*Context) BeforeCommit

func (context *Context) BeforeCommit(hook func(http.Header)) error

BeforeCommit registers a header hook that runs once immediately before the response is committed. Hooks must be registered before any response write.

func (*Context) Bind added in v0.4.0

func (context *Context) Bind(destination any) error

Bind combines tagged path, query, header, cookie, JSON, and form input.

func (*Context) BindCookie added in v0.4.0

func (context *Context) BindCookie(destination any) error

BindCookie binds request cookies to fields tagged with `cookie`.

func (*Context) BindForm added in v0.3.0

func (context *Context) BindForm(destination any) error

BindForm binds URL-encoded or multipart form fields to primitive struct fields tagged with `form`. Repeated values bind to slices.

func (*Context) BindHeader added in v0.4.0

func (context *Context) BindHeader(destination any) error

BindHeader binds request headers to fields tagged with `header`.

func (*Context) BindJSON

func (context *Context) BindJSON(destination any) error

BindJSON decodes a single JSON value into destination using the application's body limit and unknown-field policy.

func (*Context) BindPath added in v0.4.0

func (context *Context) BindPath(destination any) error

BindPath binds route parameters to fields tagged with `path`.

func (*Context) BindQuery added in v0.3.0

func (context *Context) BindQuery(destination any) error

BindQuery binds query parameters to primitive struct fields tagged with `query`. Repeated values bind to slices.

func (*Context) BytesWritten

func (context *Context) BytesWritten() int64

BytesWritten returns the response body bytes written so far.

func (*Context) ClientIP added in v0.15.0

func (context *Context) ClientIP() (netip.Addr, error)

ClientIP returns the direct peer unless it is an explicitly trusted proxy. Trusted proxy chains are evaluated from the application outward, stopping at the first untrusted address.

func (*Context) Committed

func (context *Context) Committed() bool

Committed reports whether the response headers were written.

func (*Context) Flush added in v0.15.0

func (context *Context) Flush() error

Flush sends buffered response data when the server supports streaming.

func (*Context) FormFile added in v0.3.0

func (context *Context) FormFile(name string) (multipart.File, *multipart.FileHeader, error)

FormFile returns the first uploaded file for name. The caller must close the file. Vial removes any temporary multipart files after the request finishes.

func (*Context) FormValue added in v0.10.0

func (context *Context) FormValue(name string) (string, error)

FormValue returns the first URL-encoded or multipart form value for name.

func (*Context) Get

func (context *Context) Get(key string) (any, bool)

Get returns a request-scoped value.

func (*Context) Header

func (context *Context) Header(name string) string

Header returns a request header value.

func (*Context) JSON

func (context *Context) JSON(status int, value any) error

JSON writes a JSON response with status.

func (*Context) Logger

func (context *Context) Logger() *slog.Logger

Logger returns the request logger.

func (*Context) NoContent

func (context *Context) NoContent(status int) error

NoContent writes a response status without a body.

func (*Context) Param

func (context *Context) Param(name string) string

Param returns a path parameter by name.

func (*Context) Query

func (context *Context) Query(name string) string

Query returns the first query value for name.

func (*Context) Redirect

func (context *Context) Redirect(status int, location string) error

Redirect writes an HTTP redirect response.

func (*Context) Request

func (context *Context) Request() *http.Request

Request returns the underlying HTTP request.

func (*Context) Response

func (context *Context) Response() http.ResponseWriter

Response returns the tracked HTTP response writer.

func (*Context) ResponseController added in v0.15.0

func (context *Context) ResponseController() *http.ResponseController

ResponseController exposes standard-library optional response operations.

func (*Context) Route

func (context *Context) Route() *Route

Route returns metadata for the matched route.

func (*Context) Set

func (context *Context) Set(key string, value any)

Set stores a request-scoped value.

func (*Context) SetLogger

func (context *Context) SetLogger(logger *slog.Logger)

SetLogger replaces the request logger when logger is non-nil.

func (*Context) SetWriteDeadline added in v0.15.0

func (context *Context) SetWriteDeadline(deadline time.Time) error

SetWriteDeadline sets the response write deadline when supported.

func (*Context) Status

func (context *Context) Status() int

Status returns the response status written so far.

func (*Context) Text

func (context *Context) Text(status int, value string) error

Text writes a plain-text response with status.

type ErrorHandler

type ErrorHandler func(*Context, error)

ErrorHandler renders errors returned by handlers or middleware.

type Group

type Group struct {
	// contains filtered or unexported fields
}

Group applies a common path prefix and middleware chain to related routes.

func (*Group) Delete

func (group *Group) Delete(path string, handler Handler, options ...RouteOption)

Delete registers a DELETE route in the group.

func (*Group) Get

func (group *Group) Get(path string, handler Handler, options ...RouteOption)

Get registers a GET route in the group.

func (*Group) Group

func (group *Group) Group(prefix string) *Group

Group creates a nested group that inherits the current middleware chain.

func (*Group) Handle

func (group *Group) Handle(method, path string, handler Handler, options ...RouteOption)

Handle registers a route in the group.

func (*Group) HandleHTTP added in v0.15.0

func (group *Group) HandleHTTP(pattern string, handler http.Handler, options ...RouteOption)

HandleHTTP registers a standard library handler under the group's prefix.

func (*Group) Options

func (group *Group) Options(path string, handler Handler, options ...RouteOption)

Options registers an OPTIONS route in the group.

func (*Group) Patch

func (group *Group) Patch(path string, handler Handler, options ...RouteOption)

Patch registers a PATCH route in the group.

func (*Group) Post

func (group *Group) Post(path string, handler Handler, options ...RouteOption)

Post registers a POST route in the group.

func (*Group) Put

func (group *Group) Put(path string, handler Handler, options ...RouteOption)

Put registers a PUT route in the group.

func (*Group) Use

func (group *Group) Use(middleware ...Middleware)

Use adds middleware to routes subsequently registered through the group.

type HTTPError

type HTTPError struct {
	Status  int
	Code    string
	Message string
	Cause   error
	Headers http.Header
}

HTTPError is an application error with a stable HTTP representation.

func BadRequest

func BadRequest(code, message string) *HTTPError

BadRequest creates a 400 HTTP error.

func Conflict

func Conflict(code, message string) *HTTPError

Conflict creates a 409 HTTP error.

func Forbidden

func Forbidden(code, message string) *HTTPError

Forbidden creates a 403 HTTP error.

func InternalServerError

func InternalServerError(cause error) *HTTPError

InternalServerError creates a generic 500 HTTP error that retains cause.

func MethodNotAllowed added in v0.3.0

func MethodNotAllowed(code, message string) *HTTPError

MethodNotAllowed creates a 405 HTTP error.

func NewHTTPError

func NewHTTPError(status int, code, message string) *HTTPError

NewHTTPError creates an HTTP error with a stable status, code, and message.

func NotFound

func NotFound(code, message string) *HTTPError

NotFound creates a 404 HTTP error.

func RequestEntityTooLarge

func RequestEntityTooLarge(code, message string) *HTTPError

RequestEntityTooLarge creates a 413 HTTP error.

func Unauthorized

func Unauthorized(code, message string) *HTTPError

Unauthorized creates a 401 HTTP error.

func UnsupportedMediaType

func UnsupportedMediaType(code, message string) *HTTPError

UnsupportedMediaType creates a 415 HTTP error.

func WrapHTTPError

func WrapHTTPError(status int, code, message string, cause error) *HTTPError

WrapHTTPError creates an HTTP error that retains cause.

func (*HTTPError) Error

func (err *HTTPError) Error() string

Error returns the public error message and retained cause.

func (*HTTPError) Unwrap

func (err *HTTPError) Unwrap() error

Unwrap returns the retained cause.

type HTTPMiddleware

type HTTPMiddleware func(http.Handler) http.Handler

HTTPMiddleware is the standard net/http middleware contract. Use it for tracing and other instrumentation that must wrap the complete HTTP response.

type Handler

type Handler func(*Context) error

Handler is the framework's HTTP handler contract. Returning errors keeps transport error rendering centralized and makes middleware composition simple.

func AsyncMetricsHandler added in v0.16.0

func AsyncMetricsHandler(provider AsyncMetricsProvider) Handler

AsyncMetricsHandler exposes dependency-free Prometheus/OpenMetrics metrics.

func OperationCancelHandler added in v0.16.0

func OperationCancelHandler(executor AsyncExecutor, authorize OperationAuthorizer) Handler

OperationCancelHandler returns a cancellation handler for a route containing {id}.

func OperationStatusHandler added in v0.16.0

func OperationStatusHandler(executor AsyncExecutor, authorize OperationAuthorizer) Handler

OperationStatusHandler returns a polling handler for a route containing {id}.

type HealthCheck added in v0.15.0

type HealthCheck func(context.Context) error

HealthCheck reports whether one readiness dependency can currently serve.

type LifecycleHook added in v0.4.0

type LifecycleHook func(context.Context) error

LifecycleHook runs while the application starts or stops.

type Middleware

type Middleware func(Handler) Handler

Middleware wraps a Handler with cross-cutting behavior.

type Module added in v0.4.0

type Module interface {
	Name() string
	Register(*Registrar) error
}

Module registers one named application capability.

type Operation added in v0.16.0

type Operation struct {
	ID            string            `json:"id"`
	Name          string            `json:"name"`
	Status        OperationStatus   `json:"status"`
	Progress      int               `json:"progress"`
	CreatedAt     time.Time         `json:"created_at"`
	StartedAt     *time.Time        `json:"started_at"`
	FinishedAt    *time.Time        `json:"finished_at"`
	Result        any               `json:"result"`
	Error         *OperationError   `json:"error"`
	Metadata      map[string]string `json:"-"`
	Attempt       int               `json:"attempt"`
	MaxAttempts   int               `json:"max_attempts"`
	NextAttemptAt *time.Time        `json:"next_attempt_at"`
}

Operation describes submitted background work.

type OperationAuthorizer added in v0.16.0

type OperationAuthorizer func(*Context, *Operation) error

OperationAuthorizer applies application ownership rules before status or cancellation is exposed. Helpers require one to avoid globally readable IDs.

type OperationError added in v0.16.0

type OperationError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

OperationError is the safe, client-visible reason an operation failed.

func (*OperationError) Error added in v0.16.0

func (operationError *OperationError) Error() string

type OperationStatus added in v0.16.0

type OperationStatus string

OperationStatus is the current state of an asynchronous operation.

const (
	OperationPending   OperationStatus = "pending"
	OperationRunning   OperationStatus = "running"
	OperationRetrying  OperationStatus = "retrying"
	OperationSucceeded OperationStatus = "succeeded"
	OperationFailed    OperationStatus = "failed"
	OperationCancelled OperationStatus = "cancelled"
)

func (OperationStatus) Terminal added in v0.16.0

func (status OperationStatus) Terminal() bool

Terminal reports whether no further state transition is possible.

type Option

type Option func(*config)

Option configures an App.

func WithBaseContext added in v0.15.0

func WithBaseContext(baseContext func(net.Listener) context.Context) Option

WithBaseContext sets http.Server.BaseContext.

func WithConnContext added in v0.15.0

func WithConnContext(connContext func(context.Context, net.Conn) context.Context) Option

WithConnContext sets http.Server.ConnContext.

func WithDisallowUnknownJSONFields

func WithDisallowUnknownJSONFields(enabled bool) Option

WithDisallowUnknownJSONFields controls rejection of unknown JSON fields.

func WithHTTPProtocols added in v0.18.0

func WithHTTPProtocols(protocols *http.Protocols) Option

WithHTTPProtocols sets http.Server.Protocols.

func WithHealthCheckTimeout added in v0.15.0

func WithHealthCheckTimeout(timeout time.Duration) Option

WithHealthCheckTimeout sets the maximum duration of each readiness check.

func WithIdleTimeout

func WithIdleTimeout(timeout time.Duration) Option

WithIdleTimeout sets the HTTP server keep-alive idle timeout.

func WithLogger

func WithLogger(logger *slog.Logger) Option

WithLogger sets the application logger.

func WithMaxBodySize

func WithMaxBodySize(bytes int64) Option

WithMaxBodySize sets the request body limit when bytes is positive.

func WithMaxHeaderBytes added in v0.15.0

func WithMaxHeaderBytes(bytes int) Option

WithMaxHeaderBytes sets the maximum request-header size.

func WithReadHeaderTimeout

func WithReadHeaderTimeout(timeout time.Duration) Option

WithReadHeaderTimeout sets the HTTP server header-read timeout.

func WithReadTimeout

func WithReadTimeout(timeout time.Duration) Option

WithReadTimeout sets the HTTP server request-read timeout.

func WithServerErrorLog added in v0.15.0

func WithServerErrorLog(logger *log.Logger) Option

WithServerErrorLog sets http.Server.ErrorLog.

func WithShutdownTimeout

func WithShutdownTimeout(timeout time.Duration) Option

WithShutdownTimeout sets the graceful shutdown deadline when timeout is positive.

func WithTrustedProxies added in v0.15.0

func WithTrustedProxies(values ...string) Option

WithTrustedProxies trusts forwarding headers only when the direct peer matches one of the listed IPs or CIDRs. The default trusts none.

func WithWriteTimeout

func WithWriteTimeout(timeout time.Duration) Option

WithWriteTimeout sets the HTTP server response-write timeout.

type Registrar added in v0.4.0

type Registrar struct {
	// contains filtered or unexported fields
}

Registrar exposes the route registration surface available to modules.

func (*Registrar) Go added in v0.15.0

func (registrar *Registrar) Go(name string, task Task, options ...TaskOption)

Go registers a supervised module background task.

func (*Registrar) Group added in v0.4.0

func (registrar *Registrar) Group(prefix string) *Group

Group creates a module route group.

func (*Registrar) Handle added in v0.4.0

func (registrar *Registrar) Handle(method, path string, handler Handler, options ...RouteOption)

Handle registers a module route.

func (*Registrar) HandleHTTP added in v0.4.0

func (registrar *Registrar) HandleHTTP(pattern string, handler http.Handler, options ...RouteOption)

HandleHTTP mounts a standard-library handler in the module.

func (*Registrar) Health added in v0.15.0

func (registrar *Registrar) Health(path string)

Health registers a module liveness endpoint.

func (*Registrar) OnStart added in v0.15.0

func (registrar *Registrar) OnStart(hooks ...LifecycleHook)

OnStart registers module startup hooks.

func (*Registrar) OnStop added in v0.15.0

func (registrar *Registrar) OnStop(hooks ...LifecycleHook)

OnStop registers module shutdown hooks.

func (*Registrar) Readiness added in v0.15.0

func (registrar *Registrar) Readiness(path string, checks ...HealthCheck)

Readiness registers a module readiness endpoint.

func (*Registrar) Use added in v0.4.0

func (registrar *Registrar) Use(middleware ...Middleware)

Use adds middleware to every route registered by the module.

type ResponseWriter

type ResponseWriter struct {
	http.ResponseWriter
	// contains filtered or unexported fields
}

ResponseWriter tracks response state while retaining access to the underlying standard-library writer through Unwrap.

func (*ResponseWriter) BytesWritten

func (writer *ResponseWriter) BytesWritten() int64

BytesWritten returns the response body bytes written so far.

func (*ResponseWriter) Committed

func (writer *ResponseWriter) Committed() bool

Committed reports whether response headers were written.

func (*ResponseWriter) Status

func (writer *ResponseWriter) Status() int

Status returns the written status or 200 before commitment.

func (*ResponseWriter) Unwrap

func (writer *ResponseWriter) Unwrap() http.ResponseWriter

Unwrap returns the underlying standard-library response writer.

func (*ResponseWriter) Write

func (writer *ResponseWriter) Write(data []byte) (int, error)

Write writes response data and records its size.

func (*ResponseWriter) WriteHeader

func (writer *ResponseWriter) WriteHeader(status int)

WriteHeader records and writes the first response status.

func (*ResponseWriter) WriteString

func (writer *ResponseWriter) WriteString(value string) (int, error)

WriteString writes text without converting it to bytes when the underlying writer supports io.StringWriter, while tracking commitment and byte counts.

type RetryPolicy added in v0.16.0

type RetryPolicy struct {
	MaxAttempts    int
	InitialBackoff time.Duration
	MaxBackoff     time.Duration
}

RetryPolicy configures durable redelivery after transient worker failures. Its zero value permits one attempt and therefore disables retries.

type Route

type Route struct {
	Method          string
	Path            string
	Pattern         string
	Name            string
	Module          string
	MiddlewareCount int
	Parameters      []string
}

Route contains metadata for a registered endpoint.

type RouteOption added in v0.4.0

type RouteOption struct {
	// contains filtered or unexported fields
}

RouteOption configures route metadata.

func RouteMiddleware added in v0.15.0

func RouteMiddleware(middleware ...Middleware) RouteOption

RouteMiddleware applies middleware only to the configured route.

func RouteName added in v0.4.0

func RouteName(name string) RouteOption

RouteName assigns a globally unique identifier for observability, diagnostics, and URL generation through App.URL.

type SubmitRequest added in v0.16.0

type SubmitRequest struct {
	Name             string
	Payload          any
	IdempotencyKey   string
	IdempotencyScope string
	Metadata         map[string]string
	Retry            RetryPolicy
}

SubmitRequest contains serializable, application-level data for an operation. IdempotencyScope must identify the authenticated owner when IdempotencyKey is set.

type Task added in v0.7.0

type Task func(context.Context) error

Task is application work that runs until its context is canceled or it fails.

type TaskOption added in v0.7.0

type TaskOption struct {
	// contains filtered or unexported fields
}

TaskOption configures a background task.

func NonCritical added in v0.7.0

func NonCritical() TaskOption

NonCritical keeps the application running when the task exits or fails.

type Validator added in v0.10.0

type Validator interface {
	Validate() error
}

Validator validates bound request data. Returning a *fault.Error preserves its field errors for response handling and rendering.

type ValueKey added in v0.15.0

type ValueKey[T any] struct {
	// contains filtered or unexported fields
}

ValueKey is a collision-safe request value key. Keep and reuse the pointer returned by NewValueKey; identity, not the display name, selects the value.

func NewValueKey added in v0.15.0

func NewValueKey[T any](name string) *ValueKey[T]

NewValueKey creates a typed request value key.

func (*ValueKey[T]) FromRequest added in v0.15.0

func (key *ValueKey[T]) FromRequest(request *http.Request) (T, bool)

FromRequest returns the same typed value through the underlying request.

func (*ValueKey[T]) Get added in v0.15.0

func (key *ValueKey[T]) Get(context *Context) (T, bool)

Get returns a typed value from a Vial context.

func (*ValueKey[T]) Name added in v0.15.0

func (key *ValueKey[T]) Name() string

Name returns the diagnostic name of the key. Names do not affect identity.

func (*ValueKey[T]) Set added in v0.15.0

func (key *ValueKey[T]) Set(context *Context, value T)

Set stores a value for the current request. Values are safe for concurrent access during the request but should not be retained after it completes.

Directories

Path Synopsis
Package async contains Vial's bounded in-memory executor.
Package async contains Vial's bounded in-memory executor.
Package auth provides provider-neutral request identities and authorization guards for Vial.
Package auth provides provider-neutral request identities and authorization guards for Vial.
cmd
vial command
Package config loads typed application configuration from explicit files and environment variables.
Package config loads typed application configuration from explicit files and environment variables.
contrib
asyncpostgres
Package asyncpostgres implements Vial's durable PostgreSQL executor.
Package asyncpostgres implements Vial's durable PostgreSQL executor.
examples
async command
config command
events command
hello command
json-api command
modules command
observability command
openapi command
security command
sse command
tasks command
upload command
web command
Package fault defines transport-neutral application errors.
Package fault defines transport-neutral application errors.
internal
dev
load
Package load runs bounded HTTP load checks for the vial command.
Package load runs bounded HTTP load checks for the vial command.
Package middleware contains the framework's optional default HTTP middleware.
Package middleware contains the framework's optional default HTTP middleware.
Package openapi generates OpenAPI 3.1 JSON from Vial routes and Go request and response types.
Package openapi generates OpenAPI 3.1 JSON from Vial routes and Go request and response types.
Package render writes html/template output through Vial handlers.
Package render writes html/template output through Vial handlers.
Package session provides encrypted, authenticated cookie sessions for Vial.
Package session provides encrypted, authenticated cookie sessions for Vial.
Package sqlkit adds small database/sql helpers for transactions and embedded forward migrations.
Package sqlkit adds small database/sql helpers for transactions and embedded forward migrations.
Package sse writes Server-Sent Events and provides bounded in-process fan-out.
Package sse writes Server-Sent Events and provides bounded in-process fan-out.
Package testkit runs Vial applications through their full lifecycle in HTTP tests.
Package testkit runs Vial applications through their full lifecycle in HTTP tests.
Package vialgrpc mounts a grpc-go server in Vial and ties it to application shutdown.
Package vialgrpc mounts a grpc-go server in Vial and ties it to application shutdown.
Package vialws adapts coder/websocket connections to Vial request lifecycle.
Package vialws adapts coder/websocket connections to Vial request lifecycle.

Jump to

Keyboard shortcuts

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