Documentation
¶
Overview ¶
Package app provides a batteries-included, cloud-native web framework built on top of the Rivaas router. It features high-performance routing, comprehensive request binding & validation, automatic OpenAPI generation, and OpenTelemetry-native observability, along with lifecycle management and sensible defaults for building production-ready web applications.
Overview ¶
The app package wraps the router package with additional features:
- Integrated observability (metrics, tracing, logging)
- Lifecycle hooks (OnStart, OnReady, OnShutdown, OnStop)
- Graceful shutdown handling
- Server configuration management
- Request binding and validation
- OpenAPI/Swagger documentation with ETag-based caching
- Health check endpoints
- Development and production modes
When to Use ¶
Use the app package when:
- Building a complete web application with batteries included
- You want integrated observability configured out of the box
- You need development with sensible defaults
- Building REST APIs with common middleware patterns
- You prefer convention over configuration
Use the router package directly when:
- Building a library or framework that needs full control
- You have custom observability setup already configured
- You need complete flexibility without any opinions
- Integrating into existing systems with established patterns
Constructor Pattern ¶
The app package follows the functional options pattern used throughout Rivaas:
Options apply to an internal config struct (not the App type directly)
New() validates the config and builds the App from the validated config
New() returns (*App, error) because app initialization can fail. The app initializes external resources (metrics, tracing, logging) that may fail to connect to backends, validate configurations, or allocate resources.
MustNew() is provided as a convenience wrapper that panics on error. This follows the standard Go idiom (like regexp.MustCompile, template.Must) and is useful for initialization in main() functions where errors should abort startup.
All configuration options use the "With" prefix for consistency.
Grouping options (e.g., WithServer, WithRouter) accept sub-options to organize related settings and reduce API surface.
Option Validation ¶
Options must not be nil. Passing a nil option to New(), MustNew(), or methods that accept options (e.g., Test) returns a validation error, not a panic. This applies to both top-level options (e.g., WithServer) and nested options (e.g., WithReadTimeout inside WithServer).
Quick Start ¶
Simple application with defaults:
app, err := app.New()
if err != nil {
log.Fatal(err)
}
app.GET("/", func(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{"message": "Hello"})
})
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := app.Start(ctx); err != nil {
log.Fatal(err)
}
Full-featured application with observability, health, and debug endpoints:
app, err := app.New(
app.WithServiceName("my-api"),
app.WithServiceVersion("v1.0.0"),
app.WithEnvironment("production"),
// Observability: all three pillars in one place
app.WithObservability(
app.WithLogging(logging.WithJSONHandler()),
app.WithMetrics(), // Prometheus is default
app.WithTracing(tracing.WithOTLP("localhost:4317")),
),
// Health endpoints: /livez and /readyz
app.WithHealthEndpoints(
app.WithLivenessCheck("process", func(ctx context.Context) error {
return nil
}),
app.WithReadinessCheck("database", func(ctx context.Context) error {
return db.PingContext(ctx)
}),
),
// Debug endpoints: /debug/pprof/* (conditionally enabled)
app.WithDebugEndpoints(
app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
),
app.WithServer(
app.WithReadTimeout(15 * time.Second),
app.WithWriteTimeout(15 * time.Second),
),
)
if err != nil {
log.Fatal(err)
}
Or using MustNew for initialization that panics on error:
app := app.MustNew(
app.WithServiceName("my-service"),
app.WithObservability(
app.WithMetrics(), // Prometheus is default
),
)
Observability ¶
The app package integrates three pillars of observability:
- Metrics: Prometheus-compatible metrics with automatic HTTP request instrumentation
- Tracing: OpenTelemetry tracing with request context propagation
- Logging: Structured logging with slog, including request-scoped fields
Request spans use the same semantics as the tracing package: W3C trace context extraction, sampling (e.g. WithSampleRate), and standard HTTP attributes (http.method, http.url, http.route, http.status_code, service.name, etc.).
From handlers, use Context.SetSpanAttribute and Context.AddSpanEvent on the current span. For child spans (e.g. "db-query", "call-service"), use Context.StartSpan and Context.FinishSpan for success, or Context.FinishSpanWithHTTPStatus when you have an HTTP status, or Context.FinishSpanWithError when the span fails. Use Context.RecordError to record an error on the request span without ending it. Use Context.CopyTraceContext to get a context for goroutines that share the same trace. Use Context.WithSpan to run a function under a span that is finished from the returned error:
ctx, span := c.StartSpan("db-query")
defer c.FinishSpan(span)
// or: defer func() { if err != nil { c.FinishSpanWithError(span, err) } else { c.FinishSpan(span) } }()
// or: err := c.WithSpan("fetch-user", func(ctx context.Context) error { ... })
Use Context.Tracer only when you need to pass the tracer to another library (e.g. DB driver, HTTP client) or use tracer-specific options (inject/extract).
For custom metrics from handlers, use Context.RecordHistogram, Context.IncrementCounter, Context.AddCounter, and Context.SetGauge:
c.IncrementCounter("requests_total")
c.AddCounter("bytes_processed", 1024, attribute.String("type", "upload"))
c.RecordHistogram("duration_seconds", 0.5)
c.SetGauge("queue_size", 10)
All observability features are optional and can be enabled independently:
app.New(
app.WithObservability(
app.WithMetrics(), // Prometheus is default; use metrics.WithOTLP() for OTLP
app.WithTracing(tracing.WithOTLP("localhost:4317")),
app.WithLogging(logging.WithJSONHandler()),
),
)
Lifecycle Hooks ¶
The app provides lifecycle hooks for application events:
- OnStart: Called before server starts (sequential, stops on first error)
- OnReady: Called when server is ready to accept connections (async, non-blocking)
- OnShutdown: Called during graceful shutdown (LIFO order)
- OnStop: Called after shutdown completes (best-effort)
Example:
if err := app.OnStart(func(ctx context.Context) error {
return db.Connect(ctx)
}); err != nil {
log.Fatal(err)
}
if err := app.OnReady(func() {
log.Println("Server is ready!")
}); err != nil {
log.Fatal(err)
}
if err := app.OnShutdown(func(ctx context.Context) {
db.Close()
}); err != nil {
log.Fatal(err)
}
Request Handling ¶
Handlers receive an app.Context that extends router.Context with app-level features:
- Request binding (JSON, form, query parameters)
- Request validation
- Access to observability (metrics, tracing, logging)
The app uses a single handler type (app.HandlerFunc, which wraps router.HandlerFunc) for consistency across groups, version groups, and individual routes. This ensures the integration layer remains predictable and middleware composition is uniform.
Example:
app.POST("/users", func(c *app.Context) {
var req CreateUserRequest
if err := c.Bind(&req); err != nil {
c.Fail(err)
return
}
// Process request...
c.JSON(http.StatusCreated, user)
})
Or using the type-safe generic API:
app.POST("/users", func(c *app.Context) {
req, ok := app.MustBind[CreateUserRequest](c)
if !ok {
return // Error already written
}
// Process request...
c.JSON(http.StatusCreated, user)
})
Request Binding and Validation ¶
The app package provides a unified API for binding and validation:
- Context.Bind: Binds and validates request data (default behavior)
- Context.MustBind: Bind and validate, write error on failure
- Context.BindOnly: Bind without validation (for advanced use)
- Context.Validate: Validate only (after BindOnly); accepts ValidateOption for app-scoped options
Generic type-safe variants:
- Bind: Type-safe binding with generics
- MustBind: Type-safe Must pattern
- BindOnly: Bind without validation (for advanced use)
Options for customization. Bind and Validate options must not be nil; passing a nil option returns an error.
- WithStrict: Reject unknown JSON fields (Bind)
- WithPartial: Partial validation for PATCH requests (Bind)
- WithoutValidation: Skip validation step
- WithBindingOptions: Pass options to binding package
- WithValidationOptions: Pass options to validation package (Bind)
- WithValidatePartial, WithValidateStrict, WithValidateOptions: Options for Context.Validate
- WithValidationEngine: Use a custom validation engine for Bind/Validate (e.g. for redaction or test isolation)
Example with options:
app.PATCH("/users/:id", func(c *app.Context) {
req, ok := app.MustBind[UpdateUserRequest](c, app.WithPartial())
if !ok {
return
}
// Only provided fields are validated
})
Server Configuration ¶
Configure server address and timeouts using functional options:
app.New(
app.WithPort(3000), // Listen on port 3000
app.WithHost("127.0.0.1"), // Bind to localhost only
app.WithServer(
app.WithReadTimeout(10 * time.Second),
app.WithWriteTimeout(10 * time.Second),
app.WithIdleTimeout(60 * time.Second),
app.WithShutdownTimeout(30 * time.Second),
),
)
For HTTPS use WithTLS(certFile, keyFile); for mTLS use WithMTLS(serverCert, ...MTLSOption). Then call Start(ctx). Default port is 8080 for HTTP and 8443 for TLS/mTLS; override with WithPort or RIVAAS_PORT. Configuration is automatically validated to catch common misconfigurations.
Environment Variables ¶
The app package supports configuration via environment variables using WithEnv:
app.New(
app.WithServiceName("orders-api"),
app.WithEnv(), // Enable RIVAAS_* environment variable overrides
)
Environment variables override programmatic configuration. Supported variables:
Core: RIVAAS_ENV - Environment mode: "development" or "production" RIVAAS_SERVICE_NAME - Service name for observability RIVAAS_SERVICE_VERSION - Service version Server: RIVAAS_PORT - Server port (default 8080 for HTTP, 8443 for TLS/mTLS; e.g., "8080", "443") RIVAAS_HOST - HTTP server host/interface (e.g., "127.0.0.1") RIVAAS_READ_TIMEOUT - Request read timeout (e.g., "10s") RIVAAS_WRITE_TIMEOUT - Response write timeout (e.g., "10s") RIVAAS_SHUTDOWN_TIMEOUT - Graceful shutdown timeout (e.g., "30s") Logging: RIVAAS_LOG_LEVEL - Log level: "debug", "info", "warn", "error" RIVAAS_LOG_FORMAT - Log format: "json", "text", or "console" Observability: RIVAAS_METRICS_EXPORTER - Metrics exporter: "prometheus", "otlp", or "stdout" RIVAAS_METRICS_ADDR - Prometheus address (default: ":9090") RIVAAS_METRICS_PATH - Prometheus path (default: "/metrics") RIVAAS_METRICS_ENDPOINT - OTLP metrics endpoint (e.g., "http://localhost:4318") RIVAAS_TRACING_EXPORTER - Tracing exporter: "otlp", "otlp-http", or "stdout" RIVAAS_TRACING_ENDPOINT - OTLP tracing endpoint (e.g., "localhost:4317") Debug: RIVAAS_PPROF_ENABLED - Enable pprof: "true" or "false"
Use WithEnvPrefix for a custom prefix:
app.New(
app.WithEnvPrefix("MYAPP_"), // Use MYAPP_* instead of RIVAAS_*
)
Invalid environment values cause New to return an error (fail-fast).
Examples ¶
See the examples directory for complete working examples:
- examples/01-quick-start: Minimal setup to get started (basic routing, JSON responses)
- examples/02-blog: Real-world blog API with configuration, validation, OpenAPI docs, observability, and testing
Architecture ¶
The app package is built on top of the router package:
┌─────────────────────────────────────────┐
│ Application Layer │
│ (app package - this package) │
│ │
│ • Configuration Management │
│ • Lifecycle Hooks │
│ • Observability Integration │
│ • Server Management │
│ • Request Binding/Validation │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Router Layer │
│ (router package) │
│ │
│ • HTTP Routing │
│ • Middleware Chain │
│ • Request Context │
│ • Path Parameters │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Standard Library │
│ (net/http) │
└─────────────────────────────────────────┘
Example ¶
Example demonstrates basic app usage.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
a.GET("/", func(c *app.Context) {
if jsonErr := c.JSON(http.StatusOK, map[string]string{
"message": "Hello, World!",
}); jsonErr != nil {
log.Printf("Failed to write response: %v", jsonErr)
}
})
fmt.Println("App created successfully")
}
Output: App created successfully
Example (HealthEndpoints) ¶
Example_healthEndpoints demonstrates health check endpoint configuration.
package main
import (
"context"
"fmt"
"rivaas.dev/app"
)
func main() {
a := app.MustNew(
app.WithServiceName("example-api"),
app.WithHealthEndpoints(
app.WithLivenessCheck("process", func(ctx context.Context) error {
// Process is alive
return nil
}),
app.WithReadinessCheck("database", func(ctx context.Context) error {
// Check database connection
// return db.PingContext(ctx)
return nil
}),
),
)
fmt.Printf("Health endpoints configured: %s\n", a.ServiceName())
}
Output: Health endpoints configured: example-api
Example (LifecycleHooks) ¶
Example_lifecycleHooks demonstrates lifecycle hook registration.
package main
import (
"context"
"fmt"
"log"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
if err := a.OnStart(func(ctx context.Context) error {
// Initialize database, run migrations, etc.
fmt.Println("OnStart: Initializing...")
return nil
}); err != nil {
log.Fatal(err)
}
if err := a.OnReady(func() {
// Register with service discovery, warmup caches, etc.
fmt.Println("OnReady: Server is ready")
}); err != nil {
log.Fatal(err)
}
if err := a.OnShutdown(func(ctx context.Context) {
// Close connections, flush buffers, etc.
fmt.Println("OnShutdown: Cleaning up...")
}); err != nil {
log.Fatal(err)
}
fmt.Println("Lifecycle hooks registered")
}
Output: Lifecycle hooks registered
Example (Middleware) ¶
Example_middleware demonstrates middleware usage.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
a.Use(func(c *app.Context) {
// Add custom header
c.Header("X-Custom", "value")
c.Next()
})
a.GET("/test", func(c *app.Context) {
if err := c.String(http.StatusOK, "ok"); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
fmt.Println("Middleware registered")
}
Output: Middleware registered
Example (PartialValidation) ¶
Example_partialValidation demonstrates partial validation for PATCH endpoints using Bind with WithPartial.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
type UpdateUserRequest struct {
Name *string `json:"name" validate:"omitempty,min=3"`
Email *string `json:"email" validate:"omitempty,email"`
}
a.PATCH("/users/:id", func(c *app.Context) {
req, err := app.Bind[UpdateUserRequest](c, app.WithPartial())
if err != nil {
c.Fail(err)
return
}
userID := c.Param("id")
if jsonErr := c.JSON(http.StatusOK, map[string]string{
"message": "User updated",
"id": userID,
}); jsonErr != nil {
log.Printf("Failed to write response: %v", jsonErr)
}
_ = req // Use req for update logic
})
fmt.Println("PATCH handler with partial validation registered")
}
Output: PATCH handler with partial validation registered
Example (Routing) ¶
Example_routing demonstrates route registration.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
a.GET("/users", func(c *app.Context) {
if err := c.JSON(http.StatusOK, map[string]string{"users": "list"}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
a.POST("/users", func(c *app.Context) {
if err := c.JSON(http.StatusCreated, map[string]string{"user": "created"}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
fmt.Println("Routes registered")
}
Output: Routes registered
Example (StrictBinding) ¶
Example_strictBinding demonstrates strict mode for typo detection using Bind with WithStrict.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
}
a.POST("/users", func(c *app.Context) {
req, err := app.Bind[CreateUserRequest](c, app.WithStrict())
if err != nil {
c.Fail(err)
return
}
if jsonErr := c.JSON(http.StatusCreated, map[string]string{
"message": "User created",
"name": req.Name,
}); jsonErr != nil {
log.Printf("Failed to write response: %v", jsonErr)
}
})
fmt.Println("Handler with strict binding registered")
}
Output: Handler with strict binding registered
Example (Testing) ¶
Example_testing demonstrates testing patterns.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
a.GET("/health", func(c *app.Context) {
if err := c.String(http.StatusOK, "ok"); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
req := httptest.NewRequest(http.MethodGet, "/health", nil)
resp, err := a.Test(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d\n", resp.StatusCode)
//nolint:errcheck // example code, we don't care about the error here
resp.Body.Close()
}
Output: Status: 200
Example (WithObservability) ¶
Example_withObservability demonstrates full observability setup.
package main
import (
"fmt"
"rivaas.dev/app"
"rivaas.dev/metrics"
"rivaas.dev/tracing"
)
func main() {
a := app.MustNew(
app.WithServiceName("example-api"),
app.WithServiceVersion("v1.0.0"),
app.WithObservability(
app.WithMetrics(metrics.WithPrometheus(":9090", "/metrics")),
app.WithTracing(tracing.WithNoop()),
),
)
fmt.Printf("Service: %s\n", a.ServiceName())
fmt.Printf("Metrics: enabled\n")
}
Output: Service: example-api Metrics: enabled
Index ¶
- Constants
- Variables
- func Bind[T any](c *Context, opts ...BindOption) (T, error)
- func BindOnly[T any](c *Context, opts ...BindOption) (T, error)
- func ExpectJSON(t testingT, resp *http.Response, statusCode int, out any)
- func GetMTLSCertificate(req *http.Request) *x509.Certificate
- func MustBind[T any](c *Context, opts ...BindOption) (T, bool)
- type AccessLogScope
- type App
- func (a *App) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (a *App) BaseLogger() *slog.Logger
- func (a *App) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (a *App) Environment() string
- func (a *App) File(path, filepath string)
- func (a *App) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (a *App) GetMetricsHandler() (http.Handler, error)
- func (a *App) GetMetricsServerAddress() string
- func (a *App) Group(prefix string, middleware ...HandlerFunc) *Group
- func (a *App) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (a *App) Metrics() *metrics.Recorder
- func (a *App) MustURLFor(routeName string, params map[string]string, query map[string][]string) string
- func (a *App) NoRoute(handler HandlerFunc)
- func (a *App) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (a *App) OnReady(fn func()) error
- func (a *App) OnReload(fn func(context.Context) error) error
- func (a *App) OnRoute(fn func(*route.Route)) error
- func (a *App) OnShutdown(fn func(context.Context)) error
- func (a *App) OnStart(fn func(context.Context) error) error
- func (a *App) OnStop(fn func()) error
- func (a *App) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (a *App) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (a *App) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (a *App) PrintRoutes()
- func (a *App) Readiness() *ReadinessManager
- func (a *App) Reload(ctx context.Context) error
- func (a *App) Route(name string) (*route.Route, bool)
- func (a *App) Router() *router.Router
- func (a *App) Routes() []*route.Route
- func (a *App) ServiceName() string
- func (a *App) ServiceVersion() string
- func (a *App) Start(ctx context.Context) error
- func (a *App) Static(prefix, root string)
- func (a *App) StaticFS(prefix string, fs http.FileSystem)
- func (a *App) Test(req *http.Request, opts ...TestOption) (*http.Response, error)
- func (a *App) TestJSON(method, path string, body any, opts ...TestOption) (*http.Response, error)
- func (a *App) Tracing() *tracing.Tracer
- func (a *App) URLFor(routeName string, params map[string]string, query map[string][]string) (string, error)
- func (a *App) Use(middleware ...HandlerFunc)
- func (a *App) ValidateRoutes() error
- func (a *App) Version(version string) *VersionGroup
- func (a *App) WrapHandler(handler HandlerFunc) router.HandlerFunc
- type BindOption
- type CheckFunc
- type ConfigError
- type ConfigErrors
- type Context
- func (c *Context) AddCounter(name string, value int64, attributes ...attribute.KeyValue)
- func (c *Context) AddSpanEvent(name string, attrs ...attribute.KeyValue)
- func (c *Context) BadRequest(err error)
- func (c *Context) Bind(out any, opts ...BindOption) error
- func (c *Context) BindOnly(out any, opts ...BindOption) error
- func (c *Context) Conflict(err error)
- func (c *Context) CopyTraceContext() context.Context
- func (c *Context) Fail(err error)
- func (c *Context) FailStatus(status int, err error)
- func (c *Context) FinishSpan(span trace.Span)
- func (c *Context) FinishSpanWithError(span trace.Span, err error)
- func (c *Context) FinishSpanWithHTTPStatus(span trace.Span, statusCode int)
- func (c *Context) Forbidden(err error)
- func (c *Context) Gone(err error)
- func (c *Context) IncrementCounter(name string, attributes ...attribute.KeyValue)
- func (c *Context) InternalError(err error)
- func (c *Context) MustBind(out any, opts ...BindOption) bool
- func (c *Context) NotFound(err error)
- func (c *Context) Presence() validation.PresenceMap
- func (c *Context) RecordError(err error)
- func (c *Context) RecordHistogram(name string, value float64, attributes ...attribute.KeyValue)
- func (c *Context) ResetBinding()
- func (c *Context) ServiceUnavailable(err error)
- func (c *Context) SetGauge(name string, value float64, attributes ...attribute.KeyValue)
- func (c *Context) SetSpanAttribute(key string, value any)
- func (c *Context) Span() trace.Span
- func (c *Context) SpanID() string
- func (c *Context) StartSpan(name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
- func (c *Context) TooManyRequests(err error)
- func (c *Context) TraceContext() context.Context
- func (c *Context) TraceID() string
- func (c *Context) Tracer() *tracing.Tracer
- func (c *Context) Unauthorized(err error)
- func (c *Context) UnprocessableEntity(err error)
- func (c *Context) Validate(v any, opts ...ValidateOption) error
- func (c *Context) WithSpan(name string, fn func(context.Context) error) error
- type DebugOption
- type Gate
- type Group
- func (g *Group) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (g *Group) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (g *Group) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (g *Group) Group(prefix string, middleware ...HandlerFunc) *Group
- func (g *Group) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (g *Group) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (g *Group) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (g *Group) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (g *Group) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (g *Group) Use(middleware ...HandlerFunc)
- type HandlerFunc
- type HealthOption
- func WithHealthPrefix(prefix string) HealthOption
- func WithHealthTimeout(d time.Duration) HealthOption
- func WithLivenessCheck(name string, check CheckFunc) HealthOption
- func WithLivezPath(path string) HealthOption
- func WithReadinessCheck(name string, check CheckFunc) HealthOption
- func WithReadyzPath(path string) HealthOption
- type Hooks
- type LoggingConfig
- type LoggingHandler
- type LoggingLevel
- type MTLSOption
- func WithAuthorize(fn func(*x509.Certificate) (principal string, allowed bool)) MTLSOption
- func WithClientCAs(pool *x509.CertPool) MTLSOption
- func WithConfigForClient(fn func(*tls.ClientHelloInfo) (*tls.Config, error)) MTLSOption
- func WithMinVersion(version uint16) MTLSOption
- func WithSNI(fn func(*tls.ClientHelloInfo) (*tls.Certificate, error)) MTLSOption
- type MetricsConfig
- type MetricsProvider
- type ObservabilityConfig
- type ObservabilityOption
- func WithAccessLogScope(scope AccessLogScope) ObservabilityOption
- func WithAccessLogging(enabled bool) ObservabilityOption
- func WithExcludePaths(paths ...string) ObservabilityOption
- func WithExcludePatterns(patterns ...string) ObservabilityOption
- func WithExcludePrefixes(prefixes ...string) ObservabilityOption
- func WithLogging(opts ...logging.Option) ObservabilityOption
- func WithMetrics(opts ...metrics.Option) ObservabilityOption
- func WithMetricsOnMainRouter(path string) ObservabilityOption
- func WithMetricsSeparateServer(addr, path string) ObservabilityOption
- func WithSlowThreshold(d time.Duration) ObservabilityOption
- func WithTracing(opts ...tracing.Option) ObservabilityOption
- func WithoutDefaultExclusions() ObservabilityOption
- type Option
- func WithDebugEndpoints(opts ...DebugOption) Option
- func WithDefaultErrorFormat(mediaType string) Option
- func WithEnv() Option
- func WithEnvPrefix(prefix string) Option
- func WithEnvironment(env string) Option
- func WithErrorFormatterFor(mediaType string, opts ...errors.Option) Option
- func WithErrorFormatters(formatters map[string]errors.Formatter) Option
- func WithHealthEndpoints(opts ...HealthOption) Option
- func WithHost(host string) Option
- func WithMTLS(serverCert tls.Certificate, opts ...MTLSOption) Option
- func WithMiddleware(middlewares ...HandlerFunc) Option
- func WithObservability(opts ...ObservabilityOption) Option
- func WithObservabilityFromConfig(cfg ObservabilityConfig) Option
- func WithOpenAPI(opts ...openapi.Option) Option
- func WithPort(port int) Option
- func WithRouter(opts ...router.Option) Option
- func WithServer(opts ...ServerOption) Option
- func WithServiceName(name string) Option
- func WithServiceVersion(version string) Option
- func WithTLS(certFile, keyFile string) Option
- func WithValidationEngine(engine *validation.Engine) Option
- func WithoutDefaultMiddleware() Option
- type ReadinessManager
- type RouteOption
- type ServerOption
- func WithIdleTimeout(d time.Duration) ServerOption
- func WithMaxHeaderBytes(n int) ServerOption
- func WithReadHeaderTimeout(d time.Duration) ServerOption
- func WithReadTimeout(d time.Duration) ServerOption
- func WithShutdownTimeout(d time.Duration) ServerOption
- func WithWriteTimeout(d time.Duration) ServerOption
- type TestOption
- type TracingConfig
- type TracingProvider
- type ValidateOption
- type VersionGroup
- func (vg *VersionGroup) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (vg *VersionGroup) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (vg *VersionGroup) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (vg *VersionGroup) Group(prefix string, middleware ...HandlerFunc) *VersionGroup
- func (vg *VersionGroup) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (vg *VersionGroup) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (vg *VersionGroup) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (vg *VersionGroup) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (vg *VersionGroup) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
- func (vg *VersionGroup) Use(middleware ...HandlerFunc)
Examples ¶
- Package
- Package (HealthEndpoints)
- Package (LifecycleHooks)
- Package (Middleware)
- Package (PartialValidation)
- Package (Routing)
- Package (StrictBinding)
- Package (Testing)
- Package (WithObservability)
- App.OnReload
- App.Reload
- Bind
- Context.AddCounter
- Context.Bind
- Context.Bind (WithOptions)
- Context.BindOnly
- Context.FinishSpan
- Context.FinishSpanWithError
- Context.MustBind
- Context.StartSpan
- Context.WithSpan
- MustBind
Constants ¶
const ( DefaultServiceName = "rivaas-app" DefaultVersion = "1.0.0" DefaultEnvironment = "development" DefaultPort = 8080 DefaultTLSPort = 8443 // Default port when serving TLS or mTLS (overridable with WithPort) DefaultReadTimeout = 10 * time.Second DefaultWriteTimeout = 10 * time.Second DefaultIdleTimeout = 60 * time.Second DefaultReadHeaderTimeout = 2 * time.Second DefaultMaxHeaderBytes = 1 << 20 // 1MB DefaultShutdownTimeout = 30 * time.Second // Environment constants EnvironmentDevelopment = "development" EnvironmentProduction = "production" )
Default configuration values.
const ( // Core application settings EnvMode = "ENV" // Environment mode: "development" or "production" EnvServiceName = "SERVICE_NAME" // Service name for observability EnvServiceVersion = "SERVICE_VERSION" // Service version // Server settings EnvPort = "PORT" // HTTP server port (e.g., "8080") EnvHost = "HOST" // HTTP server host/interface (e.g., "127.0.0.1") EnvReadTimeout = "READ_TIMEOUT" // Request read timeout (e.g., "10s") EnvWriteTimeout = "WRITE_TIMEOUT" // Response write timeout (e.g., "10s") EnvShutdownTimeout = "SHUTDOWN_TIMEOUT" // Graceful shutdown timeout (e.g., "30s") // Logging settings EnvLogLevel = "LOG_LEVEL" // Log level: "debug", "info", "warn", "error" EnvLogFormat = "LOG_FORMAT" // Log format: "json", "text", or "console" // Observability settings EnvMetricsExporter = "METRICS_EXPORTER" // Metrics exporter: "prometheus", "otlp", or "stdout" EnvMetricsAddr = "METRICS_ADDR" // Prometheus address (e.g., ":9090") EnvMetricsPath = "METRICS_PATH" // Prometheus path (e.g., "/metrics") EnvMetricsEndpoint = "METRICS_ENDPOINT" // OTLP endpoint (e.g., "http://localhost:4318") EnvTracingExporter = "TRACING_EXPORTER" // Tracing exporter: "otlp", "otlp-http", or "stdout" EnvTracingEndpoint = "TRACING_ENDPOINT" // OTLP endpoint (e.g., "localhost:4317") // Debug settings EnvPprofEnabled = "PPROF_ENABLED" // Enable pprof: "true" or "false" )
Environment variable names for framework configuration. These are used when WithEnv or WithEnvPrefix is called.
const EnvPrefix = "RIVAAS_"
EnvPrefix is the environment variable prefix for Rivaas framework settings.
Variables ¶
var ErrRouterFrozen = errors.New("cannot register hooks after router is frozen")
ErrRouterFrozen is returned when a lifecycle hook is registered after the router has been frozen (e.g. after Start() or Router().Freeze()). Register all hooks before starting the server. Use errors.Is(err, app.ErrRouterFrozen) to detect.
Functions ¶
func Bind ¶ added in v0.9.0
func Bind[T any](c *Context, opts ...BindOption) (T, error)
Bind binds request data to type T and validates it. This is the recommended way to bind requests with type safety.
Bind automatically:
- Detects Content-Type and binds from appropriate sources
- Binds path, query, header, and cookie parameters based on struct tags
- Validates the bound struct using the configured strategy
- Tracks field presence for partial validation support
Errors:
- binding.ErrOutMustBePointer: T is not a struct type
- binding.ErrUnsupportedContentType: Content-Type not supported
- validation.Error: validation failed (one or more field errors)
Example:
req, err := app.Bind[CreateUserRequest](c)
if err != nil {
c.Fail(err)
return
}
// req is of type CreateUserRequest
With options (e.g. PATCH with partial validation, or strict unknown-field rejection):
req, err := app.Bind[CreateUserRequest](c, app.WithStrict()) req, err := app.Bind[UpdateUserRequest](c, app.WithPartial())
Example ¶
ExampleBind demonstrates type-safe binding with generics.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
}
a.POST("/users", func(c *app.Context) {
req, err := app.Bind[CreateUserRequest](c)
if err != nil {
c.Fail(err)
return
}
if jsonErr := c.JSON(http.StatusCreated, map[string]string{
"message": "User created",
"name": req.Name,
}); jsonErr != nil {
log.Printf("Failed to write response: %v", jsonErr)
}
})
fmt.Println("Handler with generic Bind registered")
}
Output: Handler with generic Bind registered
func BindOnly ¶ added in v0.9.0
func BindOnly[T any](c *Context, opts ...BindOption) (T, error)
BindOnly binds request data to type T without validation. Use when you need fine-grained control over the bind/validate lifecycle.
Example:
req, err := app.BindOnly[Request](c)
if err != nil {
c.Fail(err)
return
}
req.Normalize() // Custom processing
if err := c.Validate(&req); err != nil {
c.Fail(err)
return
}
func ExpectJSON ¶
ExpectJSON is a test helper that asserts a response has the expected status code and JSON body. It decodes the JSON into the provided output value.
Example:
var user User ExpectJSON(t, resp, 200, &user) assert.Equal(t, "Alice", user.Name)
func GetMTLSCertificate ¶
func GetMTLSCertificate(req *http.Request) *x509.Certificate
GetMTLSCertificate extracts the client certificate from an HTTP request. It returns the first peer certificate if available, or nil if the request is not using mTLS or no certificate is present.
It is useful for extracting principal information (e.g., CN, SAN) in handlers after the connection has been authorized via WithAuthorize.
Example:
func handler(c *router.Context) {
cert := app.GetMTLSCertificate(c.Request)
if cert != nil {
principal := cert.Subject.CommonName
// Use principal for authorization, logging, etc.
}
}
func MustBind ¶ added in v0.9.0
func MustBind[T any](c *Context, opts ...BindOption) (T, bool)
MustBind binds and validates, writing an error response on failure. Returns the bound value and true if successful.
MustBind eliminates boilerplate error handling for the common case.
Example:
req, ok := app.MustBind[CreateUserRequest](c)
if !ok {
return // Error already written
}
// req is of type CreateUserRequest
Example ¶
ExampleMustBind demonstrates type-safe binding with the Must pattern.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
}
a.POST("/users", func(c *app.Context) {
req, ok := app.MustBind[CreateUserRequest](c)
if !ok {
return // Error already written
}
if err := c.JSON(http.StatusCreated, map[string]string{
"message": "User created",
"name": req.Name,
}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
fmt.Println("Handler with MustBind registered")
}
Output: Handler with MustBind registered
Types ¶
type AccessLogScope ¶ added in v0.18.0
type AccessLogScope string
AccessLogScope controls which HTTP requests are logged as access logs. When unset, production defaults to AccessLogScopeErrorsOnly and development to AccessLogScopeAll.
const ( // AccessLogScopeAll logs every request (including 2xx). Use in production only if you need full request logs. AccessLogScopeAll AccessLogScope = "all" // AccessLogScopeErrorsOnly logs only errors (status >= 400) and slow requests. Reduces log volume. AccessLogScopeErrorsOnly AccessLogScope = "errors_only" )
type App ¶
type App struct {
// contains filtered or unexported fields
}
App represents the high-level application framework. App wraps the router with integrated observability and common middleware. Create an App using New or MustNew.
func MustNew ¶
MustNew creates a new App instance or panics on error. It is a convenience function that panics if initialization fails, useful for initialization in main() functions.
Example:
app := app.MustNew(
app.WithServiceName("my-service"),
app.WithServiceVersion("v1.0.0"),
app.WithObservability(
app.WithLogging(logging.WithJSONHandler()),
app.WithMetrics(), // Prometheus is default
app.WithTracing(tracing.WithOTLP("localhost:4317")),
),
)
func New ¶
New creates a new App instance with the given options. New returns an error if the configuration is invalid or initialization fails.
Example:
app, err := app.New(
app.WithServiceName("my-service"),
app.WithObservability(
app.WithMetrics(), // Prometheus is default
app.WithTracing(tracing.WithOTLP("localhost:4317")),
),
)
if err != nil {
log.Fatal(err)
}
func (*App) Any ¶
func (a *App) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
Any registers a route that matches all HTTP methods. It is useful for catch-all endpoints like health checks or proxies.
It registers 7 separate routes internally (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). For endpoints that only need specific methods, use individual method registrations (GET, POST, etc.).
Returns the GET route (most common for docs/constraints).
Example:
app.Any("/health", healthCheckHandler)
app.Any("/webhook/*", webhookProxyHandler)
func (*App) BaseLogger ¶
BaseLogger returns the application's base logger without request-specific context. BaseLogger should be used for background jobs, startup/shutdown logging, or other non-request contexts.
BaseLogger never returns nil - if no logger is configured, a no-op logger is returned.
Example:
app := app.New(...)
app.BaseLogger().Info("application started",
slog.String("port", "8080"),
slog.String("environment", "production"),
)
// Background job
go func() {
app.BaseLogger().Info("background job started")
// ... do work
}()
func (*App) DELETE ¶
func (a *App) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
DELETE registers a DELETE route with optional middleware and OpenAPI documentation.
Example:
app.DELETE("/users/:id", deleteUser,
app.WithBefore(authMiddleware),
app.WithDoc(
openapi.WithSummary("Delete user"),
openapi.WithResponse(204, nil),
),
)
func (*App) Environment ¶
Environment returns the current environment (development or production).
Example:
if app.Environment() == "production" {
// Enable production-only features
}
func (*App) File ¶
File serves a single file at the given path. File is commonly used for serving favicon.ico, robots.txt, etc.
Example:
app.File("/favicon.ico", "./static/favicon.ico")
app.File("/robots.txt", "./static/robots.txt")
func (*App) GET ¶
func (a *App) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
GET registers a GET route with optional middleware and OpenAPI documentation.
The first parameter is the main handler. Additional options can configure: - Pre-handler middleware (WithBefore) - Post-handler middleware (WithAfter) - OpenAPI documentation (WithDoc)
Example:
app.GET("/users/:id", getUser,
app.WithBefore(authMiddleware, rateLimitMiddleware),
app.WithAfter(auditLogMiddleware),
app.WithDoc(
openapi.WithSummary("Get user"),
openapi.WithDescription("Retrieves a user by ID"),
openapi.WithResponse(200, UserResponse{}),
openapi.WithResponse(404, ErrorResponse{}),
openapi.WithTags("users"),
),
)
Returns the underlying router.Route for setting constraints:
app.GET("/users/:id", getUser, opts...).WhereInt("id")
func (*App) GetMetricsHandler ¶
GetMetricsHandler returns the metrics HTTP handler if metrics are enabled. It returns an error if metrics are not enabled or if using a non-Prometheus provider.
Example:
handler, err := app.GetMetricsHandler()
if err != nil {
return err
}
http.Handle("/metrics", handler)
func (*App) GetMetricsServerAddress ¶
GetMetricsServerAddress returns the metrics server address if metrics are enabled. It returns an empty string if metrics are not enabled.
Example:
addr := app.GetMetricsServerAddress()
if addr != "" {
fmt.Printf("Metrics available at http://%s/metrics\n", addr)
}
func (*App) Group ¶
func (a *App) Group(prefix string, middleware ...HandlerFunc) *Group
Group creates a new route group. Group creates groups that support HandlerFunc (with Context), providing access to binding and validation features.
Example:
api := app.Group("/api/v1", AuthMiddleware())
api.GET("/users", handler) // handler receives *app.Context
api.POST("/users", handler) // handler receives *app.Context
func (*App) HEAD ¶
func (a *App) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
HEAD registers a HEAD route with optional middleware and OpenAPI documentation.
Example:
app.HEAD("/users/:id", handler).WhereInt("id")
func (*App) Metrics ¶
Metrics returns the metrics configuration if enabled. It returns nil if metrics are not enabled.
Example:
if recorder := app.Metrics(); recorder != nil {
// Access metrics recorder
}
func (*App) MustURLFor ¶
func (a *App) MustURLFor(routeName string, params map[string]string, query map[string][]string) string
MustURLFor generates a URL from a route name and parameters, panicking on error. MustURLFor should be used when you're certain the route exists and all parameters are provided.
Example:
url := app.MustURLFor("users.get", map[string]string{"id": "123"}, nil)
// Returns: "/users/123"
func (*App) NoRoute ¶
func (a *App) NoRoute(handler HandlerFunc)
NoRoute sets the handler for requests that don't match any registered routes. NoRoute allows customizing 404 error responses instead of using the default http.NotFound.
Example:
app.NoRoute(func(c *Context) {
c.JSON(http.StatusNotFound, map[string]string{"error": "route not found"})
})
Setting handler to nil restores the default http.NotFound behavior.
func (*App) OPTIONS ¶
func (a *App) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
OPTIONS registers an OPTIONS route with optional middleware and OpenAPI documentation.
Example:
app.OPTIONS("/users", handler)
func (*App) OnReady ¶
OnReady registers a hook that runs after the server starts listening. Hooks can run asynchronously and errors are logged but don't stop the server. It should be used for warmup tasks, service discovery registration, etc.
Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.
Example:
app.OnReady(func() {
log.Println("Server ready!")
registerWithConsul()
})
func (*App) OnReload ¶ added in v0.14.0
OnReload registers a hook that runs when the application receives a reload signal (SIGHUP) or when Reload() is called programmatically. Hooks run sequentially, and if any hook returns an error, subsequent hooks are skipped. Reload errors are logged but do not stop the server - it continues serving with the old configuration.
OnReload should be used for reloading runtime configuration without restarting the server:
- Re-reading configuration files
- Rotating TLS certificates
- Flushing caches
- Adjusting log levels
- Updating connection pool settings
SIGHUP signal handling is automatically enabled when at least one OnReload hook is registered. On Unix systems, sending SIGHUP to the process will trigger all registered reload hooks. On Windows, SIGHUP is not available, but Reload() can still be called programmatically.
Note: Routes and middleware cannot be reloaded as the router is frozen after startup.
Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.
Example:
app.OnReload(func(ctx context.Context) error {
cfg, err := loadConfig("config.yaml")
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
applyConfig(cfg)
return nil
})
Example ¶
ExampleApp_OnReload demonstrates how to use the OnReload hook to reload configuration when SIGHUP is received.
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"rivaas.dev/app"
)
func main() {
// Create app - SIGHUP handling is automatically enabled when hooks are registered
myApp := app.MustNew(
app.WithServiceName("example-reload"),
)
// Register a reload hook to reload configuration
// SIGHUP is automatically enabled when this hook is registered
if err := myApp.OnReload(func(ctx context.Context) error {
fmt.Println("Reloading configuration...")
// In a real application, you would:
// - Re-read config files
// - Rotate TLS certificates
// - Flush caches
// - Update connection pool settings
return nil
}); err != nil {
log.Fatal(err)
}
myApp.GET("/", func(c *app.Context) {
_ = c.String(200, "Hello, World!") //nolint:errcheck // Example code
})
// Set up signal handling for graceful shutdown
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
// Start server - SIGHUP will trigger reload hooks automatically
if err := myApp.Start(ctx); err != nil {
log.Fatal(err)
}
}
Output:
func (*App) OnRoute ¶
OnRoute registers a hook that fires when a route is registered. OnRoute is useful for route validation, logging, or documentation generation. OnRoute hooks are disabled after router is frozen.
Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.
Example:
app.OnRoute(func(rt *route.Route) {
log.Printf("Registered: %s %s", rt.Method(), rt.Path())
})
func (*App) OnShutdown ¶
OnShutdown registers a hook that runs during graceful shutdown. Hooks run in reverse order (LIFO) and receive a context with the shutdown timeout. It should be used for cleanup that must complete within the timeout (closing connections, flushing buffers).
Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.
Example:
app.OnShutdown(func(ctx context.Context) {
db.Close()
flushMetrics(ctx)
})
func (*App) OnStart ¶
OnStart registers a hook that runs before the server starts listening. Hooks run sequentially, and if any hook returns an error, startup is aborted. It should be used for initialization that must succeed (database connections, migrations, etc.).
Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.
Example:
app.OnStart(func(ctx context.Context) error {
return db.PingContext(ctx)
})
func (*App) OnStop ¶
OnStop registers a hook that runs after the server stops. Hooks run in best-effort mode - panics are caught and logged. It should be used for final cleanup that doesn't need to complete within a timeout.
Returns ErrRouterFrozen if called after the router is frozen (e.g. after Start() or Freeze()). Register all hooks before starting the server.
Example:
app.OnStop(func() {
cleanupTempFiles()
})
func (*App) PATCH ¶
func (a *App) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
PATCH registers a PATCH route with optional middleware and OpenAPI documentation.
Example:
app.PATCH("/users/:id", patchUser,
app.WithBefore(authMiddleware),
app.WithDoc(
openapi.WithSummary("Partially update user"),
openapi.WithRequest(PatchUserRequest{}),
openapi.WithResponse(200, UserResponse{}),
),
)
func (*App) POST ¶
func (a *App) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
POST registers a POST route with optional middleware and OpenAPI documentation.
Example:
app.POST("/users", createUser,
app.WithBefore(authMiddleware),
app.WithDoc(
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
openapi.WithResponse(201, UserResponse{}),
),
)
func (*App) PUT ¶
func (a *App) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
PUT registers a PUT route with optional middleware and OpenAPI documentation.
Example:
app.PUT("/users/:id", updateUser,
app.WithBefore(authMiddleware),
app.WithDoc(
openapi.WithSummary("Update user"),
openapi.WithRequest(UpdateUserRequest{}),
openapi.WithResponse(200, UserResponse{}),
),
)
func (*App) PrintRoutes ¶
func (a *App) PrintRoutes()
PrintRoutes prints all registered routes to stdout in a formatted table. It is useful for development and debugging to see all available routes.
It uses lipgloss/table for terminal output with color-coded HTTP methods and proper table formatting. A colorprofile.Writer automatically downsamples ANSI colors to match the terminal's capabilities (TrueColor → ANSI256 → ANSI). If output is not a TTY, ANSI sequences are stripped entirely. This respects the NO_COLOR environment variable and handles all terminal capability detection automatically.
Colors are only enabled in development mode.
Example output:
┌────────┬─────────┬──────────────────┬──────────────────┐ │ Method │ Version │ Path │ Handler │ ├────────┼─────────┼──────────────────┼──────────────────┤ │ GET │ - │ / │ handler │ │ GET │ v1 │ /users/:id │ handler │ │ POST │ - │ /users │ handler │ └────────┴─────────┴──────────────────┴──────────────────┘
func (*App) Readiness ¶
func (a *App) Readiness() *ReadinessManager
Readiness returns the readiness manager for registering gates.
Example:
type DatabaseGate struct {
db *sql.DB
}
func (g *DatabaseGate) Ready() bool { return g.db.Ping() == nil }
func (g *DatabaseGate) Name() string { return "database" }
app.Readiness().Register("db", &DatabaseGate{db: db})
func (*App) Reload ¶ added in v0.14.0
Reload triggers a reload of the application by executing all registered OnReload hooks. Reload can be called programmatically or is triggered automatically when SIGHUP is received (SIGHUP handling is automatically enabled when OnReload hooks are registered).
Hooks are executed sequentially, and if any hook returns an error, subsequent hooks are skipped. Reload errors are logged but do not stop the server - it continues serving with the old configuration.
Concurrent calls to Reload() are serialized via an internal mutex to prevent race conditions.
Example - Programmatic reload:
if err := app.Reload(ctx); err != nil {
log.Printf("reload failed: %v", err)
}
Example - Admin endpoint:
app.POST("/admin/reload", func(c *app.Context) {
if err := app.Reload(c.Request.Context()); err != nil {
c.InternalError(err)
return
}
c.JSON(200, map[string]string{"status": "reloaded"})
})
Example ¶
ExampleApp_Reload demonstrates programmatic reload without SIGHUP.
package main
import (
"context"
"fmt"
"log"
"rivaas.dev/app"
)
func main() {
myApp := app.MustNew(
app.WithServiceName("example-reload-programmatic"),
)
var configVersion int
if err := myApp.OnReload(func(ctx context.Context) error {
configVersion++
fmt.Printf("Config reloaded, version: %d\n", configVersion)
return nil
}); err != nil {
log.Fatal(err)
}
// Create an admin endpoint that triggers reload
myApp.POST("/admin/reload", func(c *app.Context) {
if err := myApp.Reload(c.Request.Context()); err != nil {
c.InternalError(err)
return
}
_ = c.JSON(200, map[string]string{ //nolint:errcheck // Example code
"status": "reloaded",
})
})
// Programmatically trigger reload
ctx := context.Background()
if err := myApp.Reload(ctx); err != nil {
log.Printf("reload failed: %v", err)
}
}
Output: Config reloaded, version: 1
func (*App) Route ¶
Route retrieves a route by name. It returns the route and true if found, false otherwise. It panics if the router is not frozen (call after app.Start() or app.Router().Freeze()).
Example:
route, ok := app.Route("users.get")
if ok {
fmt.Printf("Route: %s %s\n", route.Method(), route.Path())
}
func (*App) Router ¶
Router returns the underlying router for advanced usage. It provides access to router-level features that are not exposed through App.
Example:
app.Router().Freeze() // Manually freeze router app.Router().SetObservabilityRecorder(customRecorder)
func (*App) Routes ¶
Routes returns an immutable snapshot of all named routes. Routes panics if the router is not frozen (call after app.Start() or app.Router().Freeze()).
Example:
routes := app.Routes()
for _, route := range routes {
fmt.Printf("%s: %s %s\n", route.Name(), route.Method(), route.Path())
}
func (*App) ServiceName ¶
ServiceName returns the configured service name.
Example:
name := app.ServiceName()
fmt.Printf("Service: %s\n", name)
func (*App) ServiceVersion ¶
ServiceVersion returns the configured service version.
Example:
version := app.ServiceVersion()
fmt.Printf("Version: %s\n", version)
func (*App) Start ¶ added in v0.3.0
Start starts the server with graceful shutdown. Start automatically freezes the router before starting, making routes immutable. The server runs HTTP, HTTPS, or mTLS depending on configuration: use WithTLS or WithMTLS at construction to serve over TLS; otherwise plain HTTP is used.
The server listens on the address configured via WithPort and WithHost. Default is :8080 for HTTP and :8443 when using WithTLS or WithMTLS, overridable by WithPort and by RIVAAS_PORT and RIVAAS_HOST when WithEnv is used.
The context controls the application lifecycle - when canceled, it triggers graceful shutdown of the server and all observability components (metrics, tracing).
Note: Signal handling should be configured by the caller using signal.NotifyContext. This follows the Go pattern of explicit signal handling at the application boundary.
Example:
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
app := app.MustNew(
app.WithServiceName("my-service"),
app.WithPort(3000),
)
if err := app.Start(ctx); err != nil {
log.Fatal(err)
}
HTTPS example (default port 8443):
app := app.MustNew(
app.WithServiceName("my-service"),
app.WithTLS("server.crt", "server.key"),
)
if err := app.Start(ctx); err != nil { ... }
func (*App) Static ¶
Static serves static files from the given directory. Static is a convenience wrapper that delegates to router.Static.
Example:
app.Static("/static", "./public")
func (*App) StaticFS ¶
func (a *App) StaticFS(prefix string, fs http.FileSystem)
StaticFS serves files from the given filesystem. StaticFS is particularly useful with Go's embed.FS for embedding static assets.
Example:
//go:embed static
var staticFiles embed.FS
app.StaticFS("/static", http.FS(staticFiles))
func (*App) Test ¶
Test executes an HTTP request against the app without starting a server. Test is useful for unit testing handlers and middleware.
The request is executed in a goroutine with an optional timeout via context. If a timeout occurs, Test returns an error immediately, but the handler goroutine may continue running until it completes (the router's ServeHTTP cannot be canceled mid-execution). This is acceptable for test scenarios where handlers are expected to complete within a reasonable time.
Test returns an *http.Response that can be inspected for status, headers, and body.
Example:
req := httptest.NewRequest("GET", "/users/123", nil)
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, 200, resp.StatusCode)
func (*App) TestJSON ¶
TestJSON is a convenience method for testing JSON requests. TestJSON automatically sets Content-Type and encodes the body as JSON.
Example:
body := map[string]string{"name": "Alice"}
resp, err := app.TestJSON("POST", "/users", body)
func (*App) Tracing ¶
Tracing returns the tracing configuration if enabled. It returns nil if tracing is not enabled.
Example:
if cfg := app.Tracing(); cfg != nil {
// Access tracing configuration
}
func (*App) URLFor ¶
func (a *App) URLFor(routeName string, params map[string]string, query map[string][]string) (string, error)
URLFor generates a URL from a route name and parameters. URLFor returns an error if the route is not found or if required parameters are missing.
Example:
url, err := app.URLFor("users.get", map[string]string{"id": "123"}, nil)
// Returns: "/users/123", nil
func (*App) Use ¶
func (a *App) Use(middleware ...HandlerFunc)
Use adds middleware to the app. Use adds middleware that will be executed for all routes.
Example:
app.Use(AuthMiddleware(), LoggingMiddleware())
app.GET("/users", handler) // Will execute auth + logging + handler
func (*App) ValidateRoutes ¶ added in v0.20.0
ValidateRoutes returns all route-option validation errors (e.g. nil route options) collected during route registration. Call before starting the server (e.g. before [Router].Freeze or before passing the app to a runner) so config errors are reported at init. Returns nil if there are no route validation errors.
func (*App) Version ¶
func (a *App) Version(version string) *VersionGroup
Version creates a version group that supports HandlerFunc. Version allows using Context features (binding, validation, logging) with router versioning.
Routes registered in a version group are automatically scoped to that version. The version is detected from the request path, headers, query parameters, or other configured versioning strategies.
Example:
v1 := app.Version("v1")
v1.GET("/status", handlers.Status)
v1.POST("/users", handlers.CreateUser)
func (*App) WrapHandler ¶
func (a *App) WrapHandler(handler HandlerFunc) router.HandlerFunc
WrapHandler wraps an app.HandlerFunc to convert it to a router.HandlerFunc. It creates an app.Context from the router.Context and manages pooling.
The context is guaranteed to be returned to the pool even if the handler panics, ensuring no context leaks occur. The recovery middleware will still catch panics for proper error handling, but this ensures resource cleanup.
It is useful when you need to use router-level features (like route constraints) while still using app.HandlerFunc with full app.Context support.
Example:
a.Router().GET("/users/:id", a.WrapHandler(handlers.GetUserByID)).WhereInt("id")
type BindOption ¶ added in v0.9.0
type BindOption func(*bindConfig)
BindOption configures Bind behavior. BindOptions can be passed to Context.Bind, Context.MustBind, Bind, and MustBind.
func WithBindingOptions ¶ added in v0.9.0
func WithBindingOptions(opts ...binding.Option) BindOption
WithBindingOptions passes options directly to the binding package. Use for advanced binding configuration.
Example:
req, err := app.Bind[Request](c,
app.WithBindingOptions(
binding.WithTimeLayouts("2006-01-02"),
binding.WithMaxDepth(16),
),
)
func WithPartial ¶ added in v0.9.0
func WithPartial() BindOption
WithPartial enables partial validation for PATCH requests. Only fields present in the request body are validated. The "required" constraint is ignored for absent fields.
Example:
req, err := app.Bind[UpdateUserRequest](c, app.WithPartial())
func WithPresence ¶ added in v0.9.0
func WithPresence(pm validation.PresenceMap) BindOption
WithPresence explicitly sets the presence map. Usually auto-detected from JSON body; use this for custom scenarios.
Example:
pm, _ := validation.ComputePresence(rawJSON) req, err := app.Bind[Request](c, app.WithPresence(pm))
func WithStrict ¶ added in v0.9.0
func WithStrict() BindOption
WithStrict rejects unknown JSON fields during binding. Use this to catch typos and API drift early.
Example:
req, err := app.Bind[CreateUserRequest](c, app.WithStrict())
Equivalent to the old BindAndValidateStrict method.
func WithValidationOptions ¶ added in v0.9.0
func WithValidationOptions(opts ...validation.Option) BindOption
WithValidationOptions passes options directly to the validation package. Use for advanced validation configuration.
Example:
req, err := app.Bind[Request](c,
app.WithValidationOptions(
validation.WithStrategy(validation.StrategyTags),
validation.WithMaxErrors(10),
),
)
func WithoutValidation ¶ added in v0.9.0
func WithoutValidation() BindOption
WithoutValidation skips the validation step. Use when you only need binding, or will validate separately.
Example:
req, err := app.Bind[Request](c, app.WithoutValidation())
Equivalent to using Context.BindOnly or BindOnly.
type CheckFunc ¶
CheckFunc defines a function that performs a health or readiness check. The function should return nil if the check passes, or an error if it fails. The context may be canceled if the check takes too long.
type ConfigError ¶
type ConfigError struct {
// Field is the name of the configuration field that failed validation
Field string
// Value is the actual value that was provided (may be nil for missing values)
Value any
// Message is a human-readable error message explaining the validation failure
Message string
// Constraint is an optional constraint that was violated (e.g., "must be positive", "must be between X and Y")
Constraint string
// Hint is an optional suggestion for how to fix the error (e.g., which option or env var to use).
// When non-empty, Error() appends it to the returned string.
Hint string
}
ConfigError represents a configuration validation error with structured information.
ConfigError provides structured error information that enables:
- Programmatic error inspection (field-level error detection)
- Rich formatting for CLI/web UI display
- Error aggregation for batch validation
- Internationalization support (field names remain constant)
Validation happens once at startup, not during request handling.
func (*ConfigError) Error ¶
func (e *ConfigError) Error() string
Error implements the error interface and returns a formatted error message. It formats the ConfigError as a human-readable string. When Hint is non-empty, it is appended in parentheses so the message is actionable.
func (*ConfigError) Unwrap ¶
func (e *ConfigError) Unwrap() error
Unwrap returns nil as ConfigError is a leaf error type. It allows errors.Is() and errors.As() to work correctly.
type ConfigErrors ¶ added in v0.20.0
type ConfigErrors struct {
Errors []*ConfigError
}
ConfigErrors represents multiple configuration validation errors. ConfigErrors allows collecting all config errors before returning them.
func (*ConfigErrors) Add ¶ added in v0.20.0
func (ce *ConfigErrors) Add(err *ConfigError)
Add appends a new ConfigError to the ConfigErrors. It collects config errors for batch reporting.
func (*ConfigErrors) Error ¶ added in v0.20.0
func (ce *ConfigErrors) Error() string
Error implements the error interface and returns a formatted error message listing all config errors.
func (*ConfigErrors) HasErrors ¶ added in v0.20.0
func (ce *ConfigErrors) HasErrors() bool
HasErrors returns true if there are any config errors. It checks if the ConfigErrors contains any errors.
func (*ConfigErrors) ToError ¶ added in v0.20.0
func (ce *ConfigErrors) ToError() error
ToError returns nil if there are no errors, otherwise returns the ConfigErrors as an error. It is useful for returning from config validation functions.
type Context ¶
type Context struct {
*router.Context // Embed router context for HTTP functionality
// contains filtered or unexported fields
}
Context wraps router.Context with app-level features including binding and validation. Context embeds router.Context to provide all HTTP routing functionality while adding high-level integration with binding and validation packages.
Context instances are pooled by the App and reused across requests. They are normally created and initialized by the App (via the pool and wrapHandler). When constructing a Context for tests (e.g. from the pool), set app for app-specific error formatter, logging, and observability. If app is nil, methods like Fail() still work using the default error formatter and slog.Default().
func TestContextWithBody ¶ added in v0.9.0
TestContextWithBody creates a Context with JSON body for testing. TestContextWithBody is useful for testing binding and validation logic.
Example:
body := map[string]string{"name": "Alice", "email": "alice@example.com"}
c, err := app.TestContextWithBody("POST", "/users", body)
if err != nil {
t.Fatal(err)
}
var req CreateUserRequest
err = c.Bind(&req)
func TestContextWithBodyAndApp ¶ added in v0.20.0
TestContextWithBodyAndApp creates a Context with JSON body using the given App. Use this when testing app-specific configuration (e.g. WithValidationEngine).
func TestContextWithForm ¶ added in v0.9.0
TestContextWithForm creates a Context with form data for testing. TestContextWithForm is useful for testing form binding logic.
Example:
values := map[string][]string{
"name": {"Alice"},
"email": {"alice@example.com"},
}
c, err := app.TestContextWithForm("POST", "/users", values)
if err != nil {
t.Fatal(err)
}
var req CreateUserRequest
err = c.Bind(&req)
func (*Context) AddCounter ¶ added in v0.19.0
AddCounter adds a value to a custom counter metric. This is a no-op when metrics are not configured.
Example ¶
ExampleContext_AddCounter demonstrates adding to a counter by an arbitrary amount.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"go.opentelemetry.io/otel/attribute"
"rivaas.dev/app"
"rivaas.dev/metrics"
)
func main() {
a := app.MustNew(
app.WithObservability(app.WithMetrics(metrics.WithStdout())),
)
a.POST("/upload", func(c *app.Context) {
// Simulate processing N bytes
bytesProcessed := int64(1024)
c.AddCounter("bytes_processed", bytesProcessed, attribute.String("type", "upload"))
c.IncrementCounter("uploads_total")
if err := c.JSON(http.StatusOK, map[string]string{"status": "ok"}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
req := httptest.NewRequest(http.MethodPost, "/upload", nil)
resp, err := a.Test(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d\n", resp.StatusCode)
//nolint:errcheck // example cleanup
resp.Body.Close()
}
Output: Status: 200
func (*Context) AddSpanEvent ¶ added in v0.17.0
AddSpanEvent adds an event to the current span with optional attributes. This is a no-op if tracing is not active.
func (*Context) BadRequest ¶
BadRequest responds with a 400 Bad Request error. Pass nil for a generic "Bad Request" message.
Example:
c.BadRequest(nil) // generic c.BadRequest(validationErr) // with validation details
func (*Context) Bind ¶
func (c *Context) Bind(out any, opts ...BindOption) error
Bind binds request data and validates it. Bind is the recommended method for handling request input.
Bind automatically:
- Detects Content-Type and binds from appropriate sources
- Binds path, query, header, and cookie parameters based on struct tags
- Validates the bound struct using the configured strategy
- Tracks field presence for partial validation support
Supported sources based on tags:
- path: "name" - URL path parameters
- query: "name" - Query string parameters
- header: "name" - HTTP headers
- cookie: "name" - Cookies
- json: "name" - JSON request body
- form: "name" - Form data (application/x-www-form-urlencoded or multipart/form-data)
For binding without validation, use Context.BindOnly. For separate binding and validation, use Context.BindOnly and Context.Validate.
Errors:
- binding.ErrOutMustBePointer: out is not a pointer to struct or map
- binding.ErrRequestBodyNil: request body is nil when JSON/form binding is needed
- binding.ErrUnsupportedContentType: Content-Type is not supported
- validation.Error: validation failed (one or more field errors)
Example:
var req CreateUserRequest
if err := c.Bind(&req); err != nil {
c.Fail(err)
return
}
With options:
if err := c.Bind(&req, app.WithStrict(), app.WithPartial()); err != nil {
c.Fail(err)
return
}
Note: For multipart forms with file uploads, files must be retrieved separately using c.File() or c.Files().
Example ¶
ExampleContext_Bind demonstrates basic request binding and validation.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
}
a.POST("/users", func(c *app.Context) {
var req CreateUserRequest
if err := c.Bind(&req); err != nil {
c.Fail(err)
return
}
if err := c.JSON(http.StatusCreated, map[string]string{
"message": "User created",
"name": req.Name,
}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
fmt.Println("Handler with binding and validation registered")
}
Output: Handler with binding and validation registered
Example (WithOptions) ¶
ExampleContext_Bind_withOptions demonstrates binding with options.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
}
a.POST("/users", func(c *app.Context) {
var req CreateUserRequest
if err := c.Bind(&req, app.WithStrict()); err != nil {
c.Fail(err)
return
}
if err := c.JSON(http.StatusCreated, map[string]string{
"message": "User created",
}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
fmt.Println("Handler with strict binding registered")
}
Output: Handler with strict binding registered
func (*Context) BindOnly ¶ added in v0.9.0
func (c *Context) BindOnly(out any, opts ...BindOption) error
BindOnly binds request data without validation. Use when you need fine-grained control over the bind/validate lifecycle.
Example:
var req Request
if err := c.BindOnly(&req); err != nil {
c.Fail(err)
return
}
req.Normalize() // Custom processing
if err := c.Validate(&req); err != nil {
c.Fail(err)
return
}
Example ¶
ExampleContext_BindOnly demonstrates binding without validation.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
a.POST("/users", func(c *app.Context) {
var req CreateUserRequest
if err := c.BindOnly(&req); err != nil {
c.Fail(err)
return
}
// Custom processing before validation
req.Email = normalizeEmail(req.Email)
// Validate separately
if err := c.Validate(&req); err != nil {
c.Fail(err)
return
}
if err := c.JSON(http.StatusCreated, map[string]string{
"message": "User created",
}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
fmt.Println("Handler with separate bind and validate registered")
}
func normalizeEmail(email string) string {
return email
}
Output: Handler with separate bind and validate registered
func (*Context) Conflict ¶ added in v0.11.0
Conflict responds with a 409 Conflict error. Pass nil for a generic "Conflict" message.
Example:
c.Conflict(nil) // generic
c.Conflict(fmt.Errorf("user already exists")) // custom message
func (*Context) CopyTraceContext ¶ added in v0.20.0
CopyTraceContext returns a new context that carries the current trace for use in goroutines or background work. Delegates to tracing.CopyTraceContext(c.RequestContext()).
func (*Context) Fail ¶ added in v0.11.0
Fail responds with a formatted error using the configured formatter. Fail automatically aborts the handler chain after writing the response.
Fail is the recommended way to return errors in handlers. The HTTP status is determined from the error (via ErrorType interface) or defaults to 500 Internal Server Error.
Fail selects the formatter based on:
- Content negotiation (Accept header) if multiple formatters are configured
- Default formatter if single formatter is configured
- RFC 9457 formatter as ultimate fallback
Example:
if err := c.Bind(&req); err != nil {
c.Fail(err)
return
}
if user == nil {
c.Fail(fmt.Errorf("user not found"))
return
}
See also Context.FailStatus for explicit status codes and convenience methods like Context.NotFound, Context.BadRequest, Context.Unauthorized.
To collect multiple errors and respond later (e.g. multi-field validation), use c.Context.CollectError(err), then c.Context.HasErrors() / c.Context.Errors() and send one response.
func (*Context) FailStatus ¶ added in v0.11.0
FailStatus responds with an error and explicit status code. FailStatus automatically aborts the handler chain.
FailStatus is useful when you want to override the error's default status.
Example:
c.FailStatus(http.StatusNotFound, err) c.FailStatus(http.StatusBadRequest, validationErr)
func (*Context) FinishSpan ¶ added in v0.19.0
FinishSpan ends a child span with status Ok. Use for spans that complete successfully and have no HTTP status. Delegates to tracing.FinishSpan. No-op if app/tracing nil.
Example ¶
ExampleContext_FinishSpan demonstrates ending a child span with success.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"rivaas.dev/app"
"rivaas.dev/tracing"
)
func main() {
a := app.MustNew(
app.WithObservability(app.WithTracing(tracing.WithNoop())),
)
a.GET("/ping", func(c *app.Context) {
_, span := c.StartSpan("check")
defer c.FinishSpan(span)
if err := c.JSON(http.StatusOK, map[string]string{"ok": "true"}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
req := httptest.NewRequest(http.MethodGet, "/ping", nil)
resp, err := a.Test(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d\n", resp.StatusCode)
//nolint:errcheck // example cleanup
resp.Body.Close()
}
Output: Status: 200
func (*Context) FinishSpanWithError ¶ added in v0.20.0
FinishSpanWithError marks the span as failed with the given error and ends it. Delegates to tracing.FinishSpanWithError. No-op if app/tracing nil.
Example ¶
ExampleContext_FinishSpanWithError demonstrates ending a span with an error.
package main
import (
"errors"
"fmt"
"log"
"net/http"
"net/http/httptest"
"rivaas.dev/app"
"rivaas.dev/tracing"
)
func main() {
a := app.MustNew(
app.WithObservability(app.WithTracing(tracing.WithNoop())),
)
a.GET("/fail", func(c *app.Context) {
_, span := c.StartSpan("op")
err := errors.New("something failed")
c.FinishSpanWithError(span, err)
if werr := c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}); werr != nil {
log.Printf("Failed to write response: %v", werr)
}
})
req := httptest.NewRequest(http.MethodGet, "/fail", nil)
resp, err := a.Test(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d\n", resp.StatusCode)
//nolint:errcheck // example cleanup
resp.Body.Close()
}
Output: Status: 500
func (*Context) FinishSpanWithHTTPStatus ¶ added in v0.20.0
FinishSpanWithHTTPStatus ends a child span and sets status from the HTTP status code. Delegates to tracing.FinishSpanWithHTTPStatus. No-op if app/tracing nil.
func (*Context) Forbidden ¶
Forbidden responds with a 403 Forbidden error. Pass nil for a generic "Forbidden" message.
Example:
c.Forbidden(nil) // generic
c.Forbidden(fmt.Errorf("insufficient permissions")) // custom message
func (*Context) Gone ¶ added in v0.11.0
Gone responds with a 410 Gone error. Pass nil for a generic "Gone" message.
Example:
c.Gone(nil) // generic
c.Gone(fmt.Errorf("resource permanently deleted")) // custom message
func (*Context) IncrementCounter ¶ added in v0.17.0
IncrementCounter increments a custom counter metric by one. This is a no-op when metrics are not configured.
func (*Context) InternalError ¶
InternalError responds with a 500 Internal Server Error. Pass nil for a generic "Internal Server Error" message.
Example:
c.InternalError(nil) // generic c.InternalError(err) // with error details (logged but sanitized in response)
func (*Context) MustBind ¶ added in v0.9.0
func (c *Context) MustBind(out any, opts ...BindOption) bool
MustBind binds and validates, writing an error response on failure. Returns true if successful, false if an error was written.
MustBind eliminates boilerplate error handling for the common case.
Example:
var req CreateUserRequest
if !c.MustBind(&req) {
return // Error already written
}
// Continue with validated request
Example ¶
ExampleContext_MustBind demonstrates the Must pattern for binding.
package main
import (
"fmt"
"log"
"net/http"
"rivaas.dev/app"
)
func main() {
a := app.MustNew()
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
}
a.POST("/users", func(c *app.Context) {
var req CreateUserRequest
if !c.MustBind(&req) {
return // Error already written
}
if err := c.JSON(http.StatusCreated, map[string]string{
"message": "User created",
"name": req.Name,
}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
fmt.Println("Handler with MustBind registered")
}
Output: Handler with MustBind registered
func (*Context) NotFound ¶
NotFound responds with a 404 Not Found error. Pass nil for a generic "Not Found" message.
Example:
c.NotFound(nil) // generic "Not Found"
c.NotFound(fmt.Errorf("user %s not found", id)) // custom message
c.NotFound(ErrUserNotFound) // domain error
func (*Context) Presence ¶
func (c *Context) Presence() validation.PresenceMap
Presence returns the presence map for the current request. Presence returns nil if no binding has occurred yet.
Example:
pm := c.Presence()
if pm != nil && pm.Has("email") {
// email field was present in request
}
func (*Context) RecordError ¶ added in v0.20.0
RecordError records an error on the current request span without ending it. Delegates to tracing.RecordErrorFromContext(c.RequestContext(), err). No-op if app/tracing nil.
func (*Context) RecordHistogram ¶ added in v0.17.0
RecordHistogram records a custom histogram metric. This is a no-op when metrics are not configured.
func (*Context) ResetBinding ¶
func (c *Context) ResetBinding()
ResetBinding resets the binding metadata for this context. ResetBinding is useful for testing or when you need to rebind a request.
func (*Context) ServiceUnavailable ¶ added in v0.11.0
ServiceUnavailable responds with a 503 Service Unavailable error. Pass nil for a generic "Service Unavailable" message.
Example:
c.ServiceUnavailable(nil) // generic
c.ServiceUnavailable(fmt.Errorf("maintenance mode")) // custom message
func (*Context) SetGauge ¶ added in v0.17.0
SetGauge sets a custom gauge metric. This is a no-op when metrics are not configured.
func (*Context) SetSpanAttribute ¶ added in v0.17.0
SetSpanAttribute adds an attribute to the current span. This is a no-op if tracing is not active.
func (*Context) Span ¶ added in v0.17.0
Span returns the OpenTelemetry span for this request, if tracing is enabled. Returns a non-recording span if tracing is not enabled.
func (*Context) SpanID ¶ added in v0.17.0
SpanID returns the current span ID from the active span. Returns an empty string if tracing is not active.
func (*Context) StartSpan ¶ added in v0.19.0
func (c *Context) StartSpan(name string, opts ...trace.SpanStartOption) (context.Context, trace.Span)
StartSpan starts a child span with the given name. It is the single, discoverable way to create child spans from handlers. Always end the span with FinishSpan or FinishSpanWithHTTPStatus (e.g. defer c.FinishSpan(span)). If tracing is nil or disabled, returns the request context and a non-recording span.
Example ¶
ExampleContext_StartSpan demonstrates creating a child span from a handler. Use StartSpan and defer FinishSpan for custom spans (e.g. db-query, external call).
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"go.opentelemetry.io/otel/attribute"
"rivaas.dev/app"
"rivaas.dev/tracing"
)
func main() {
a := app.MustNew(
app.WithObservability(app.WithTracing(tracing.WithNoop())),
)
a.GET("/users", func(c *app.Context) {
ctx, span := c.StartSpan("db-query")
defer c.FinishSpan(span)
span.SetAttributes(attribute.String("db.operation", "list"))
_ = ctx // use ctx for DB call so the span is the parent
if err := c.JSON(http.StatusOK, map[string]string{"users": "list"}); err != nil {
log.Printf("Failed to write response: %v", err)
}
})
req := httptest.NewRequest(http.MethodGet, "/users", nil)
resp, err := a.Test(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d\n", resp.StatusCode)
//nolint:errcheck // example cleanup
resp.Body.Close()
}
Output: Status: 200
func (*Context) TooManyRequests ¶ added in v0.11.0
TooManyRequests responds with a 429 Too Many Requests error. Pass nil for a generic "Too Many Requests" message.
Example:
c.TooManyRequests(nil) // generic
c.TooManyRequests(fmt.Errorf("rate limit exceeded")) // custom message
func (*Context) TraceContext ¶ added in v0.17.0
TraceContext returns the request context (which carries the active span when tracing is enabled). Use it for manual span creation or context propagation.
func (*Context) TraceID ¶ added in v0.17.0
TraceID returns the current trace ID from the active span. Returns an empty string if tracing is not active.
func (*Context) Tracer ¶ added in v0.19.0
Tracer returns the app's tracer, or nil if tracing is not configured. Use Tracer() only when you need to pass the tracer to another library (e.g. DB driver, HTTP client) or use tracer-specific options (inject/extract). For child spans in handlers, use StartSpan and FinishSpan instead.
func (*Context) Unauthorized ¶
Unauthorized responds with a 401 Unauthorized error. Pass nil for a generic "Unauthorized" message.
Example:
c.Unauthorized(nil) // generic
c.Unauthorized(fmt.Errorf("invalid token")) // custom message
func (*Context) UnprocessableEntity ¶ added in v0.11.0
UnprocessableEntity responds with a 422 Unprocessable Entity error. Pass nil for a generic "Unprocessable Entity" message.
Example:
c.UnprocessableEntity(nil) // generic c.UnprocessableEntity(validationErr) // validation details
func (*Context) Validate ¶ added in v0.9.0
func (c *Context) Validate(v any, opts ...ValidateOption) error
Validate validates a struct using the configured validation strategy. Use after BindOnly for fine-grained control. Options are app-scoped: use WithValidatePartial, WithValidateStrict, or WithValidateOptions.
Example:
if err := c.Validate(&req, app.WithValidatePartial()); err != nil {
c.Fail(err)
return
}
func (*Context) WithSpan ¶ added in v0.20.0
WithSpan runs fn under a new span with the given name. The span is finished with success if fn returns nil, or with error if fn returns non-nil. Returns the error from fn. No-op if app/tracing nil (runs fn and returns its error). Delegates to StartSpan and FinishSpan/FinishSpanWithError.
Example ¶
ExampleContext_WithSpan demonstrates running a function under a span.
package main
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httptest"
"rivaas.dev/app"
"rivaas.dev/tracing"
)
func main() {
a := app.MustNew(
app.WithObservability(app.WithTracing(tracing.WithNoop())),
)
a.GET("/user/:id", func(c *app.Context) {
id := c.Param("id")
err := c.WithSpan("fetch-user", func(ctx context.Context) error {
_ = ctx
_ = id
return nil
})
if err != nil {
if werr := c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()}); werr != nil {
log.Printf("Failed to write response: %v", werr)
}
return
}
if werr := c.JSON(http.StatusOK, map[string]string{"id": id}); werr != nil {
log.Printf("Failed to write response: %v", werr)
}
})
req := httptest.NewRequest(http.MethodGet, "/user/123", nil)
resp, err := a.Test(req)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d\n", resp.StatusCode)
//nolint:errcheck // example cleanup
resp.Body.Close()
}
Output: Status: 200
type DebugOption ¶ added in v0.2.0
type DebugOption func(*debugSettings)
DebugOption configures debug endpoint settings. These options configure pprof and other debug endpoints.
func WithDebugPrefix ¶ added in v0.2.0
func WithDebugPrefix(prefix string) DebugOption
WithDebugPrefix sets the mount prefix for debug endpoints. Default is "/debug", which mounts pprof at "/debug/pprof/*".
Example:
app.MustNew(
app.WithDebugEndpoints(
app.WithDebugPrefix("/_debug"),
app.WithPprof(),
),
)
// Endpoints: /_debug/pprof/*, etc.
func WithPprof ¶ added in v0.2.0
func WithPprof() DebugOption
WithPprof enables pprof endpoints for profiling and debugging.
Security rationale: pprof endpoints are disabled by default and require explicit opt-in because they expose sensitive runtime information that can be exploited:
Attack vectors:
- Goroutine dumps reveal internal logic and potential race conditions
- Heap dumps may contain secrets, tokens, or PII in memory
- CPU profiling can be used for timing attacks or DoS (profiling has overhead)
- Alloc profiles reveal memory usage patterns useful for resource exhaustion attacks
Safe usage patterns:
- Development: Enable unconditionally (no external exposure)
- Staging: Enable behind VPN or IP allowlist
- Production: Enable only with proper authentication middleware Example: app.Use(authMiddleware); app.WithDebugEndpoints(app.WithPprof())
Endpoints registered (when enabled):
- GET /debug/pprof/ - Main pprof index
- GET /debug/pprof/cmdline - Command line
- GET /debug/pprof/profile - CPU profile
- GET /debug/pprof/symbol - Symbol lookup
- POST /debug/pprof/symbol - Symbol lookup
- GET /debug/pprof/trace - Execution trace
- GET /debug/pprof/{profile} - Named profiles (allocs, block, goroutine, heap, mutex, threadcreate)
Example:
// Development: enable pprof
app.MustNew(
app.WithDebugEndpoints(
app.WithPprof(),
),
)
// Production: enable only if explicitly requested via environment
app.MustNew(
app.WithDebugEndpoints(
app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
),
)
func WithPprofIf ¶ added in v0.2.0
func WithPprofIf(condition bool) DebugOption
WithPprofIf conditionally enables pprof endpoints based on the given condition. This is useful for environment-based configuration.
Example:
app.MustNew(
app.WithDebugEndpoints(
app.WithPprofIf(os.Getenv("PPROF_ENABLED") == "true"),
),
)
type Gate ¶
type Gate interface {
// Ready returns true if the component is ready to serve traffic.
Ready() bool
// Name returns the name of the gate for identification.
Name() string
}
Gate represents a component that reports its readiness status. Used for runtime registration of readiness checks that need to be dynamically added or removed during application lifecycle.
For static readiness checks configured at startup, prefer using WithHealthEndpoints with WithReadinessCheck instead, as it provides better DX through the functional options pattern.
Use Gate when you need:
- Dynamic registration/unregistration at runtime
- Component-owned readiness state (e.g., database connection pool)
- Integration with external libraries that manage their own state
type Group ¶
type Group struct {
// contains filtered or unexported fields
}
Group represents a route group that allows organizing related routes under a common path prefix with shared middleware. It enables hierarchical organization of API endpoints and middleware application.
Groups created from App support app.HandlerFunc (with app.Context), providing access to binding and validation features.
Example:
api := app.Group("/api/v1", AuthMiddleware())
api.GET("/users", handler) // handler receives *app.Context
api.POST("/users", handler) // handler receives *app.Context
func (*Group) Any ¶
func (g *Group) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
Any registers a route that matches all HTTP methods. It is useful for catch-all endpoints like health checks or proxies.
It registers 7 separate routes internally (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). For endpoints that only need specific methods, use individual method registrations (GET, POST, etc.).
Returns the GET route (most common for docs/constraints).
Example:
api := app.Group("/api/v1")
api.Any("/health", healthCheckHandler)
func (*Group) DELETE ¶
func (g *Group) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
DELETE adds a DELETE route to the group with the group's prefix.
Example:
api := app.Group("/api/v1")
api.DELETE("/users/:id", deleteUser)
func (*Group) GET ¶
func (g *Group) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
GET adds a GET route to the group with the group's prefix.
Example:
api := app.Group("/api/v1")
api.GET("/users", handler)
api.GET("/users/:id", getUser,
app.WithDoc(openapi.WithSummary("Get user")),
)
func (*Group) Group ¶
func (g *Group) Group(prefix string, middleware ...HandlerFunc) *Group
Group creates a nested route group under the current group. It combines the parent's prefix with the provided prefix. It inherits middleware from the parent group.
Example:
api := app.Group("/api")
v1 := api.Group("/v1") // Creates /api/v1 prefix
v1.GET("/users", handler) // Matches /api/v1/users
func (*Group) HEAD ¶
func (g *Group) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
HEAD adds a HEAD route to the group with the group's prefix.
Example:
api := app.Group("/api/v1")
api.HEAD("/users/:id", handler)
func (*Group) OPTIONS ¶
func (g *Group) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
OPTIONS adds an OPTIONS route to the group with the group's prefix.
Example:
api := app.Group("/api/v1")
api.OPTIONS("/users", handler)
func (*Group) PATCH ¶
func (g *Group) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
PATCH adds a PATCH route to the group with the group's prefix.
Example:
api := app.Group("/api/v1")
api.PATCH("/users/:id", patchUser)
func (*Group) POST ¶
func (g *Group) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
POST adds a POST route to the group with the group's prefix.
Example:
api := app.Group("/api/v1")
api.POST("/users", createUser,
app.WithDoc(
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
),
)
func (*Group) PUT ¶
func (g *Group) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
PUT adds a PUT route to the group with the group's prefix.
Example:
api := app.Group("/api/v1")
api.PUT("/users/:id", updateUser)
func (*Group) Use ¶
func (g *Group) Use(middleware ...HandlerFunc)
Use adds middleware to the group that will be executed for all routes in this group. Use middleware is executed after the router's global middleware but before the route-specific handlers.
Example:
api := app.Group("/api")
api.Use(AuthMiddleware(), LoggingMiddleware())
api.GET("/users", getUsersHandler) // Will execute auth + logging + handler
type HandlerFunc ¶
type HandlerFunc func(*Context)
HandlerFunc defines a handler function that receives an Context. HandlerFunc provides access to both router functionality and app-level features like Context.Bind and Context.BindOnly (use Context.Validate after BindOnly when needed).
type HealthOption ¶ added in v0.2.0
type HealthOption func(*healthSettings)
HealthOption configures health endpoint settings. These options configure liveness (/livez) and readiness (/readyz) probes.
func WithHealthPrefix ¶ added in v0.2.0
func WithHealthPrefix(prefix string) HealthOption
WithHealthPrefix sets the mount prefix for health endpoints. By default, endpoints are mounted at root (e.g., /livez, /readyz). Use this to mount under a different prefix (e.g., /_system/livez).
Example:
app.MustNew(
app.WithHealthEndpoints(
app.WithHealthPrefix("/_system"),
),
)
// Endpoints: /_system/livez, /_system/readyz
func WithHealthTimeout ¶ added in v0.2.0
func WithHealthTimeout(d time.Duration) HealthOption
WithHealthTimeout sets the timeout for each health check. Each check runs with an independent context.WithTimeout to prevent one slow dependency from blocking the entire health check. Default is 1 second.
Example:
app.WithHealthEndpoints(
app.WithHealthTimeout(500 * time.Millisecond),
)
func WithLivenessCheck ¶ added in v0.2.0
func WithLivenessCheck(name string, check CheckFunc) HealthOption
WithLivenessCheck adds a liveness check. Liveness checks determine if the process is alive and should be dependency-free. If any liveness check fails, /livez returns 503.
Multiple calls accumulate checks. If no liveness checks are provided, /livez always returns 200 (process is running).
Example:
app.WithHealthEndpoints(
app.WithLivenessCheck("process", func(ctx context.Context) error {
// Dependency-free check: process is alive
return nil
}),
app.WithLivenessCheck("goroutines", func(ctx context.Context) error {
if runtime.NumGoroutine() > 10000 {
return errors.New("too many goroutines")
}
return nil
}),
)
func WithLivezPath ¶ added in v0.16.0
func WithLivezPath(path string) HealthOption
WithLivezPath sets the path for the liveness probe endpoint. Default is "/livez". The path is appended to the prefix (if set).
Example:
app.WithHealthEndpoints(
app.WithLivezPath("/live"),
)
// Endpoint: /live (or /{prefix}/live if prefix is set)
func WithReadinessCheck ¶ added in v0.2.0
func WithReadinessCheck(name string, check CheckFunc) HealthOption
WithReadinessCheck adds a readiness check. Readiness checks determine if the service is ready to accept traffic. These typically check external dependencies (database, cache, external APIs). If any readiness check fails, /readyz returns 503.
Multiple calls accumulate checks. If no readiness checks are provided, /readyz always returns 204 (service is ready).
Example:
app.WithHealthEndpoints(
app.WithReadinessCheck("database", func(ctx context.Context) error {
return db.PingContext(ctx)
}),
app.WithReadinessCheck("cache", func(ctx context.Context) error {
return redis.Ping(ctx).Err()
}),
app.WithReadinessCheck("upstream", func(ctx context.Context) error {
resp, err := http.Get("https://api.example.com/health")
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("upstream unhealthy")
}
return nil
}),
)
func WithReadyzPath ¶ added in v0.2.0
func WithReadyzPath(path string) HealthOption
WithReadyzPath sets the path for the readiness probe endpoint. Default is "/readyz". The path is appended to the prefix (if set).
Example:
app.WithHealthEndpoints(
app.WithReadyzPath("/ready"),
)
// Endpoint: /ready (or /{prefix}/ready if prefix is set)
type Hooks ¶
type Hooks struct {
// contains filtered or unexported fields
}
Hooks manages application lifecycle hooks. It stores callbacks for different lifecycle events.
type LoggingConfig ¶ added in v0.5.0
type LoggingConfig struct {
Handler LoggingHandler `config:"handler" json:"handler" yaml:"handler"`
Level LoggingLevel `config:"level" json:"level" yaml:"level"`
}
LoggingConfig configures structured logging. This struct can be loaded from configuration files (YAML, JSON, etc.).
Example YAML:
logging: handler: json level: info
type LoggingHandler ¶ added in v0.5.0
type LoggingHandler string
LoggingHandler defines log output formats.
const ( // LoggingConsole uses console handler (human-readable). LoggingConsole LoggingHandler = "console" // LoggingJSON uses JSON handler (machine-readable). LoggingJSON LoggingHandler = "json" )
type LoggingLevel ¶ added in v0.5.0
type LoggingLevel string
LoggingLevel defines log levels.
const ( // LoggingDebug enables debug-level logging. LoggingDebug LoggingLevel = "debug" // LoggingInfo enables info-level logging. LoggingInfo LoggingLevel = "info" // LoggingWarn enables warn-level logging. LoggingWarn LoggingLevel = "warn" // LoggingError enables error-level logging. LoggingError LoggingLevel = "error" )
type MTLSOption ¶
type MTLSOption func(*mtlsConfig)
MTLSOption configures mtlsConfig.
func WithAuthorize ¶
func WithAuthorize(fn func(*x509.Certificate) (principal string, allowed bool)) MTLSOption
WithAuthorize sets a callback that maps client certificates to principal identity and authorization. The callback returns the principal (e.g., CN, SAN SPIFFE ID) and whether access is allowed. If not set, any valid client certificate is accepted.
Example:
WithAuthorize(func(cert *x509.Certificate) (string, bool) {
// Extract SPIFFE ID from SAN
for _, uri := range cert.URIs {
if uri.Scheme == "spiffe" {
return uri.String(), true
}
}
// Fallback to CN
return cert.Subject.CommonName, cert.Subject.CommonName != ""
})
func WithClientCAs ¶
func WithClientCAs(pool *x509.CertPool) MTLSOption
WithClientCAs sets the certificate authority pool for validating client certificates. It is required for mTLS - without it, client certificate validation will fail.
func WithConfigForClient ¶
func WithConfigForClient(fn func(*tls.ClientHelloInfo) (*tls.Config, error)) MTLSOption
WithConfigForClient sets a callback for per-client TLS configuration. Useful for hot-reloading certificates or dynamic configuration. If not set, default configuration is used.
func WithMinVersion ¶
func WithMinVersion(version uint16) MTLSOption
WithMinVersion sets the minimum TLS version to accept. Defaults to TLS 1.3 if not specified. Use TLS 1.2 or lower only if compatibility is required.
func WithSNI ¶
func WithSNI(fn func(*tls.ClientHelloInfo) (*tls.Certificate, error)) MTLSOption
WithSNI sets a callback for SNI (Server Name Indication) support. Allows serving different certificates based on the requested hostname. If not set, ServerCert is used for all connections.
type MetricsConfig ¶ added in v0.5.0
type MetricsConfig struct {
Provider MetricsProvider `config:"provider" json:"provider" yaml:"provider"`
Endpoint string `config:"endpoint" json:"endpoint" yaml:"endpoint"`
Path string `config:"path" json:"path" yaml:"path"`
}
MetricsConfig configures metrics collection. This struct can be loaded from configuration files (YAML, JSON, etc.).
Example YAML:
metrics: provider: prometheus endpoint: ":9090" path: /metrics
type MetricsProvider ¶ added in v0.5.0
type MetricsProvider string
MetricsProvider defines available metrics backends.
const ( // MetricsPrometheus uses Prometheus exporter for metrics. MetricsPrometheus MetricsProvider = "prometheus" // MetricsOTLP uses OTLP HTTP exporter for metrics. MetricsOTLP MetricsProvider = "otlp" // MetricsStdout uses stdout exporter for metrics (development/testing). MetricsStdout MetricsProvider = "stdout" )
type ObservabilityConfig ¶ added in v0.5.0
type ObservabilityConfig struct {
Tracing TracingConfig `config:"tracing" json:"tracing" yaml:"tracing"`
Metrics MetricsConfig `config:"metrics" json:"metrics" yaml:"metrics"`
Logging LoggingConfig `config:"logging" json:"logging" yaml:"logging"`
ExcludePaths []string `config:"excludePaths" json:"excludePaths" yaml:"excludePaths"`
ExcludePrefixes []string `config:"excludePrefixes" json:"excludePrefixes" yaml:"excludePrefixes"`
}
ObservabilityConfig is the unified observability configuration. Embed this in your app config struct for seamless config loading.
Example YAML:
observability:
tracing:
provider: otlp
endpoint: localhost:4317
metrics:
provider: prometheus
logging:
handler: json
level: info
excludePaths:
- /livez
- /readyz
Example usage:
type AppConfig struct {
Server ServerConfig `config:"server"`
Observability app.ObservabilityConfig `config:"observability"`
}
app.New(
app.WithServiceName("my-api"),
app.WithObservabilityConfig(cfg.Observability),
)
type ObservabilityOption ¶ added in v0.2.0
type ObservabilityOption func(*observabilitySettings)
ObservabilityOption configures unified observability settings. These options configure metrics, tracing, logging, and shared settings like path exclusions.
func WithAccessLogScope ¶ added in v0.18.0
func WithAccessLogScope(scope AccessLogScope) ObservabilityOption
WithAccessLogScope sets which requests are logged. When not set, production defaults to errors-only and development to all requests. Invalid scope values cause validation to fail at startup.
Example:
app.WithObservability(
app.WithAccessLogScope(app.AccessLogScopeErrorsOnly),
)
func WithAccessLogging ¶ added in v0.2.0
func WithAccessLogging(enabled bool) ObservabilityOption
WithAccessLogging enables or disables access logging. Default is true.
Example:
app.WithObservability(
app.WithAccessLogging(false), // Disable access logs
)
func WithExcludePaths ¶ added in v0.2.0
func WithExcludePaths(paths ...string) ObservabilityOption
WithExcludePaths adds exact paths to exclude from ALL observability (metrics, tracing, access logging). Multiple calls accumulate paths. Default exclusions are preserved unless WithoutDefaultExclusions() is called first.
Example:
app.WithObservability(
app.WithExcludePaths("/custom-health", "/k8s-probe"),
)
func WithExcludePatterns ¶ added in v0.2.0
func WithExcludePatterns(patterns ...string) ObservabilityOption
WithExcludePatterns adds regex patterns to exclude from ALL observability. Paths matching any pattern will be excluded.
Example:
app.WithObservability(
app.WithExcludePatterns(`^/v[0-9]+/internal/.*`, `^/debug/.*`),
)
func WithExcludePrefixes ¶ added in v0.2.0
func WithExcludePrefixes(prefixes ...string) ObservabilityOption
WithExcludePrefixes adds path prefixes to exclude from ALL observability. Paths starting with any of these prefixes will be excluded.
Example:
app.WithObservability(
app.WithExcludePrefixes("/internal/", "/admin/", "/debug/"),
)
func WithLogging ¶ added in v0.2.0
func WithLogging(opts ...logging.Option) ObservabilityOption
WithLogging enables structured logging with the given options. Service name and version are automatically injected from app-level configuration.
If not provided, a no-op logger is used (logs are discarded).
The app automatically derives request-scoped loggers that include:
- HTTP metadata (method, route, target path, client IP)
- Request ID (if X-Request-ID header is present)
- Trace/span IDs (if OpenTelemetry tracing is enabled)
Example:
app.MustNew(
app.WithServiceName("orders-api"),
app.WithServiceVersion("v1.0.0"),
app.WithObservability(
app.WithLogging(
logging.WithJSONHandler(),
logging.WithDebugLevel(),
// Service name/version auto-injected from app config
),
),
)
func WithMetrics ¶
func WithMetrics(opts ...metrics.Option) ObservabilityOption
WithMetrics enables metrics collection with the given options. Service name and version are automatically injected from app-level configuration.
Example:
app.MustNew(
app.WithServiceName("orders-api"),
app.WithObservability(
app.WithMetrics(), // Prometheus is default
),
)
func WithMetricsOnMainRouter ¶ added in v0.2.0
func WithMetricsOnMainRouter(path string) ObservabilityOption
WithMetricsOnMainRouter mounts the metrics endpoint on the main application router instead of running a separate metrics server.
By default, the metrics package runs a separate server on :9090. Use this option when you need metrics on the same port as the application, such as in Kubernetes environments with strict ingress rules.
The separate metrics server is automatically disabled when this option is used.
Example:
app.MustNew(
app.WithServiceName("orders-api"),
app.WithObservability(
app.WithMetrics(), // Prometheus is default
app.WithMetricsOnMainRouter("/metrics"),
),
)
func WithMetricsSeparateServer ¶ added in v0.2.0
func WithMetricsSeparateServer(addr, path string) ObservabilityOption
WithMetricsSeparateServer configures the separate metrics server with custom address and path.
By default, metrics run on a separate server at :9090/metrics. Use this option to customize the port or endpoint path.
This is mutually exclusive with WithMetricsOnMainRouter.
Example:
app.MustNew(
app.WithServiceName("orders-api"),
app.WithObservability(
app.WithMetrics(), // Prometheus is default
app.WithMetricsSeparateServer(":9091", "/custom-metrics"),
),
)
func WithSlowThreshold ¶ added in v0.2.0
func WithSlowThreshold(d time.Duration) ObservabilityOption
WithSlowThreshold sets the duration threshold for marking requests as "slow". Slow requests are always logged, even when using AccessLogScopeErrorsOnly. Default is 1 second.
Example:
app.WithObservability(
app.WithSlowThreshold(500 * time.Millisecond),
)
func WithTracing ¶
func WithTracing(opts ...tracing.Option) ObservabilityOption
WithTracing enables distributed tracing with the given options. Service name and version are automatically injected from app-level configuration.
Example:
app.MustNew(
app.WithServiceName("orders-api"),
app.WithObservability(
app.WithTracing(tracing.WithOTLP("localhost:4317")),
),
)
func WithoutDefaultExclusions ¶ added in v0.2.0
func WithoutDefaultExclusions() ObservabilityOption
WithoutDefaultExclusions clears the default path exclusions. By default, common health/probe paths are excluded (/health, /livez, /ready, etc.). Use this option to start with an empty exclusion list, then add your own paths.
Example:
app.WithObservability(
app.WithoutDefaultExclusions(),
app.WithExcludePaths("/only-this", "/and-that"),
)
type Option ¶
type Option func(*config)
Option defines functional options for app configuration. Option functions are used to configure an App instance during creation.
func WithDebugEndpoints ¶ added in v0.2.0
func WithDebugEndpoints(opts ...DebugOption) Option
WithDebugEndpoints enables and configures debug endpoints. By default, no debug features are enabled - you must explicitly opt-in to specific features like pprof for security reasons.
Example:
// Development: full debug access
app.MustNew(
app.WithServiceName("orders-api"),
app.WithDebugEndpoints(
app.WithPprof(),
),
)
// Production: conditional debug access
app.MustNew(
app.WithServiceName("orders-api"),
app.WithDebugEndpoints(
app.WithDebugPrefix("/_internal/debug"),
app.WithPprofIf(os.Getenv("ENABLE_DEBUG") == "true"),
),
)
func WithDefaultErrorFormat ¶
WithDefaultErrorFormat sets the default format when no Accept header matches. Only used when content-negotiated formatters are configured (via WithErrorFormatterFor with non-empty media types or WithErrorFormatters).
Example:
app.New(
app.WithErrorFormatterFor("application/problem+json", errors.WithRFC9457("...")),
app.WithErrorFormatterFor("application/json", errors.WithSimple()),
app.WithDefaultErrorFormat("application/problem+json"),
)
func WithEnv ¶ added in v0.6.0
func WithEnv() Option
WithEnv enables environment variable overrides for framework configuration. Environment variables use the RIVAAS_ prefix and take precedence over programmatic configuration.
Supported variables:
Core: RIVAAS_ENV - Environment mode: "development" or "production" RIVAAS_SERVICE_NAME - Service name for observability RIVAAS_SERVICE_VERSION - Service version Server: RIVAAS_PORT - HTTP server port (e.g., "8080") RIVAAS_HOST - HTTP server host/interface (e.g., "127.0.0.1") RIVAAS_READ_TIMEOUT - Request read timeout (e.g., "10s") RIVAAS_WRITE_TIMEOUT - Response write timeout (e.g., "10s") RIVAAS_SHUTDOWN_TIMEOUT - Graceful shutdown timeout (e.g., "30s") Logging: RIVAAS_LOG_LEVEL - Log level: "debug", "info", "warn", "error" RIVAAS_LOG_FORMAT - Log format: "json", "text", or "console" Observability: RIVAAS_METRICS_EXPORTER - Metrics exporter: "prometheus", "otlp", or "stdout" RIVAAS_METRICS_ADDR - Prometheus address (default: ":9090") RIVAAS_METRICS_PATH - Prometheus path (default: "/metrics") RIVAAS_METRICS_ENDPOINT - OTLP metrics endpoint (e.g., "http://localhost:4318") RIVAAS_TRACING_EXPORTER - Tracing exporter: "otlp", "otlp-http", or "stdout" RIVAAS_TRACING_ENDPOINT - OTLP tracing endpoint (e.g., "localhost:4317") Debug: RIVAAS_PPROF_ENABLED - Enable pprof: "true" or "false"
Example:
export RIVAAS_ENV=production
export RIVAAS_PORT=3000
export RIVAAS_LOG_LEVEL=warn
app := app.MustNew(
app.WithServiceName("orders-api"),
app.WithEnv(), // Applies environment overrides
)
func WithEnvPrefix ¶ added in v0.6.0
WithEnvPrefix enables environment variable overrides with a custom prefix. Use this when deploying multiple Rivaas services that need different configurations.
The prefix is prepended to the standard variable names. For example, with prefix "ORDERS_":
- ORDERS_ENV instead of RIVAAS_ENV
- ORDERS_PORT instead of RIVAAS_PORT
Example:
// Service 1: uses ORDERS_ENV, ORDERS_PORT, etc.
app.MustNew(
app.WithServiceName("orders-api"),
app.WithEnvPrefix("ORDERS_"),
)
// Service 2: uses PAYMENTS_ENV, PAYMENTS_PORT, etc.
app.MustNew(
app.WithServiceName("payments-api"),
app.WithEnvPrefix("PAYMENTS_"),
)
func WithEnvironment ¶
WithEnvironment sets the environment mode. Valid values are "development" or "production". Invalid values cause validation to fail during New.
Environment affects:
- Access log scope (when unset via WithAccessLogScope, production defaults to errors-only, development to all)
- Startup banner (development shows route table)
- Terminal colors (production strips ANSI sequences)
Example:
app.New(app.WithEnvironment("production"))
func WithErrorFormatterFor ¶ added in v0.20.0
WithErrorFormatterFor configures an error formatter from options. The app builds the formatter via errors.New(opts...); invalid options are reported during config validation.
Use empty mediaType ("") for a single formatter for all responses (no content negotiation). Use a non-empty mediaType (e.g. "application/problem+json") to register a formatter for content negotiation; multiple calls accumulate. Cannot mix: use either a single formatter ("") or content-negotiated formatters, not both.
Example:
// Single formatter for all responses
app.New(
app.WithServiceName("my-service"),
app.WithErrorFormatterFor("", errors.WithRFC9457("https://api.example.com/problems")),
)
// Content negotiation by Accept header
app.New(
app.WithServiceName("my-service"),
app.WithErrorFormatterFor("application/problem+json", errors.WithRFC9457("https://api.example.com/problems")),
app.WithErrorFormatterFor("application/json", errors.WithSimple()),
app.WithDefaultErrorFormat("application/problem+json"),
)
func WithErrorFormatters ¶
WithErrorFormatters configures multiple error formatters with content negotiation by Accept header. Advanced: use when you need to pass pre-built or custom formatters. Prefer WithErrorFormatterFor for option-based configuration.
Example:
app.New(
app.WithServiceName("my-service"),
app.WithErrorFormatters(map[string]errors.Formatter{
"application/problem+json": errors.MustNew(errors.WithRFC9457("https://api.example.com/problems")),
"application/json": errors.MustNew(errors.WithSimple()),
}),
app.WithDefaultErrorFormat("application/problem+json"),
)
func WithHealthEndpoints ¶ added in v0.2.0
func WithHealthEndpoints(opts ...HealthOption) Option
WithHealthEndpoints enables and configures health check endpoints. This registers /livez (liveness) and /readyz (readiness) endpoints.
Endpoints registered:
- GET /livez (or /{prefix}/livez) - Liveness probe Returns 200 "ok" if all liveness checks pass, 503 if any fail
- GET /readyz (or /{prefix}/readyz) - Readiness probe Returns 204 if all readiness checks pass, 503 if any fail
Example:
app.MustNew(
app.WithServiceName("orders-api"),
app.WithHealthEndpoints(
app.WithHealthPrefix("/_system"),
app.WithHealthTimeout(800 * time.Millisecond),
app.WithLivenessCheck("process", func(ctx context.Context) error {
return nil // Process is alive
}),
app.WithReadinessCheck("database", func(ctx context.Context) error {
return db.PingContext(ctx)
}),
app.WithReadinessCheck("cache", func(ctx context.Context) error {
return redis.Ping(ctx).Err()
}),
),
)
func WithHost ¶ added in v0.6.0
WithHost sets the host/interface to bind the HTTP server to. Default is "" (all interfaces, equivalent to "0.0.0.0"). Use "127.0.0.1" or "localhost" to restrict to local connections only.
Example:
// Bind to all interfaces (default)
app.New(app.WithPort(8080))
// Bind to localhost only (e.g., behind reverse proxy)
app.New(
app.WithHost("127.0.0.1"),
app.WithPort(8080),
)
func WithMTLS ¶ added in v0.18.0
func WithMTLS(serverCert tls.Certificate, opts ...MTLSOption) Option
WithMTLS configures the server to serve HTTPS with mutual TLS (mTLS) using the given server certificate and options. Only one of WithTLS or WithMTLS may be used. Default listen port is 8443 unless overridden by WithPort or RIVAAS_PORT when WithEnv is used.
Example:
serverCert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
app.New(
app.WithServiceName("my-api"),
app.WithMTLS(serverCert,
app.WithClientCAs(caCertPool),
app.WithMinVersion(tls.VersionTLS13),
), // default port 8443; use WithPort(443) to override
)
// ...
app.Start(ctx)
func WithMiddleware ¶
func WithMiddleware(middlewares ...HandlerFunc) Option
WithMiddleware adds middleware during app initialization. Middleware provided here will be added before any middleware added via Use(). Multiple calls to WithMiddleware are supported and will accumulate.
Note: This does not affect default middleware (recovery). Use WithoutDefaultMiddleware() to disable default middleware.
Example:
app.New(
app.WithServiceName("my-service"),
app.WithMiddleware(
middleware.Logger(),
middleware.Recovery(),
),
)
func WithObservability ¶ added in v0.2.0
func WithObservability(opts ...ObservabilityOption) Option
WithObservability configures all observability components: metrics, tracing, and logging. This is the single entry point for configuring the three pillars of observability.
Components:
- WithLogging: enables structured logging (service name/version auto-injected)
- WithMetrics: enables metrics collection (Prometheus, OTLP)
- WithTracing: enables distributed tracing (OTLP, Jaeger)
Shared settings (apply to all components):
- WithExcludePaths, WithExcludePrefixes, WithExcludePatterns, WithoutDefaultExclusions
- WithAccessLogging, WithAccessLogScope, WithSlowThreshold
Default exclusions include common health/probe paths: /health, /livez, /ready, /readyz, /live, /metrics, /debug/*
Example:
app.MustNew(
app.WithServiceName("orders-api"),
app.WithServiceVersion("v1.0.0"),
app.WithObservability(
app.WithLogging(logging.WithJSONHandler(), logging.WithDebugLevel()),
app.WithMetrics(), // Prometheus is default; use metrics.WithOTLP() for OTLP
app.WithTracing(tracing.WithOTLP("localhost:4317")),
app.WithExcludePaths("/custom-health"),
app.WithExcludePrefixes("/internal/", "/admin/"),
app.WithAccessLogScope(app.AccessLogScopeErrorsOnly),
app.WithSlowThreshold(500 * time.Millisecond),
),
)
func WithObservabilityFromConfig ¶ added in v0.5.0
func WithObservabilityFromConfig(cfg ObservabilityConfig) Option
WithObservabilityFromConfig configures all observability from a single config struct. This is a convenience method that converts declarative configuration into functional options and applies them via the existing WithObservability function.
This function is ideal for loading observability configuration from files (YAML, JSON, etc.). Invalid tracing, metrics, or logging configuration is reported when the app is constructed (e.g. from New) as a validation error, not via panic.
Example:
app.New(
app.WithServiceName("blog-api"),
app.WithObservabilityFromConfig(cfg.Observability),
)
func WithOpenAPI ¶
WithOpenAPI enables OpenAPI specification generation with the given options. Service name and version are automatically injected from app-level configuration after all options are applied (in config validation), so option order does not matter. If not explicitly set via openapi.WithTitle(), the app's service name and version are used.
Example:
app.New(
app.WithServiceName("my-service"),
app.WithServiceVersion("v1.0.0"),
app.WithOpenAPI(
openapi.WithTitle("My API", "1.0.0"),
openapi.WithDescription("API description"),
openapi.WithBearerAuth("bearerAuth", "JWT authentication"),
openapi.WithServer("http://localhost:8080", "Local development"),
openapi.WithSwaggerUI(true, "/docs"),
openapi.WithUIDocExpansion(openapi.DocExpansionList),
openapi.WithUISyntaxTheme(openapi.SyntaxThemeMonokai),
),
)
func WithPort ¶ added in v0.6.0
WithPort sets the server listen port. Default is 8080 for HTTP; when using WithTLS or WithMTLS the default is 8443. Override with WithPort(n) in all cases. Can be overridden by RIVAAS_PORT when WithEnv is used.
Example:
app.New(app.WithPort(3000))
func WithRouter ¶ added in v0.5.0
WithRouter passes router options through to the underlying router.
Example:
app := app.New(
app.WithServiceName("my-service"),
app.WithRouter(
router.WithBloomFilterSize(2000),
router.WithoutCancellationCheck(),
router.WithTemplateRouting(true),
router.WithVersioning(),
),
)
Multiple calls to WithRouter accumulate options.
func WithServer ¶ added in v0.5.0
func WithServer(opts ...ServerOption) Option
WithServer configures server settings using functional options.
Example:
app.New(
app.WithServer(
app.WithReadTimeout(15 * time.Second),
app.WithWriteTimeout(15 * time.Second),
app.WithShutdownTimeout(30 * time.Second),
),
)
func WithServiceName ¶
WithServiceName sets the service name used in observability metadata. An empty name causes validation to fail during New.
Example:
app.New(app.WithServiceName("my-api"))
func WithServiceVersion ¶
WithServiceVersion sets the service version used in observability metadata. An empty version causes validation to fail during New.
Example:
app.New(app.WithServiceVersion("v1.0.0"))
func WithTLS ¶ added in v0.18.0
WithTLS configures the server to serve HTTPS using the given certificate and key files. Only one of WithTLS or WithMTLS may be used. Both certFile and keyFile must be non-empty. Default listen port is 8443 unless overridden by WithPort or RIVAAS_PORT when WithEnv is used.
Example:
app.New(
app.WithServiceName("my-api"),
app.WithTLS("server.crt", "server.key"), // default port 8443; use WithPort(443) to override
)
// ...
app.Start(ctx)
func WithValidationEngine ¶ added in v0.20.0
func WithValidationEngine(engine *validation.Engine) Option
WithValidationEngine sets the validation engine used by Context.Bind and Context.Validate. When set, the app uses this engine instead of the package-level validation.DefaultEngine. Use this for custom validation configuration (e.g. redaction, MaxErrors) or test isolation.
Example:
engine := validation.MustNew(validation.WithRedactor(myRedactor))
app := app.MustNew(
app.WithServiceName("my-api"),
app.WithValidationEngine(engine),
)
func WithoutDefaultMiddleware ¶ added in v0.2.0
func WithoutDefaultMiddleware() Option
WithoutDefaultMiddleware disables the default middleware (recovery). Use this when you want full control over middleware and don't want the framework to automatically add recovery middleware.
Example:
app.New(
app.WithServiceName("my-service"),
app.WithoutDefaultMiddleware(),
app.WithMiddleware(myCustomRecovery), // Add your own
)
type ReadinessManager ¶
type ReadinessManager struct {
// contains filtered or unexported fields
}
ReadinessManager manages readiness gates for runtime health checks. ReadinessManager is safe for concurrent use by multiple goroutines.
This complements the static WithReadinessCheck options by allowing dynamic registration and unregistration of readiness gates at runtime.
Typical use cases:
- Database connection pools that manage their own health
- External service clients with retry/circuit breaker logic
- Components that need to temporarily mark themselves as not ready
func (*ReadinessManager) Check ¶
func (rm *ReadinessManager) Check() (bool, map[string]bool)
Check checks if all registered gates are ready. Check returns true if all gates are ready, false otherwise. Check also returns a map of gate names to their readiness status.
Example:
ready, status := app.Readiness().Check()
if !ready {
for name, isReady := range status {
if !isReady {
log.Printf("Gate %s is not ready", name)
}
}
}
func (*ReadinessManager) Register ¶
func (rm *ReadinessManager) Register(name string, gate Gate)
Register registers a readiness gate at runtime. If a gate with the same name already exists, it is replaced.
Example:
type DatabaseGate struct {
db *sql.DB
}
func (g *DatabaseGate) Ready() bool {
return g.db.Ping() == nil
}
func (g *DatabaseGate) Name() string { return "database" }
app.Readiness().Register("db", &DatabaseGate{db: db})
func (*ReadinessManager) Unregister ¶
func (rm *ReadinessManager) Unregister(name string)
Unregister removes a readiness gate by name. This is useful when a component is being shut down or is no longer relevant to the application's readiness.
Example:
// During graceful shutdown of a specific component
app.Readiness().Unregister("database")
type RouteOption ¶ added in v0.4.0
type RouteOption func(*routeConfig)
RouteOption configures a route. Options can configure middleware (before/after), documentation, or combine multiple options. This follows the functional options pattern used throughout the framework.
func RouteOptions ¶ added in v0.4.0
func RouteOptions(opts ...RouteOption) RouteOption
RouteOptions combines multiple options into a single option. This is useful for creating reusable option sets.
Example:
var Authenticated = app.RouteOptions(
app.WithBefore(authMiddleware),
app.WithDoc(
openapi.Security("bearerAuth"),
openapi.Response(401, UnauthorizedError{}),
),
)
app.GET("/users/:id", getUser,
Authenticated,
app.WithDoc(
openapi.Summary("Get user"),
openapi.Response(200, UserResponse{}),
),
)
func WithAfter ¶ added in v0.4.0
func WithAfter(handlers ...HandlerFunc) RouteOption
WithAfter adds post-handler middleware to the route. Middleware added with WithAfter executes after the main handler.
Example:
app.GET("/users/:id", getUser,
app.WithAfter(auditLogMiddleware, metricsMiddleware),
)
func WithBefore ¶ added in v0.4.0
func WithBefore(handlers ...HandlerFunc) RouteOption
WithBefore adds pre-handler middleware to the route. Middleware added with WithBefore executes before the main handler.
Example:
app.GET("/users/:id", getUser,
app.WithBefore(authMiddleware, rateLimitMiddleware),
)
func WithDoc ¶ added in v0.4.0
func WithDoc(opts ...openapi.OperationOption) RouteOption
WithDoc adds OpenAPI documentation to the route. Documentation options are provided by the openapi package.
Example:
app.GET("/users/:id", getUser,
app.WithDoc(
openapi.Summary("Get user"),
openapi.Description("Retrieves a user by ID"),
openapi.Response(200, UserResponse{}),
openapi.Response(404, ErrorResponse{}),
openapi.Tags("users"),
),
)
func WithoutDoc ¶ added in v0.4.0
func WithoutDoc() RouteOption
WithoutDoc explicitly disables documentation for this route. This is useful when global documentation is enabled but specific routes should be excluded.
Example:
app.GET("/health", healthCheck,
app.WithoutDoc(),
)
type ServerOption ¶
type ServerOption func(*serverConfig)
ServerOption configures server settings.
func WithIdleTimeout ¶
func WithIdleTimeout(d time.Duration) ServerOption
WithIdleTimeout sets the server idle timeout. WithIdleTimeout configures how long the server waits for the next request on a keep-alive connection.
Example:
app.New(
app.WithServer(
app.WithIdleTimeout(60 * time.Second),
),
)
func WithMaxHeaderBytes ¶
func WithMaxHeaderBytes(n int) ServerOption
WithMaxHeaderBytes sets the maximum size of request headers. WithMaxHeaderBytes configures the maximum number of bytes allowed in request headers.
Example:
app.New(
app.WithServer(
app.WithMaxHeaderBytes(1 << 20), // 1MB
),
)
func WithReadHeaderTimeout ¶
func WithReadHeaderTimeout(d time.Duration) ServerOption
WithReadHeaderTimeout sets the server read header timeout. WithReadHeaderTimeout configures how long the server waits to read request headers.
Example:
app.New(
app.WithServer(
app.WithReadHeaderTimeout(2 * time.Second),
),
)
func WithReadTimeout ¶
func WithReadTimeout(d time.Duration) ServerOption
WithReadTimeout sets the server read timeout. WithReadTimeout configures how long the server waits to read the entire request.
Example:
app.New(
app.WithServer(
app.WithReadTimeout(10 * time.Second),
),
)
func WithShutdownTimeout ¶
func WithShutdownTimeout(d time.Duration) ServerOption
WithShutdownTimeout sets the graceful shutdown timeout. WithShutdownTimeout configures how long the server waits for graceful shutdown to complete.
Example:
app.New(
app.WithServer(
app.WithShutdownTimeout(30 * time.Second),
),
)
func WithWriteTimeout ¶
func WithWriteTimeout(d time.Duration) ServerOption
WithWriteTimeout sets the server write timeout. WithWriteTimeout configures how long the server waits to write the response.
Example:
app.New(
app.WithServer(
app.WithWriteTimeout(10 * time.Second),
),
)
type TestOption ¶
type TestOption func(*testConfig)
TestOption configures test execution behavior.
func WithContext ¶
func WithContext(ctx context.Context) TestOption
WithContext uses the provided context for the test request. Useful for testing context propagation and cancellation.
func WithTimeout ¶
func WithTimeout(d time.Duration) TestOption
WithTimeout sets the test request timeout. Use -1 for no timeout.
Example:
resp, err := app.Test(req, WithTimeout(5*time.Second))
type TracingConfig ¶ added in v0.5.0
type TracingConfig struct {
Provider TracingProvider `config:"provider" json:"provider" yaml:"provider"`
Endpoint string `config:"endpoint" json:"endpoint" yaml:"endpoint"`
SampleRate float64 `config:"sampleRate" json:"sampleRate" yaml:"sampleRate"`
Insecure bool `config:"insecure" json:"insecure" yaml:"insecure"`
}
TracingConfig configures distributed tracing. This struct can be loaded from configuration files (YAML, JSON, etc.).
Example YAML:
tracing: provider: otlp endpoint: localhost:4317 sampleRate: 0.1 insecure: true
type TracingProvider ¶ added in v0.5.0
type TracingProvider string
TracingProvider defines available tracing backends.
const ( // TracingStdout exports traces to stdout (development/testing). TracingStdout TracingProvider = "stdout" // TracingOTLP exports traces via OTLP gRPC protocol. TracingOTLP TracingProvider = "otlp" // TracingOTLPHTTP exports traces via OTLP HTTP protocol. TracingOTLPHTTP TracingProvider = "otlp-http" // TracingNoop is a no-op provider (no traces exported). TracingNoop TracingProvider = "noop" )
type ValidateOption ¶ added in v0.20.0
type ValidateOption func(*validateConfig)
ValidateOption configures Context.Validate behavior. ValidateOptions can be passed to Context.Validate.
func WithValidateOptions ¶ added in v0.20.0
func WithValidateOptions(opts ...validation.Option) ValidateOption
WithValidateOptions passes options directly to the validation package for this validate call. Use for advanced validation configuration when using Context.Validate.
Example:
if err := c.Validate(&req,
app.WithValidatePartial(),
app.WithValidateOptions(validation.WithMaxErrors(5)),
); err != nil {
c.Fail(err)
return
}
func WithValidatePartial ¶ added in v0.20.0
func WithValidatePartial() ValidateOption
WithValidatePartial enables partial validation for this validate call. Only fields present in the request (or in the presence map) are validated; "required" is ignored for absent fields. Use after Context.BindOnly for PATCH-style flows.
Example:
if err := c.Validate(&req, app.WithValidatePartial()); err != nil {
c.Fail(err)
return
}
func WithValidateStrict ¶ added in v0.20.0
func WithValidateStrict() ValidateOption
WithValidateStrict disallows unknown fields for this validate call. Use when validating JSON-backed structs and you want to reject unknown keys.
Example:
if err := c.Validate(&req, app.WithValidateStrict()); err != nil {
c.Fail(err)
return
}
type VersionGroup ¶
type VersionGroup struct {
// contains filtered or unexported fields
}
VersionGroup represents a version-specific route group that allows organizing related routes under a specific API version. VersionGroup supports app.HandlerFunc (with app.Context), providing access to binding, validation, and logging features.
Routes registered in a VersionGroup are automatically scoped to that version. The version is detected from the request path, headers, query parameters, or other configured versioning strategies.
Example:
v1 := app.Version("v1")
v1.GET("/status", handlers.Status) // handler receives *app.Context
v1.POST("/users", handlers.CreateUser) // handler receives *app.Context
func (*VersionGroup) Any ¶
func (vg *VersionGroup) Any(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
Any registers a route that matches all HTTP methods. It is useful for catch-all endpoints like health checks or proxies.
It registers 7 separate routes internally (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS). For endpoints that only need specific methods, use individual method registrations (GET, POST, etc.).
Returns the GET route (most common for docs/constraints).
Example:
v1.Any("/health", healthCheckHandler)
func (*VersionGroup) DELETE ¶
func (vg *VersionGroup) DELETE(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
DELETE adds a DELETE route to the version group.
Example:
v1.DELETE("/users/:id", deleteUser).WhereInt("id")
func (*VersionGroup) GET ¶
func (vg *VersionGroup) GET(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
GET adds a GET route to the version group.
Example:
v1.GET("/users/:id", handler).WhereInt("id")
v1.GET("/users/:id", getUser,
app.WithDoc(openapi.WithSummary("Get user")),
)
func (*VersionGroup) Group ¶
func (vg *VersionGroup) Group(prefix string, middleware ...HandlerFunc) *VersionGroup
Group creates a nested version group under the current version group. It combines the parent's prefix with the provided prefix. It inherits middleware from the parent group.
Example:
v1 := app.Version("v1")
api := v1.Group("/api", AuthMiddleware()) // Creates /api prefix within v1
api.GET("/users", handler) // Matches /api/users in v1
func (*VersionGroup) HEAD ¶
func (vg *VersionGroup) HEAD(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
HEAD adds a HEAD route to the version group.
Example:
v1.HEAD("/users/:id", handler).WhereInt("id")
func (*VersionGroup) OPTIONS ¶
func (vg *VersionGroup) OPTIONS(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
OPTIONS adds an OPTIONS route to the version group.
Example:
v1.OPTIONS("/users", handler)
func (*VersionGroup) PATCH ¶
func (vg *VersionGroup) PATCH(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
PATCH adds a PATCH route to the version group.
Example:
v1.PATCH("/users/:id", patchUser).WhereInt("id")
func (*VersionGroup) POST ¶
func (vg *VersionGroup) POST(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
POST adds a POST route to the version group.
Example:
v1.POST("/users", createUser,
app.WithDoc(
openapi.WithSummary("Create user"),
openapi.WithRequest(CreateUserRequest{}),
),
)
func (*VersionGroup) PUT ¶
func (vg *VersionGroup) PUT(path string, handler HandlerFunc, opts ...RouteOption) *route.Route
PUT adds a PUT route to the version group.
Example:
v1.PUT("/users/:id", updateUser).WhereInt("id")
func (*VersionGroup) Use ¶
func (vg *VersionGroup) Use(middleware ...HandlerFunc)
Use adds middleware to the version group that will be executed for all routes in this group. Middleware is executed after the router's global middleware but before the route-specific handlers.
It applies middleware to all later routes registered in this version group.
Example:
v1 := app.Version("v1")
v1.Use(AuthMiddleware(), LoggingMiddleware())
v1.GET("/users", getUsersHandler) // Will execute auth + logging + handler
Source Files
¶
- app.go
- banner.go
- bind.go
- bind_options.go
- context.go
- context_pool.go
- debug_endpoints.go
- debug_options.go
- doc.go
- env.go
- errors.go
- group.go
- health_options.go
- health_readiness.go
- health_standard.go
- lifecycle.go
- mtls.go
- observability.go
- observability_config.go
- observability_options.go
- openapi_state.go
- options.go
- path_filter.go
- reload_unix.go
- route_option.go
- server.go
- testing.go
- validate_options.go
- version_group.go
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
01-quick-start
command
Package main demonstrates a quick start example of the Rivaas router.
|
Package main demonstrates a quick start example of the Rivaas router. |