Documentation
¶
Overview ¶
Package gas is the core of the Gas framework: a Worker runs services with dependency injection and an event bus, and an App embeds a Worker and adds a router and an HTTP server.
The framework's modules, such as gas/config, gas/log and gas/database, meet only through the interfaces this package defines. This package imports none of them.
Usage ¶
An App is built from options, registers its routes, and runs until SIGINT, SIGTERM or the end of the context Run is given:
type Greeter struct{ prefix string }
func (g *Greeter) Greet(name string) string { return g.prefix + name }
app := gas.NewApp(
gas.WithSingletonService[*Greeter](func() *Greeter {
return &Greeter{prefix: "hello, "}
}),
)
app.Router().Get(nil, "/hello/{name}", func(c gas.Context, g *Greeter) error {
return c.Text(http.StatusOK, g.Greet(c.Param("name")))
})
if err := app.Run(context.Background()); err != nil {
log.Fatal(err)
}
With this app:
- A GET of `/hello/gas` answers 200 with the body `hello, gas`.
- A GET of `/nope` answers 404 with the RFC 9457 problem details `{"title":"Not Found","status":404,"detail":"not found","code":"not_found"}`.
Starting ¶
A Worker, and the App that embeds one, runs once: Start, then Shutdown, or Stop for an App. Run does both around a wait for a signal; see Running.
Worker.Start runs these steps, in order:
- It checks the options. An option that got a value it cannot take, such as an origin that is not an absolute URL or a negative init timeout, fails Start with ErrInvalidOption before anything is initialized.
- It initializes the pre-registered instances, in registration order.
- It builds every singleton in dependency order, calling Init on each Service with Start's ctx, under the service's init deadline (ServiceInitTimeout).
- An App binds and validates its Config (see Configuration), seals its router, checks that every DI-aware handler's dependencies are registered, and emits AppConfigUpdated.
- It emits SystemAllServicesInitialized.
- It runs the pending migrations of the MigrationManager, when one is registered, with ctx.
- It runs the ready hooks, in registration order.
When Start fails, it closes every service it had initialized, newest first, with ctx stripped of its cancellation. It returns its error joined with any Close error, and every later Start returns that error again.
A panic in an Init or a constructor fails Start the same way, with an error wrapping ErrPanic, and is panicked again once the cleanup is done. The cleanup builds nothing: it logs through the Logger only when the Logger is already built, and through slog's default logger otherwise.
A second Start returns ErrAlreadyStarted, and a Start once Shutdown has begun returns ErrShutDown.
Shutting down ¶
Worker.Shutdown runs these steps, in order:
- It waits for a CloseService or RestartService in progress.
- It emits SystemShuttingDown and waits for its subscribers.
- It closes every active service, in the reverse order of their Init, passing its ctx to each Close.
- It logs `shutdown complete` and flushes the Logger. Last of all, it closes the Logger, if it is a service, and then the services the Logger depends on, newest first.
No wait lasts longer than ctx allows. Each service turns closed just before its own Close, so a Close may still resolve the services that close after it. A failing Close does not stop the others; Shutdown returns the Close errors, and any wait cut short, joined.
Shutdown runs once. Out of the usual order:
- A second Shutdown waits for the first and returns nil.
- A Shutdown during Start waits for Start to return, or for its ctx to end. In the second case it returns the ctx's error, and Start closes what it initialized once it returns.
- A Shutdown before Start closes nothing, and makes Start return ErrShutDown.
App.Stop bounds the whole shutdown by the Config's ShutdownTimeout plus up to three times ShutdownGracePeriod, and by its ctx, which every wait honors. It runs these steps, in order:
- It emits SystemServerShuttingDown and waits for its subscribers, until the deadline.
- It shuts the HTTP server down.
- At the deadline, it cancels the context of every request still running, and waits up to ShutdownGracePeriod for them to return.
- If some do not, it closes their connections, which ends their blocked reads and writes, and waits up to ShutdownGracePeriod again. It logs how many are left when it gives up.
- It shuts the Worker down with a context whose deadline is the shutdown deadline, or ShutdownGracePeriod from then, whichever is later.
So Stop returns within ShutdownTimeout plus three ShutdownGracePeriods: two waits for requests, and the least time left to close services. A Close must return when its ctx ends, so a shutdown always ends.
Work that uses a service, such as a queue consumer, must stop before that service closes. Either run it in a service that depends on the services it uses, so it closes first, or stop it from a SystemShuttingDown subscriber, which runs before any Close.
Running ¶
Worker.Run and App.Run run the whole lifecycle:
- They start under a context that both the ctx they are given and the first SIGINT or SIGTERM end. Each Init gets it, so either one during startup fails Start through an Init that honors its ctx.
- They block until that signal or the end of ctx, then shut down within the shutdown timeout: WithShutdownTimeout for a Worker, the Config's ShutdownTimeout for an App. The shutdown keeps ctx's values but not its cancellation, so the end of ctx that stopped the run does not cut the shutdown short.
- After the first signal they stop handling signals, so a second one ends the process.
Ending ctx is a normal stop: Run returns the shutdown's error, nil when it is clean. App.Run also logs the route map and serves. It stops when the server fails, and returns that error joined with Stop's.
Services ¶
A Service has a Name, an Init and a Close. The Worker tracks every singleton service it initialized by instance, so two services with one name are both closed.
The Worker reports on its services:
- Worker.ActiveServices lists the names of the active ones.
- Worker.CheckHealth and Worker.CheckReady run the HealthReporter and ReadyReporter checks of the active services at once, and return each result by name. The results of two services with one name are joined. A check that panics reports an error wrapping ErrPanic.
Kill switch ¶
The kill switch takes a service out at runtime, and puts it back.
Worker.CloseService removes the routes, named middleware, NotFound handler and event subscriptions the service registered, then calls its Close and returns its error, wrapped. The service counts as closed either way. From then on:
- Its routes answer 503 in the unified error shape.
- Its named middleware answers 503 wherever it is used.
- Its Router.Use middleware answers 503 on every route.
So a route the service guarded never runs unguarded.
Worker.RestartService calls Init again on the same instance, which registers its routes, middleware and subscriptions anew. Only what that Init registers is served, so a route the restarted service no longer registers answers 404.
- A restart whose Init fails removes what it had registered, and leaves the service closed.
- A restart whose Init returns once Shutdown has begun closes the service and returns an error wrapping ErrShutDown, since Shutdown closes no service that is restarting.
CloseService emits SystemServiceClosed, and RestartService emits SystemServiceInitialized. Neither holds a lock while the service's code runs, so a Close or Init may call back into the Worker. Both find the service by its exact registration type, and fail with an error wrapping:
- ErrNoBuiltInstance, when the Worker started no service under it.
- ErrDuplicateServiceName, when another started service has its name, since registrations are owned by name.
- ErrNotStarted before Start succeeds, and ErrShutDown once Shutdown begins.
- ErrServiceNotActive, from CloseService for a service that is not active, and ErrServiceAlreadyActive, from RestartService for one that is not closed.
A service that is not active, because it is closed or being closed or restarted, is not handed out either. Resolving it fails with an error wrapping ErrServiceNotActive, and a handler that depends on it, or returns that error, answers 503 in the unified error shape. The one exception is a restart's own Init, on the goroutine running it, which may resolve the service it restarts; a goroutine that Init starts may not.
Dependency injection ¶
A ServiceContainer holds instances, and constructors registered with a lifetime. A constructor takes its dependencies as parameters, and returns its value, or its value and an error. The lifetimes:
- A singleton is built once.
- A scoped service is built once per Scope.
- A transient service is built on every resolution. It must not be a Service.
Resolution is by exact type. An interface is resolvable only when it is registered, or is an alias of a registered type (ServiceContainer.RegisterServiceAlias). A Service is initialized right after it is built, and a scoped Service is closed with its Scope, newest first.
Some services are registered for you:
- An App registers its *Router, so a service can take it as a dependency. It does not register its Config: a service binds the settings it needs through its own ConfigProvider dependency.
- Every Worker registers its *EventBus, and itself as HealthProvider and ReadyProvider.
The container rejects these with an error wrapping its sentinel:
- ErrNotRegistered: a dependency that is not registered.
- ErrCircularDependency: constructors that depend on each other in a cycle, a constructor or Init that resolves its own type, and builds on two goroutines that would wait on each other.
- ErrCaptiveDependency: a singleton that depends on a scoped or transient service.
- ErrScopeRequired: a scoped service resolved from the container.
- ErrScopeClosed: a scoped service resolved from a closed Scope.
- ErrInvalidServiceShape: a type that declares Init or Close without being a Service.
Wiring mistakes panic at registration instead, with an error wrapping:
- ErrInvalidConstructor: a constructor the container cannot call.
- ErrNilInstance: a nil instance.
- ErrInvalidAlias: a bad alias.
- ErrInvalidLifetime: a transient Service.
Routing ¶
A Router wraps chi. Every route, middleware and NotFound handler is owned by a service, or by the router when the service is nil. What a service owns is what the kill switch removes.
Router.Group and Router.Route take the owner once and pass a RouteGroup, whose registrations take none: everything in the group, nested groups included, belongs to that service. A RouteGroup uses the router's validator, form decoder, JSON body limit, NotFound handler and ErrorHandler; Router.NotFound and Router.SetErrorHandler set those for the whole router, and a group has neither of its own.
The router defers every registration until Start seals it, so services register middleware and routes in any order; until then every path answers 404. Once sealed, every change builds a new routing tree and publishes it atomically, and requests in flight keep the tree they started on.
Router.Use applies middleware to every route, registered before or after it. When the kill switch removes its owner, the middleware answers 503 on every route until the owner calls Use again, as a restarted service does. The new call takes the place of the old one, so the middleware keeps its position and runs once per request. A restart drops the calls its Init no longer makes.
The router answers on its own:
- A path no route matches answers 404 with code CodeNotFound.
- A path routed for other methods only answers 405 with code CodeMethodNotAllowed, and an Allow header listing them.
- A GET route also serves HEAD.
Both errors are in the unified error shape. Router.Routes reports the routes by owner, with the names of the named middleware that applies to each, the top-level ones first.
Registration panics with an error wrapping ErrUnknownMiddleware for a MiddlewareByName whose name is not registered, and ErrNotFoundHandlerSet for a NotFound handler set by a second service.
Handlers ¶
A handler is an http.HandlerFunc, or a DI-aware func(Context, Deps...) error whose dependencies are resolved from the request's Scope on each call. Another shape panics with ErrInvalidHandler.
The Router's ResponseWriter, and the one CacheControl wraps it in, is an http.Flusher, an http.Hijacker, an http.Pusher and an io.ReaderFrom, and unwraps for http.ResponseController. Each passes on to the server's writer; Hijack and Push return the server's error, such as http.ErrNotSupported, where the connection cannot. RequestLogger wraps it in chi's writer, which is an http.Pusher for an HTTP/2 request only and an http.Hijacker and io.ReaderFrom for an HTTP/1 request only. Timeout buffers the response, and its writer is none of these.
An error a DI-aware handler returns goes to the ErrorHandler. The default one logs it and writes it with WriteError: an *Error as a Problem, and anything else as a 500 with code CodeInternal. A cause never reaches the client.
The Router recovers a panic anywhere in its middleware and handlers, and passes an error wrapping ErrPanic to the ErrorHandler. It logs the panic with its stack through the request's logger, the one RequestLogger derived even for a panic in middleware below it, or else the App's, or else writes the stack to stderr. http.ErrAbortHandler is panicked again, so net/http aborts the request.
Error responses ¶
An error response is an RFC 9457 problem details object, served as ProblemContentType. A client that asks for text/html without application/json or application/problem+json gets the message as plain text instead (WantsJSON). The problem's members:
- type: the error's code under the base Router.SetProblemTypeBase or WithProblemTypeBase sets, such as `https://api.example.com/problems/not_found`. Without a base it is left out, which means `about:blank`.
- title: the HTTP status phrase.
- status: the HTTP status. A status outside 100 to 599 is written as 500.
- detail: the message.
- code: the error's code, an extension member, as are the next two.
- errors: the field errors, each with an RFC 6901 `pointer` to its field.
- details: the Error.WithDetail values.
Every error response carries `Cache-Control: no-store`. Once a response has a status or a body, WriteError writes nothing and returns ErrResponseCommitted, and the default ErrorHandler logs the error instead, so an error never corrupts a response already sent.
Binding requests ¶
Context.BindJSON and Context.BindForm read the request body into a struct and validate it:
- BindJSON reads at most the Config's MaxJSONBodyBytes. It answers 413 with code CodePayloadTooLarge past it, 400 with code CodeInvalidJSON for a malformed body, and 422 with code CodeValidationFailed and one FieldError per violation when validation fails.
- BindForm reads at most the Config's MaxFormBodyBytes. It answers 413 past it or past a BodyLimit, 400 with code CodeInvalidForm, and 422 as BindJSON does.
Context.Principal returns 401 with code CodeUnauthorized when the request has no Principal.
Middleware ¶
RequestLogger derives a per-request logger and puts it on the request context, with `request_id` when chi's RequestID middleware runs before it. It logs one line per request, `request`, with the fields `method`, `path` and `remote` as strings, `status` and `bytes` as ints, and `duration` as a time.Duration through LogEvent.Duration, which the Logger encodes: log/slog's JSON handler as integer nanoseconds, for one. The line's level:
- at info level below 400,
- at warn level below 500,
- at error level from 500.
The default ErrorHandler hands RequestLogger the errors it handles, which the line carries as `error`, so a failed request logs one line. Without RequestLogger, the ErrorHandler logs `request error` itself, at warn level for a 4xx and error for a 5xx. A 5xx is logged even with no Logger registered, through the App's logger over slog's default logger.
The other middleware:
- SecurityHeaders sets security headers with safe defaults.
- CacheControl sets Cache-Control on responses with a 2xx or 3xx status only, rounding durations up to whole seconds, and keeps a Cache-Control the handler set. A negative duration panics with ErrInvalidOption.
- BodyLimit caps request bodies, answering 413 in the unified error shape.
- Timeout gives each request a deadline, answering 503 with code CodeTimeout when it passes. It is for bounded responses, not streams: it buffers the response, up to DefaultTimeoutMaxBuffer or WithTimeoutMaxBuffer, and answers 503 past that, and a request ends only once its handler returns.
Every App is guarded by http.CrossOriginProtection. A request it rejects is logged at warn level with its method and path, and answers 403 with code CodeForbidden in the unified error shape, with the SecurityHeaders defaults. Core ships no CORS.
Events ¶
An EventBus dispatches an event to each of its subscribers on its own goroutine, one goroutine per subscriber per Emit. Emit returns a WaitGroup that completes once they return.
A subscriber that panics is recovered and, on a Worker's bus, logged with the event, its owner and the stack. A subscription made with SubscribeWithOwner is removed when the kill switch closes its owner.
Configuration ¶
An App's Config starts as DefaultConfig, or as the base config set with WithBaseConfig. Once every service is built and initialized, Start copies it, binds the ConfigProvider's values over the copy, when one is registered, and validates the copy against these rules:
- Env must be one of the allowed environments (WithAllowedEnvironments), which always include DefaultEnvironment. It binds from the key `gasenv`, where the config module's EnvExtension stores the environment it resolved from `GAS_ENV`. ConfigProvider has no other way to report the environment, so a provider that resolves it differently must store it at `gasenv`. An environment the EnvExtension allows with config.WithAllowedEnvs or config.WithDefault, such as `local`, must also be passed to WithAllowedEnvironments, or Start fails with `gas: invalid config: gasenv must be one of development, testing, staging, production`, wrapping ErrInvalidConfig.
- Host must be an IP address or an RFC 1123 hostname.
- Port must be 0 to 65535.
- ReadTimeout, WriteTimeout and IdleTimeout must not be negative, 0 being no limit, as in net/http. IdleTimeout must be positive when ReadTimeout is 0, as net/http then bounds an idle connection by neither.
- ReadHeaderTimeout and ShutdownTimeout must be positive, so no configuration leaves a connection open to a client that trickles its headers.
- MaxJSONBodyBytes and MaxFormBodyBytes must be positive.
A Config that breaks a rule fails Start, closing the services, with an error wrapping ErrInvalidConfig that names the key and the rule, never the value. A failing Bind of the provider fails Start the same way, with its error after `gas: invalid config: bind: `.
Start keeps the copy only once it is valid. Until then App.Config returns a copy of the base config, so a service's Init sees the base config; after, it returns a copy of the bound one.
App.Server builds the http.Server from the Config, with App.Handler as its handler. A server built before Start, such as by a service's Init, takes the bound address and timeouts at Start for each of those fields the caller has not changed, unless Serve has begun.
Concurrency ¶
- A Worker, an App, a ServiceContainer, a Scope, a Router and an EventBus are safe for concurrent use.
- Goroutines that resolve one singleton, or one scoped type of a Scope, share one build.
- No lock is held while a service's Init or Close, a constructor, a subscriber or a check runs.
- App.Config may be called concurrently with Start; App.Server may not. Do not change the server's fields once it serves.
- A LogEvent is used by one goroutine and sent once.
Errors ¶
The lifecycle, kill switch, configuration and HTTP errors wrap these sentinels, to be tested with errors.Is:
- ErrAlreadyStarted, ErrNotStarted, ErrShutDown: as under Starting and Shutting down.
- ErrInvalidOption: an option got a value it cannot take.
- ErrInvalidConfig: the bound Config breaks a rule.
- ErrNoBuiltInstance, ErrServiceNotActive, ErrServiceAlreadyActive, ErrDuplicateServiceName: as under Kill switch.
- ErrNotRegistered, ErrCircularDependency, ErrCaptiveDependency, ErrScopeRequired, ErrScopeClosed, ErrInvalidLifetime, ErrInvalidServiceShape, ErrInvalidConstructor, ErrNilInstance, ErrInvalidAlias: as under Dependency injection.
- ErrInvalidHandler, ErrUnknownMiddleware, ErrNotFoundHandlerSet: as under Routing.
- ErrPanic: a recovered panic.
- ErrResponseCommitted: an error written after the response was committed.
- ErrResponseTooLarge: a write past the buffer cap of Timeout.
Errors name services, types, keys, methods and paths, never a configuration value. Sample errors, as returned:
gas: already started gas: not started gas: shut down gas: invalid option: WithServiceInitTimeout must not be negative gas: invalid option: WithShutdownTimeout must be positive gas: invalid option: WithTrustedOrigin needs an absolute URL with a scheme and a host gas: invalid option: WithAllowedEnvironments needs an environment gas: invalid config: gasenv must be one of development, testing, staging, production gas: invalid config: server.host must be an IP address or an RFC 1123 hostname gas: invalid config: server.port must be 0 to 65535 gas: invalid config: server.readtimeout must not be negative gas: invalid config: server.readheadertimeout must be positive gas: invalid config: server.writetimeout must not be negative gas: invalid config: server.idletimeout must not be negative gas: invalid config: server.idletimeout must be positive when server.readtimeout is 0 gas: invalid config: server.maxjsonbodybytes must be positive gas: invalid config: server.maxformbodybytes must be positive gas: invalid config: server.shutdowntimeout must be positive gas: run migrations: dirty migration gas: ready hook: cache cold gas: handler GET /x (service "gas/router"): dependency *bytes.Buffer is not registered gas: close billing: flush failed gas: restart billing: init *gas_test.Billing: dial failed gas: restart billing: gas: shut down gas: no built instance for *gas.Router gas: duplicate service name "billing" gas: service not active: "billing" gas: service already active: "billing" gas: server shutdown: context deadline exceeded gas: stop: wait for SystemServerShuttingDown subscribers: context deadline exceeded gas: shutdown: wait for SystemShuttingDown subscribers: context deadline exceeded gas: shutdown: wait for the kill switch: context deadline exceeded gas: shutdown: wait for Start: context deadline exceeded gas: panic in Start: boom no registration for *bytes.Buffer gas: resolve *bytes.Buffer: no registration for *bytes.Buffer gas: scope closed: resolving *bytes.Buffer gas: panic in handler: boom gas: panic in health check: boom gas: response already committed gas: response too large for the timeout buffer
A registration mistake panics with an error such as:
gas: invalid option: an AppOption needs NewApp gas: already started: ReadyFunc after the ready hooks ran gas: nil instance: gas.ConfigProvider gas: invalid constructor: constructor for *bytes.Buffer is nil, not a function gas: invalid alias: alias *bytes.Buffer must be an interface gas: invalid handler: func(http.ResponseWriter) does not take gas.Context first gas: unknown middleware "auth" (route GET /x) gas: not found handler already set by service "billing" gas: invalid option: BodyLimit must be positive gas: invalid option: Timeout must be positive gas: invalid option: WithTimeoutMaxBuffer must be positive gas: invalid option: WithCacheControlMaxAge must not be negative
Index ¶
- Constants
- Variables
- func ApplyEnqueueOptions(opts []EnqueueOption) (delay time.Duration, groupID, dedupeID string, attrs map[string]string)
- func BodyLimit(n int64) func(next http.Handler) http.Handler
- func CacheControl(opt ...CacheControlOption) func(next http.Handler) http.Handler
- func MetadataValue[T any](m PrincipalMetadata, key string) (T, bool)
- func MustResolveFromRequestScope[T any](r *http.Request) T
- func RequestLogger[T Logger](opt ...RequestLoggerOption) func(next http.Handler) http.Handler
- func ResolveFromRequestScope[T any](r *http.Request) (T, error)
- func SecurityHeaders(opt ...SecurityHeadersOption) func(next http.Handler) http.Handler
- func Timeout(d time.Duration, opt ...TimeoutOption) func(next http.Handler) http.Handler
- func WantsJSON(r *http.Request) bool
- func WithLogger(ctx context.Context, logger Logger) context.Context
- func WithPrincipal(ctx context.Context, principal Principal) context.Context
- func WithRequestScope(ctx context.Context, scope *Scope) context.Context
- func WriteError(w http.ResponseWriter, r *http.Request, err error) error
- type App
- type AppConfigUpdated
- type AppConfigUpdatedPayload
- type AppOption
- func WithAllowedEnvironments(envs ...Environment) AppOption
- func WithBaseConfig(cfg Config) AppOption
- func WithCSRFDenyHandler(h http.Handler) AppOption
- func WithCSRFInsecureBypassPattern(pattern string) AppOption
- func WithErrorHandler(h ErrorHandler) AppOption
- func WithProblemTypeBase(base string) AppOption
- func WithTrustedOrigin(origin string) AppOption
- type BasePrincipalMetadata
- type CacheControlOption
- func WithCacheControlImmutable() CacheControlOption
- func WithCacheControlMaxAge(val time.Duration) CacheControlOption
- func WithCacheControlMustRevalidate() CacheControlOption
- func WithCacheControlMustUnderstand() CacheControlOption
- func WithCacheControlNoCache() CacheControlOption
- func WithCacheControlNoStore() CacheControlOption
- func WithCacheControlNoTransform() CacheControlOption
- func WithCacheControlPath(val string) CacheControlOption
- func WithCacheControlPathPrefix(val string) CacheControlOption
- func WithCacheControlPathPrefixes(val []string) CacheControlOption
- func WithCacheControlPathSuffix(val string) CacheControlOption
- func WithCacheControlPathSuffixes(val []string) CacheControlOption
- func WithCacheControlPaths(val []string) CacheControlOption
- func WithCacheControlPrivate() CacheControlOption
- func WithCacheControlProxyRevalidate() CacheControlOption
- func WithCacheControlPublic() CacheControlOption
- func WithCacheControlSMaxAge(val time.Duration) CacheControlOption
- func WithCacheControlStaleIfError(val time.Duration) CacheControlOption
- func WithCacheControlStaleWhileRevalidate(val time.Duration) CacheControlOption
- type CacheProvider
- type Config
- type ConfigProvider
- type Context
- type ContextOption
- type DatabaseProvider
- type Email
- type EmailProvider
- type EnqueueOption
- type Environment
- type Error
- func AsError(err error) (*Error, bool)
- func BadRequest(message string) *Error
- func Conflict(message string) *Error
- func Forbidden(message string) *Error
- func Internal(message string) *Error
- func NewError(status int, code, message string) *Error
- func NotFound(message string) *Error
- func ServiceUnavailable(message string) *Error
- func TooManyRequests(message string) *Error
- func Unauthorized(message string) *Error
- func Unprocessable(message string) *Error
- type ErrorHandler
- type Event
- type EventBus
- func (bus *EventBus) Close(context.Context) error
- func (bus *EventBus) Emit[E event[D], D any](data D) *sync.WaitGroup
- func (bus *EventBus) Init(context.Context) error
- func (bus *EventBus) Name() string
- func (bus *EventBus) RemoveByService(service Service)
- func (bus *EventBus) Subscribe[E event[D], D any](handler func(D))
- func (bus *EventBus) SubscribeWithOwner[E event[D], D any](service Service, handler func(D))
- type FieldError
- type HealthProvider
- type HealthReporter
- type Job
- type JobQueueProvider
- type LogEvent
- type Logger
- type LoggerContext
- type Middleware
- type Migration
- type MigrationManager
- type NopLogger
- type Option
- type Principal
- type PrincipalMetadata
- type Problem
- type ReadyProvider
- type ReadyReporter
- type RegisteredRoute
- type RequestLoggerOption
- type Resolver
- type Result
- type RouteGroup
- func (g *RouteGroup) Delete(path string, handler any, middleware ...Middleware)
- func (g *RouteGroup) Get(path string, handler any, middleware ...Middleware)
- func (g *RouteGroup) Group(fn func(g *RouteGroup))
- func (g *RouteGroup) Handle(method, path string, handler any, middleware ...Middleware)
- func (g *RouteGroup) Head(path string, handler any, middleware ...Middleware)
- func (g *RouteGroup) Patch(path string, handler any, middleware ...Middleware)
- func (g *RouteGroup) Post(path string, handler any, middleware ...Middleware)
- func (g *RouteGroup) Put(path string, handler any, middleware ...Middleware)
- func (g *RouteGroup) Route(pattern string, fn func(g *RouteGroup))
- func (g *RouteGroup) Use(middleware ...Middleware)
- func (g *RouteGroup) UseMiddlewareByName(middleware string)
- func (g *RouteGroup) UseMiddlewareFunc(middleware func(http.Handler) http.Handler)
- type Router
- func (r *Router) Close(context.Context) error
- func (r *Router) Delete(service Service, path string, handler any, middleware ...Middleware)
- func (r *Router) Get(service Service, path string, handler any, middleware ...Middleware)
- func (r *Router) Group(service Service, fn func(g *RouteGroup))
- func (r *Router) Handle(service Service, method, path string, handler any, middleware ...Middleware)
- func (r *Router) Head(service Service, path string, handler any, middleware ...Middleware)
- func (r *Router) Init(context.Context) error
- func (*Router) Name() string
- func (r *Router) NamedMiddleware() map[string]string
- func (r *Router) NotFound(service Service, handler any)
- func (r *Router) Patch(service Service, path string, handler any, middleware ...Middleware)
- func (r *Router) Post(service Service, path string, handler any, middleware ...Middleware)
- func (r *Router) Put(service Service, path string, handler any, middleware ...Middleware)
- func (r *Router) Register(service Service, name string, mw func(http.Handler) http.Handler)
- func (r *Router) RemoveByService(service Service)
- func (r *Router) Route(service Service, pattern string, fn func(g *RouteGroup))
- func (r *Router) Routes() map[string][]RegisteredRoute
- func (r *Router) Seal()
- func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (r *Router) SetErrorHandler(h ErrorHandler)
- func (r *Router) SetProblemTypeBase(base string) error
- func (r *Router) Use(service Service, middleware ...Middleware)
- func (r *Router) UseMiddlewareByName(service Service, middleware string)
- func (r *Router) UseMiddlewareFunc(service Service, middleware func(http.Handler) http.Handler)
- type Rows
- type Scope
- type Secret
- type SecurityHeadersOption
- func WithSecurityHeadersContentSecurityPolicy(val string) SecurityHeadersOption
- func WithSecurityHeadersContentTypeOptions(val string) SecurityHeadersOption
- func WithSecurityHeadersCrossOriginOpenerPolicy(val string) SecurityHeadersOption
- func WithSecurityHeadersCrossOriginResourcePolicy(val string) SecurityHeadersOption
- func WithSecurityHeadersFrameOptions(val string) SecurityHeadersOption
- func WithSecurityHeadersPermissionsPolicy(val string) SecurityHeadersOption
- func WithSecurityHeadersReferrerPolicy(val string) SecurityHeadersOption
- func WithSecurityHeadersStrictTransportSecurity(val string) SecurityHeadersOption
- type ServerSettings
- type Service
- type ServiceContainer
- func (c *ServiceContainer) BuildAll(ctx context.Context) error
- func (c *ServiceContainer) CanResolve[T any]() bool
- func (c *ServiceContainer) EachInstance(fn func(reflect.Value))
- func (c *ServiceContainer) MustResolve[T any]() T
- func (c *ServiceContainer) NewScope(ctx context.Context) *Scope
- func (c *ServiceContainer) RegisterScopedService[T any](ctor any)
- func (c *ServiceContainer) RegisterService[T any](ctor any, lifetime ServiceLifetime)
- func (c *ServiceContainer) RegisterServiceAlias[Target, Alias any]()
- func (c *ServiceContainer) RegisterServiceInstance[T any](val T)
- func (c *ServiceContainer) RegisterSingletonService[T any](ctor any)
- func (c *ServiceContainer) RegisterTransientService[T any](ctor any)
- func (c *ServiceContainer) Resolve[T any]() (T, error)
- type ServiceContainerOption
- type ServiceInitTimeout
- type ServiceLifetime
- type SystemAllServicesInitialized
- type SystemAllServicesInitializedPayload
- type SystemServerShuttingDown
- type SystemServerShuttingDownPayload
- type SystemServiceClosed
- type SystemServiceClosedPayload
- type SystemServiceInitialized
- type SystemServiceInitializedPayload
- type SystemShuttingDown
- type SystemShuttingDownPayload
- type TemplateProvider
- type TemplatedEmail
- type TimeoutOption
- type Worker
- func (w *Worker) ActiveServices() []string
- func (w *Worker) CheckHealth(ctx context.Context) map[string]error
- func (w *Worker) CheckReady(ctx context.Context) map[string]error
- func (w *Worker) CloseService[T Service](ctx context.Context) error
- func (w *Worker) ConfigProvider() ConfigProvider
- func (w *Worker) EventBus() *EventBus
- func (w *Worker) MigrationManager() MigrationManager
- func (w *Worker) ReadyFunc(fn func(*ServiceContainer) error)
- func (w *Worker) RegisterScopedService[T any](ctor any)
- func (w *Worker) RegisterService[T any](ctor any, lifetime ServiceLifetime)
- func (w *Worker) RegisterServiceAlias[Target, Alias any]()
- func (w *Worker) RegisterServiceInstance[T any](val T)
- func (w *Worker) RegisterSingletonService[T any](ctor any)
- func (w *Worker) RegisterTransientService[T any](ctor any)
- func (w *Worker) RestartService[T Service](ctx context.Context) error
- func (w *Worker) Run(ctx context.Context) error
- func (w *Worker) ServiceContainer() *ServiceContainer
- func (w *Worker) Shutdown(ctx context.Context) error
- func (w *Worker) Start(ctx context.Context) error
- type WorkerOption
- func WithReadyFunc(fn func(*ServiceContainer) error) WorkerOption
- func WithScopedService[T any](ctor any) WorkerOption
- func WithService[T any](ctor any, lifetime ServiceLifetime) WorkerOption
- func WithServiceAlias[Target, Alias any]() WorkerOption
- func WithServiceInitTimeout(timeout time.Duration) WorkerOption
- func WithServiceInstance[T any](val T) WorkerOption
- func WithShutdownTimeout(timeout time.Duration) WorkerOption
- func WithSingletonService[T any](ctor any) WorkerOption
- func WithTransientService[T any](ctor any) WorkerOption
Constants ¶
const ( // DefaultHost is the address the server listens on: every interface. DefaultHost = "0.0.0.0" // DefaultPort is the port the server listens on. DefaultPort = 8080 // DefaultReadTimeout bounds reading a whole request. DefaultReadTimeout = 5 * time.Second // DefaultReadHeaderTimeout bounds reading a request's headers. DefaultReadHeaderTimeout = 5 * time.Second // DefaultWriteTimeout bounds writing a response. DefaultWriteTimeout = 10 * time.Second // DefaultIdleTimeout bounds the wait for the next request on a // keep-alive connection. DefaultIdleTimeout = 2 * time.Minute // DefaultShutdownTimeout bounds a graceful shutdown. DefaultShutdownTimeout = 30 * time.Second // DefaultMaxJSONBodyBytes caps the request body Context.BindJSON // reads: 1 MiB. DefaultMaxJSONBodyBytes = 1 << 20 // DefaultMaxFormBodyBytes caps the request body Context.BindForm // reads: 1 MiB. DefaultMaxFormBodyBytes = 1 << 20 )
The defaults DefaultConfig sets.
const ( CodeBadRequest = "bad_request" CodeInvalidJSON = "invalid_json" CodeInvalidForm = "invalid_form" CodeForbidden = "forbidden" CodeNotFound = "not_found" CodeConflict = "conflict" CodeValidationFailed = "validation_failed" CodeRateLimited = "rate_limited" CodeInternal = "internal_error" CodePayloadTooLarge = "payload_too_large" CodeMethodNotAllowed = "method_not_allowed" CodeTimeout = "request_timeout" )
Error codes written by Gas core. Applications are free to define their own and pass them to NewError.
const DefaultEnvironment = EnvDevelopment
DefaultEnvironment is the environment DefaultConfig sets, the one an App runs in when no ConfigProvider sets another.
const DefaultServiceInitTimeout = 15 * time.Second
DefaultServiceInitTimeout is the deadline a service's Init gets unless WithServiceInitTimeout, WithServiceContainerServiceTimeout or the service's own ServiceInitTimeout sets another.
const DefaultTimeoutMaxBuffer = 8 << 20
DefaultTimeoutMaxBuffer is the most bytes of a response Timeout buffers unless WithTimeoutMaxBuffer sets another: 8 MiB.
const ProblemContentType = "application/problem+json"
ProblemContentType is the media type of a JSON error response: an RFC 9457 problem details object.
const ShutdownGracePeriod = time.Second
ShutdownGracePeriod is how long App.Stop waits for requests that outlive ShutdownTimeout once their contexts are canceled, and the least time it leaves services to close, as described under Shutting down in the package documentation.
Variables ¶
var ( // ErrNotRegistered reports that a type, or a dependency of one, has no // registration or instance in the container. ErrNotRegistered = errors.New("gas: service not registered") // ErrCircularDependency reports that the registered constructors depend // on each other in a cycle, so no build order exists. It is also returned // when a constructor or Init resolves the type being built through a // captured container or scope, when two goroutines' singleton builds // would wait on each other, and when constructors nest more than 256 deep. ErrCircularDependency = errors.New("gas: circular dependency") // ErrCaptiveDependency reports a singleton whose constructor depends on a // scoped or transient service, which the singleton would hold for the // life of the process. ErrCaptiveDependency = errors.New("gas: captive dependency") // ErrScopeRequired reports a scoped service resolved from the container // itself; it can only be resolved from a Scope. ErrScopeRequired = errors.New("gas: scoped service resolved outside a scope") // ErrInvalidLifetime reports a registration whose ServiceLifetime is not // Singleton, Scoped, or Transient. Registering a transient Service // panics with an error wrapping it, since a transient has no managed // lifecycle. ErrInvalidLifetime = errors.New("gas: invalid service lifetime") // ErrInvalidConstructor reports a constructor the container cannot // call. Registration panics with an error wrapping it. ErrInvalidConstructor = errors.New("gas: invalid constructor") // ErrNilInstance reports a nil value passed to RegisterServiceInstance // or WithServiceInstance. Registration panics with an error wrapping // it. ErrNilInstance = errors.New("gas: nil instance") // ErrInvalidAlias reports an alias RegisterServiceAlias cannot // register, or a service registered under a type that is an alias. // Registration panics with an error wrapping it. ErrInvalidAlias = errors.New("gas: invalid alias") // ErrScopeClosed reports a scoped service resolved from a Scope after // its Close. ErrScopeClosed = errors.New("gas: scope closed") // ErrInvalidServiceShape reports a registered type that declares Init or // Close but does not implement Service. ErrInvalidServiceShape = errors.New("gas: invalid service shape") )
Sentinel errors for dependency resolution. Errors from the container wrap one of these, so callers can classify a failure with errors.Is while the message still names the types involved.
var ( // ErrInvalidHandler indicates a handler passed to Handle or NotFound // whose shape is none of those Handle takes. Registration panics with // an error wrapping it. ErrInvalidHandler = errors.New("gas: invalid handler") // ErrPanic indicates a panic that gas recovered, in a handler, a // middleware, an event subscriber or a health or readiness check. The // error names where, and holds the panic value. ErrPanic = errors.New("gas: panic") )
Errors of handlers, as described under Handlers in the package documentation.
var ( // ErrUnknownMiddleware indicates a MiddlewareByName whose name is not // registered. ErrUnknownMiddleware = errors.New("gas: unknown middleware") // ErrNotFoundHandlerSet indicates a NotFound handler registered by a // service while another service's is in place. ErrNotFoundHandlerSet = errors.New("gas: not found handler already set") )
Errors of route registration, as described under Routing in the package documentation. Registration panics with an error wrapping one of them, since each is a wiring mistake fixed at the call site.
var ( // ErrNoBuiltInstance indicates that CloseService or RestartService // found no service the Worker started under the type: it was never // built, is not a singleton service, or is registered under another // type. Lookup is by exact registration type. ErrNoBuiltInstance = errors.New("gas: no built instance") // ErrServiceNotActive indicates a CloseService of a service that is // closed, or being closed or restarted, and a resolution of such a // service from the container. ErrServiceNotActive = errors.New("gas: service not active") // ErrServiceAlreadyActive indicates a RestartService of a service that // is active, or being closed or restarted. ErrServiceAlreadyActive = errors.New("gas: service already active") // ErrDuplicateServiceName indicates a CloseService or RestartService of // a service whose Name another service the Worker started shares. // Routes, middleware and subscriptions are owned by name, so the kill // switch cannot tell the two apart. ErrDuplicateServiceName = errors.New("gas: duplicate service name") )
Errors of the kill switch, as described under Kill switch in the package documentation.
var ( // ErrAlreadyStarted indicates that Start was called on a Worker that // has started or is starting, or ReadyFunc once Start has run the // ready hooks. ErrAlreadyStarted = errors.New("gas: already started") // ErrNotStarted indicates a call that needs a started Worker, such as // CloseService, RestartService or App.Serve, before Start succeeded. ErrNotStarted = errors.New("gas: not started") // ErrShutDown indicates a call made once Shutdown has begun: Start, // CloseService, RestartService or App.Serve. ErrShutDown = errors.New("gas: shut down") // ErrInvalidOption indicates an option given a value it cannot take. // Start returns it before it initializes anything. NewWorker panics // with it when it is given an AppOption. ErrInvalidOption = errors.New("gas: invalid option") )
Lifecycle errors of a Worker or App, as described under Starting and Shutting down in the package documentation.
var ErrInvalidConfig = errors.New("gas: invalid config")
ErrInvalidConfig indicates that the App's Config, as bound, breaks one of the rules listed under Configuration in the package documentation.
var ErrInvalidProblemTypeBase = errors.New("gas: invalid problem type base")
ErrInvalidProblemTypeBase indicates a problem type base that is not an absolute URL with a host and without a query or a fragment.
var ErrResponseCommitted = errors.New("gas: response already committed")
ErrResponseCommitted indicates that WriteError, or WriteErrorJSON, was called once the response had a status or a body, and wrote nothing: an error body appended to a response already sent would corrupt it.
var ErrResponseTooLarge = errors.New("gas: response too large for the timeout buffer")
ErrResponseTooLarge indicates a write that would take a response under Timeout past its buffer cap. The write fails, and the client gets a 503 instead of the response.
Functions ¶
func ApplyEnqueueOptions ¶
func ApplyEnqueueOptions( opts []EnqueueOption, ) (delay time.Duration, groupID, dedupeID string, attrs map[string]string)
ApplyEnqueueOptions returns what opts set, applied in order: the delay, the group ID, the dedupe ID and the attributes, each the zero value when no option sets it. A JobQueueProvider calls it inside Enqueue.
func BodyLimit ¶ added in v0.6.0
BodyLimit is middleware that caps the request body at n bytes, as described under Middleware in the package documentation. A request whose Content-Length is over n is answered with a 413 *Error with code CodePayloadTooLarge in the unified error shape, and its handler does not run. Otherwise the body is read through http.MaxBytesReader, so reading past n fails with *http.MaxBytesError, which Context.BindJSON and Context.BindForm turn into the same 413. An n that is not positive panics with an error wrapping ErrInvalidOption.
func CacheControl ¶
func CacheControl(opt ...CacheControlOption) func(next http.Handler) http.Handler
CacheControl is middleware that sets Cache-Control to the directives the options add, in order, on the responses with a 2xx or 3xx status to requests whose path the path options match, or every path when none is set. Error responses never get it, and a response whose handler set Cache-Control keeps the handler's. With no directive it sets nothing. A directive given a negative duration panics with an error wrapping ErrInvalidOption.
func MetadataValue ¶
func MetadataValue[T any](m PrincipalMetadata, key string) (T, bool)
MetadataValue is a type-safe helper that retrieves a typed value from PrincipalMetadata. It returns the value and true if the key exists and the type assertion succeeds, or the zero value and false otherwise.
func MustResolveFromRequestScope ¶
MustResolveFromRequestScope is ResolveFromRequestScope, and panics with the error when T cannot be resolved.
func RequestLogger ¶
RequestLogger is middleware that logs each request, as described under Middleware in the package documentation. It resolves T from the request scope, derives a per-request logger from it with With, and puts that on the request context with WithLogger, so handlers, later middleware and the default ErrorHandler log through it. The resolved logger itself is never changed, so a singleton is safe.
When T is registered scoped, the per-request logger also takes T's place in the request scope, so what resolves T later in the request gets it. A singleton T, an alias T, and a scoped T whose instance is a Service are left in place. A scoped T the per-request logger is not assignable to is left in place too, and a warning says so once.
When the request ends it logs one line, `request`, with the method, path, status, bytes written, duration and remote address, and the errors the default ErrorHandler handled: at info level below 400, warn below 500, and error from 500. When T cannot be resolved, it passes the request on and logs nothing.
func ResolveFromRequestScope ¶
ResolveFromRequestScope resolves T from the request's Scope, building it if needed, as Scope.Resolve does.
func SecurityHeaders ¶
func SecurityHeaders(opt ...SecurityHeadersOption) func(next http.Handler) http.Handler
SecurityHeaders is middleware that sets security response headers before the handler runs: by default X-Content-Type-Options, X-Frame-Options, Referrer-Policy and Permissions-Policy, with the values the options name as defaults. The options change a value, or set it empty to leave the header out.
func Timeout ¶ added in v0.6.0
Timeout is middleware that gives each request d to be served, as described under Middleware in the package documentation. It is for handlers with bounded responses, not streams. The handler runs with a context that ends after d, and writes into a buffer of at most the WithTimeoutMaxBuffer cap that is sent when it returns. When d passes first, the client gets a 503 *Error with code CodeTimeout in the unified error shape at once, and what the handler writes after is discarded; its writes fail with http.ErrHandlerTimeout. When the request's own context ends first, the client gets a 503 with code CodeUnavailable. A write past the cap fails with ErrResponseTooLarge, and the client gets a 503 with code CodeUnavailable in place of the response. Either way the request ends only once the handler returns, so the handler must honor its context. A panic in the handler is panicked again on the request's goroutine, so the router recovers it. Like http.TimeoutHandler, it does not support streaming, flushing or hijacking. A d that is not positive panics with an error wrapping ErrInvalidOption.
func WantsJSON ¶ added in v0.3.5
WantsJSON reports whether the client prefers a JSON error body. A request that explicitly asks for text/html without also accepting application/json or application/problem+json gets plain text; everything else, including an absent or */* Accept header, gets JSON.
Quality values are ignored: the rule is media-type token presence only.
func WithLogger ¶
WithLogger returns a copy of ctx with the given Logger attached.
func WithPrincipal ¶
WithPrincipal stores a Principal in the given context.
func WithRequestScope ¶
WithRequestScope returns a copy of ctx that carries scope as the request scope, such as for a test that serves a handler without an App.
func WriteError ¶ added in v0.3.5
WriteError renders err as the unified error response, negotiating a Problem or plain text via the Accept header. Values that are not, and do not wrap, an *Error render as a canonical 500 with the original discarded from the response. The problem's type uses the problem type base of the Router or App serving r.
WriteError does not log; the caller decides that. It reads only w and r, so it is safe to call from middleware that runs before the request scope exists. Once the response is committed, as a Router tracks it, it writes nothing and returns ErrResponseCommitted. A JSON encoding error is returned, wrapped.
Types ¶
type App ¶
type App struct {
*Worker
// contains filtered or unexported fields
}
App is a Worker with routing and an HTTP server, as described in the package documentation. It runs once, like its Worker: Start, Serve, Stop. It is safe for concurrent use, except as Server says.
func (*App) Config ¶
Config returns a copy of the App's Config: before Start, the base config (WithBaseConfig); once Start has bound and validated it, the bound one. Changing the copy does not change the App. It is safe to call concurrently with Start.
func (*App) Handler ¶
Handler returns the App's http.Handler: the router behind cross-origin protection, counting the requests in flight so Stop can wait for them. Use it to serve the App from a listener of your own or in tests. It is built once and does not build the server. Before Start, while the router is not sealed, every route answers 404.
func (*App) Run ¶
Run starts the App, logs the route map, serves, and blocks until SIGINT, SIGTERM, the end of ctx or a server error, then stops the App and returns Stop's error, joined with the server error. Start runs under a context that ctx and the first signal both end, so either during startup fails Start through an Init that honors its ctx. Once the first signal arrives the App stops handling them, so a second one ends the process. Stop gets ctx's values but not its cancellation; ShutdownTimeout bounds it. Ending ctx is a normal stop: Run then returns Stop's error.
func (*App) Serve ¶
Serve starts the HTTP server and blocks until it stops. It returns ErrNotStarted before Start succeeds and ErrShutDown once Stop has begun. A server closed by Stop returns nil; any other listener error is returned, wrapped.
func (*App) Server ¶
Server returns the App's *http.Server, building it on the first call from the Config as it stands, as described under Configuration in the package documentation. A server built before Start, such as by a service's Init, takes the bound address and timeouts at Start for every one of those fields the caller has not changed, unless Serve has begun. Do not call Server concurrently with Start, and do not change the server's fields once it serves.
func (*App) Stop ¶
Stop shuts the App down within ShutdownTimeout, as described under Shutting down in the package documentation: it emits SystemServerShuttingDown, shuts the server down, cancels the context of requests still running at the deadline and waits up to ShutdownGracePeriod for them, then shuts the Worker down. ctx bounds it too. Every error is returned, joined; a clean Stop returns nil.
type AppConfigUpdated ¶
type AppConfigUpdated struct {
Event[AppConfigUpdatedPayload]
}
AppConfigUpdated is emitted when the app config is updated.
type AppConfigUpdatedPayload ¶
type AppConfigUpdatedPayload struct {
Config Config
}
AppConfigUpdatedPayload carries the updated config.
type AppOption ¶
type AppOption func(*App)
AppOption configures HTTP-specific aspects of an App (error handler, CSRF, trusted origins). Only NewApp accepts AppOption values; passing one to NewWorker panics.
func WithAllowedEnvironments ¶ added in v0.6.0
func WithAllowedEnvironments(envs ...Environment) AppOption
WithAllowedEnvironments sets the environments Start accepts in Config.Env, for an app whose config module allows others, as the EnvExtension's WithAllowedEnvs does. DefaultEnvironment is always allowed, as it is the environment of an App no configuration sets one for. An empty list makes Start fail with an error wrapping ErrInvalidOption.
Default: EnvDevelopment, EnvTesting, EnvStaging and EnvProduction.
func WithBaseConfig ¶ added in v0.6.0
WithBaseConfig sets the Config that Start binds the ConfigProvider's values over and validates, in place of DefaultConfig. Start validates it even when no ConfigProvider is registered.
Default: DefaultConfig().
func WithCSRFDenyHandler ¶
WithCSRFDenyHandler sets the handler for a request cross-origin protection rejects. The default writes a 403 *Error with code CodeForbidden in the unified error shape.
func WithCSRFInsecureBypassPattern ¶
WithCSRFInsecureBypassPattern adds a path pattern, in the syntax of http.ServeMux, whose requests skip cross-origin protection. Use it only for endpoints that must take unauthenticated cross-origin requests and check them in another way, such as webhook receivers. A pattern http.CrossOriginProtection rejects panics.
func WithErrorHandler ¶
func WithErrorHandler(h ErrorHandler) AppOption
WithErrorHandler sets the function that turns a handler's error into a response; see ErrorHandler. A nil handler keeps the default.
func WithProblemTypeBase ¶ added in v0.6.0
WithProblemTypeBase sets the base URL of the problem types of the App's error responses, as Router.SetProblemTypeBase does. A base it refuses makes Start fail with an error wrapping ErrInvalidOption and ErrInvalidProblemTypeBase.
func WithTrustedOrigin ¶
WithTrustedOrigin adds an origin that may make cross-origin requests with unsafe methods, such as POST, PUT, PATCH and DELETE. The origin is an absolute URL with a scheme and a host, such as `https://app.example.com`. An origin that is not makes Start fail with an error wrapping ErrInvalidOption.
type BasePrincipalMetadata ¶
BasePrincipalMetadata is a map-based implementation of PrincipalMetadata.
func (BasePrincipalMetadata) Value ¶
func (m BasePrincipalMetadata) Value(key string) any
Value returns the metadata value for the given key, or nil if not present.
type CacheControlOption ¶
type CacheControlOption func(*cacheControlOptions)
CacheControlOption configures CacheControl.
func WithCacheControlImmutable ¶
func WithCacheControlImmutable() CacheControlOption
WithCacheControlImmutable adds `immutable`.
func WithCacheControlMaxAge ¶
func WithCacheControlMaxAge(val time.Duration) CacheControlOption
WithCacheControlMaxAge adds `max-age` with val in whole seconds, rounded up. A negative val panics in CacheControl.
func WithCacheControlMustRevalidate ¶
func WithCacheControlMustRevalidate() CacheControlOption
WithCacheControlMustRevalidate adds `must-revalidate`.
func WithCacheControlMustUnderstand ¶
func WithCacheControlMustUnderstand() CacheControlOption
WithCacheControlMustUnderstand adds `must-understand`.
func WithCacheControlNoCache ¶
func WithCacheControlNoCache() CacheControlOption
WithCacheControlNoCache adds `no-cache`.
func WithCacheControlNoStore ¶
func WithCacheControlNoStore() CacheControlOption
WithCacheControlNoStore adds `no-store`.
func WithCacheControlNoTransform ¶
func WithCacheControlNoTransform() CacheControlOption
WithCacheControlNoTransform adds `no-transform`.
func WithCacheControlPath ¶
func WithCacheControlPath(val string) CacheControlOption
WithCacheControlPath adds a path the header applies to exactly.
func WithCacheControlPathPrefix ¶
func WithCacheControlPathPrefix(val string) CacheControlOption
WithCacheControlPathPrefix adds a prefix of the paths the header applies to.
func WithCacheControlPathPrefixes ¶
func WithCacheControlPathPrefixes(val []string) CacheControlOption
WithCacheControlPathPrefixes adds prefixes of the paths the header applies to.
func WithCacheControlPathSuffix ¶
func WithCacheControlPathSuffix(val string) CacheControlOption
WithCacheControlPathSuffix adds a suffix of the paths the header applies to, such as `.css`.
func WithCacheControlPathSuffixes ¶
func WithCacheControlPathSuffixes(val []string) CacheControlOption
WithCacheControlPathSuffixes adds suffixes of the paths the header applies to.
func WithCacheControlPaths ¶
func WithCacheControlPaths(val []string) CacheControlOption
WithCacheControlPaths adds paths the header applies to exactly.
func WithCacheControlPrivate ¶
func WithCacheControlPrivate() CacheControlOption
WithCacheControlPrivate adds `private`.
func WithCacheControlProxyRevalidate ¶
func WithCacheControlProxyRevalidate() CacheControlOption
WithCacheControlProxyRevalidate adds `proxy-revalidate`.
func WithCacheControlPublic ¶
func WithCacheControlPublic() CacheControlOption
WithCacheControlPublic adds `public`.
func WithCacheControlSMaxAge ¶
func WithCacheControlSMaxAge(val time.Duration) CacheControlOption
WithCacheControlSMaxAge adds `s-maxage`, the age for shared caches, as WithCacheControlMaxAge adds `max-age`.
func WithCacheControlStaleIfError ¶
func WithCacheControlStaleIfError(val time.Duration) CacheControlOption
WithCacheControlStaleIfError adds `stale-if-error`, as WithCacheControlMaxAge adds `max-age`.
func WithCacheControlStaleWhileRevalidate ¶
func WithCacheControlStaleWhileRevalidate(val time.Duration) CacheControlOption
WithCacheControlStaleWhileRevalidate adds `stale-while-revalidate`, as WithCacheControlMaxAge adds `max-age`.
type CacheProvider ¶
type CacheProvider interface {
Get(ctx context.Context, key string) ([]byte, error)
Set(ctx context.Context, key string, value []byte, ttl time.Duration) error
Delete(ctx context.Context, key string) error
Exists(ctx context.Context, key string) (bool, error)
}
CacheProvider abstracts key-value caching. Implemented by in-memory, Redis, Valkey, or any other cache service.
type Config ¶
type Config struct {
// Env is the environment the App runs in. It binds from the key
// `gasenv`, where the config module's EnvExtension stores `GAS_ENV`.
Env Environment `json:"gasenv"`
// Server holds the HTTP server's settings, bound from the key
// `server`.
Server ServerSettings
}
Config is the App's configuration, as described under Configuration in the package documentation. Start binds it from the ConfigProvider, when one is registered, and validates it.
func DefaultConfig ¶
func DefaultConfig() *Config
DefaultConfig returns a Config holding the Default constants and DefaultEnvironment.
type ConfigProvider ¶
type ConfigProvider interface {
// SetDefault sets the value at key unless the configuration holds one.
SetDefault(key string, value any) error
// SetDefaults sets defaults from a struct or map, as SetDefault does
// for each of its keys.
SetDefaults(values any) error
// Set sets the value at key.
Set(key string, value any) error
// Bind fills dest, a pointer to a struct, from the configuration and
// validates it.
Bind(dest any) error
// Get returns the value at key, or nil.
Get(key string) any
// Find returns the value at key and reports whether it exists.
Find(key string) (value any, exist bool)
// Values returns a copy of every value.
Values() map[string]any
}
ConfigProvider is how services, and App.Start, read configuration, as described under Configuration in the package documentation. The config module's *config.Config implements it; core defines it so the root module depends on no other gas module.
type Context ¶
type Context interface {
context.Context
// ResponseWriter returns the underlying http.ResponseWriter.
ResponseWriter() http.ResponseWriter
// Request returns the underlying *http.Request.
Request() *http.Request
// JSON serializes v as JSON and writes it with the given status code.
JSON(status int, v any) error
// XML serializes v as XML and writes it with the given status code.
// Uses content type "application/xml; charset=utf-8".
XML(status int, v any) error
// HTML writes an HTML response with the given status code and content string.
HTML(status int, s string) error
// Text writes a plain-text response with the given status code.
Text(status int, s string) error
// Blob writes b with the given status code under the given content type.
// An empty contentType leaves the Content-Type header as it is; if none is
// set, net/http detects one from the start of b.
Blob(status int, contentType string, b []byte) error
// NoContent writes a 204 No Content response.
NoContent() error
// WriteError writes err as the unified error response, negotiating JSON or
// plain text the same way the default ErrorHandler does. Values that are
// not, and do not wrap, an *Error render as a canonical 500.
WriteError(err error) error
// WriteErrorJSON writes err as a Problem regardless of the Accept
// header. Use it for JSON API routes in an app whose ErrorHandler
// renders HTML.
WriteErrorJSON(err error) error
// Redirect sends an HTTP redirect to the given URL with the given status code.
Redirect(status int, url string)
// Param returns the URL parameter value by name (chi.URLParam).
Param(key string) string
// Query returns the query string parameter value by name.
Query(key string) string
// Header returns the request header value by name.
Header(key string) string
// SetHeader sets a response header.
SetHeader(key, value string)
// BindJSON decodes the request body as JSON into dest and performs automatic validation
// using the configured validator.
BindJSON(dest any) error
// BindForm binds form data from the HTTP request to the provided destination object
// and performs automatic validation using the configured validator.
BindForm(dest any) error
// Validator returns the *validator.Validate used for request validation,
// building the package default on first use if none was supplied.
Validator() *validator.Validate
// FormDecoder returns the *schema.Decoder used to decode form data into
// structs, building the package default on first use if none was supplied.
FormDecoder() *schema.Decoder
// Principal returns the authenticated Principal attached to the request,
// or a 401 *Error with code CodeUnauthorized when none is present.
Principal() (Principal, error)
}
Context is the first parameter of every DI-aware handler. It wraps the HTTP response writer and request into a single value. The per-request scope is accessible via RequestScope(c.Request()); the adapter resolves dependencies automatically, so handlers rarely need to access the scope directly.
func NewContext ¶
func NewContext( parent context.Context, w http.ResponseWriter, r *http.Request, opts ...ContextOption, ) Context
NewContext creates a Context from the standard HTTP pair. It panics if parent, w, or r is nil.
The returned Context is installed as the request's own context, so Request().Context() and the Context itself are the same value.
type ContextOption ¶
type ContextOption func(*reqContext)
ContextOption is a functional option used to modify or extend the behavior of a reqContext at creation time.
func WithFormDecoder ¶
func WithFormDecoder(d *schema.Decoder) ContextOption
WithFormDecoder sets a custom form decoder for the reqContext using the provided *schema.Decoder instance.
func WithMaxFormBodyBytes ¶ added in v0.6.0
func WithMaxFormBodyBytes(n int64) ContextOption
WithMaxFormBodyBytes sets how many bytes of the request body BindForm reads before it fails with a 413 *Error. Zero or a negative n means no limit of its own, which leaves net/http's 10 MB. Without this option the limit is 1 MiB.
func WithMaxJSONBodyBytes ¶ added in v0.5.5
func WithMaxJSONBodyBytes(n int64) ContextOption
WithMaxJSONBodyBytes sets how many bytes of the request body BindJSON reads before it fails with a 413 *Error. Zero or a negative n means no limit. Without this option the limit is 1 MiB.
func WithValidate ¶
func WithValidate(v *validator.Validate) ContextOption
WithValidate returns a ContextOption that sets the provided *validator.Validate instance to the reqContext.
type DatabaseProvider ¶
type DatabaseProvider interface {
DB() *sql.DB
Driver() string
Ping(ctx context.Context) error
Query(ctx context.Context, query string, args ...any) (Rows, error)
Exec(ctx context.Context, query string, args ...any) (Result, error)
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
WithTx(ctx context.Context, opts *sql.TxOptions, fn func(*sql.Tx) error) (err error)
}
DatabaseProvider abstracts database access. Implemented by gas/database or any other database service. DB() exposes the underlying *sql.DB so that sqlc-generated code and transactions can use it directly.
type Email ¶
type Email struct {
From string
ReplyTo string
Subject string
TextBody string
HTMLBody string
Headers map[string]string
To []string
Cc []string
Bcc []string
}
Email represents an email message.
type EmailProvider ¶
type EmailProvider interface {
Send(ctx context.Context, msg *Email) error
SendFromTemplate(ctx context.Context, msg *TemplatedEmail) error
}
EmailProvider abstracts email sending.
type EnqueueOption ¶
type EnqueueOption func(*enqueueOptions)
EnqueueOption configures an Enqueue call.
func WithDedupeID ¶
func WithDedupeID(id string) EnqueueOption
WithDedupeID sets a deduplication identifier.
func WithDelay ¶
func WithDelay(d time.Duration) EnqueueOption
WithDelay sets an initial delay before the job becomes visible to consumers.
func WithGroupID ¶
func WithGroupID(id string) EnqueueOption
WithGroupID sets the message group for FIFO queue ordering.
func WithJobAttributes ¶
func WithJobAttributes(attrs map[string]string) EnqueueOption
WithJobAttributes attaches provider-specific key-value metadata to the job.
type Environment ¶ added in v0.5.5
type Environment string
Environment is the environment an App runs in, such as development or production, as described under Configuration in the package documentation. Values are compared with their case. The config module's Environment is an alias of it.
const ( EnvDevelopment Environment = "development" EnvTesting Environment = "testing" EnvStaging Environment = "staging" EnvProduction Environment = "production" )
The standard environments, which an App accepts by default.
func (Environment) IsDevelopment ¶ added in v0.5.5
func (e Environment) IsDevelopment() bool
IsDevelopment reports whether e is EnvDevelopment.
func (Environment) IsDevelopmentLike ¶ added in v0.5.5
func (e Environment) IsDevelopmentLike() bool
IsDevelopmentLike reports whether e is EnvDevelopment or EnvTesting.
func (Environment) IsProduction ¶ added in v0.5.5
func (e Environment) IsProduction() bool
IsProduction reports whether e is EnvProduction.
func (Environment) IsProductionLike ¶ added in v0.5.5
func (e Environment) IsProductionLike() bool
IsProductionLike reports whether e is EnvProduction or EnvStaging.
func (Environment) IsStaging ¶ added in v0.5.5
func (e Environment) IsStaging() bool
IsStaging reports whether e is EnvStaging.
func (Environment) IsTesting ¶ added in v0.5.5
func (e Environment) IsTesting() bool
IsTesting reports whether e is EnvTesting.
func (Environment) String ¶ added in v0.5.5
func (e Environment) String() string
String returns e as a string.
type Error ¶ added in v0.3.5
type Error struct {
// Details holds the values of WithDetail, written as the problem's
// `details` member.
Details map[string]any
// Code is the application's error code, written as the problem's
// `code` member and appended to the problem type base for its `type`.
Code string
// Message is the human-readable explanation of this occurrence,
// written as the problem's `detail` member.
Message string
// Fields are the field-level validation failures, written as the
// problem's `errors` member.
Fields []FieldError
// Status is the HTTP status. A status outside 100 to 599 is written as
// 500.
Status int
// contains filtered or unexported fields
}
Error is the unified error shape for Gas handlers. Return it from a DI-aware handler and the ErrorHandler renders it as a Problem; see WriteError.
The wrapped cause is unexported and never serialized. It reaches logs and errors.Is / errors.As, never a client.
func BadRequest ¶ added in v0.3.5
BadRequest returns a 400 Error with code CodeBadRequest.
func NewError ¶ added in v0.3.5
NewError builds an Error with an explicit status, code, and message.
func ServiceUnavailable ¶ added in v0.3.5
ServiceUnavailable returns a 503 Error with code CodeUnavailable.
func TooManyRequests ¶ added in v0.3.5
TooManyRequests returns a 429 Error with code CodeRateLimited.
func Unauthorized ¶ added in v0.3.5
Unauthorized returns a 401 Error with code CodeUnauthorized.
func Unprocessable ¶ added in v0.3.5
Unprocessable returns a 422 Error with code CodeValidationFailed.
func (*Error) Error ¶ added in v0.3.5
Error implements the error interface. The wrapped cause is included so that logs carry the full chain.
func (*Error) Unwrap ¶ added in v0.3.5
Unwrap returns the wrapped cause, so errors.Is and errors.As see through it.
func (*Error) WithCause ¶ added in v0.3.5
WithCause attaches an underlying cause. The cause is logged and is reachable through errors.Is and errors.As, but is never written to a response.
func (*Error) WithDetail ¶ added in v0.3.5
WithDetail attaches an arbitrary key/value pair to the response payload.
type ErrorHandler ¶
ErrorHandler turns an error a handler returned, or a recovered panic, into a response. The default logs the error, as described under Handlers and errors in the package documentation, and writes it with WriteError.
type Event ¶
type Event[D any] struct{}
Event is embedded in an event type to declare the payload that event carries. An event is a type, not a value:
type OrderPlaced struct{ gas.Event[OrderPlacedPayload] }
Emit, Subscribe, and SubscribeWithOwner take that type as their only explicit type argument and infer the payload from it, so a handler registered for OrderPlaced is bound at compile time to OrderPlacedPayload. Subscriptions are keyed by the event type itself, so two events never collide, even when their payload types are identical.
type EventBus ¶
type EventBus struct {
// contains filtered or unexported fields
}
EventBus is a publish/subscribe message bus with service ownership tracking. Subscriptions are keyed by event type; those registered through SubscribeWithOwner are additionally tagged with the owning service, so the kill switch can drop them as a group when that service closes.
All methods are safe for concurrent use.
func (*EventBus) Close ¶ added in v0.5.0
Close is a no-op; subscriptions are removed through RemoveByService.
func (*EventBus) Emit ¶
Emit dispatches data to every subscriber of event type E, each on its own goroutine, and returns a WaitGroup that completes once they all return:
bus.Emit[OrderPlaced](OrderPlacedPayload{ID: id}).Wait()
Wait on it when handlers must finish before the caller continues. The returned WaitGroup is never nil, and emitting an event nobody subscribes to is a no-op. Emit holds the bus lock only long enough to snapshot the subscriber list; handlers run without it, so a handler may subscribe or emit in turn.
A handler that panics is recovered, so it neither takes the process down nor keeps the WaitGroup from completing, and the other handlers still run. On a Worker's bus the panic is logged with the event type, the owning service and the stack; on a bus of its own the stack goes to stderr.
func (*EventBus) Init ¶ added in v0.5.0
Init is a no-op. It exists so the EventBus satisfies Service and can be passed as an owner.
func (*EventBus) RemoveByService ¶ added in v0.3.5
RemoveByService removes every subscription that service registered through SubscribeWithOwner, across all event types, and drops any event whose last subscriber is gone. Subscriptions made with Subscribe have no owner and are left in place.
func (*EventBus) Subscribe ¶
Subscribe registers a handler for event type E with no owning service:
bus.Subscribe[OrderPlaced](func(p OrderPlacedPayload) { ... })
Use SubscribeWithOwner instead when subscribing from a service, so RemoveByService can clean the subscription up when that service closes. Handlers are started in registration order but run concurrently, so a handler that touches shared state must do its own synchronization.
func (*EventBus) SubscribeWithOwner ¶
SubscribeWithOwner registers a handler for event type E and records service as its owner. Worker.CloseService uses that ownership to remove every subscription belonging to a service it tears down, so services should always subscribe through this method rather than Subscribe.
type FieldError ¶ added in v0.3.5
type FieldError struct {
Message string `json:"detail"`
Pointer string `json:"pointer"`
Rule string `json:"rule"`
}
FieldError describes a single field-level validation failure, as an item of a problem's `errors` member.
Pointer is an RFC 6901 JSON pointer, in its URI fragment form, to the field in the request body, such as `#/email` or `#/items/0/name`. Each name in it is the one the client sent: the field's `json` tag name, else its `form` tag name, else, when it has neither or its `json` tag is `-`, its Go name. An embedded struct, or pointer to one, with neither name adds no name, since encoding/json flattens its fields into the enclosing object: an error on such a field at the root, such as a `required` embedded pointer, points at the whole body, `#`.
Field order is the JSON key order of the item.
type HealthProvider ¶
HealthProvider aggregates health signals across all active services that implement HealthReporter. The returned map keys are service names; a nil value indicates a healthy service.
type HealthReporter ¶
HealthReporter is implemented by services that can report their own health. Answers the question "am I alive?": a failure here signals that the process is in a broken state that a restart would resolve (lost connection that won't recover, corrupt internal state, deadlock). Maps to a Kubernetes livenessProbe. A nil return means healthy; a non-nil error describes the failure.
type Job ¶
type Job struct {
ID string
ReceiptHandle string // opaque token used by Ack/Nack
Attributes map[string]string // provider-specific metadata
Body []byte
}
Job represents a message received from a queue.
type JobQueueProvider ¶
type JobQueueProvider interface {
Enqueue(ctx context.Context, queue string, payload []byte, opts ...EnqueueOption) error
Dequeue(ctx context.Context, queue string, maxMessages int, wait time.Duration) ([]Job, error)
Ack(ctx context.Context, queue string, job Job) error
Nack(ctx context.Context, queue string, job Job) error
}
JobQueueProvider abstracts async job/message queue processing. Implemented by gas/queue/sqs or any other queue service. The interface is pull-based: consumers call Dequeue in their own worker loop and acknowledge results with Ack/Nack.
type LogEvent ¶
type LogEvent interface {
Str(key, val string) LogEvent
Int(key string, val int) LogEvent
Int64(key string, val int64) LogEvent
Float64(key string, val float64) LogEvent
Bool(key string, val bool) LogEvent
Err(key string, val error) LogEvent
Duration(key string, val time.Duration) LogEvent
Any(key string, val any) LogEvent
// Send finalizes and emits the log event.
Send()
}
LogEvent is a single structured log entry. Field methods return the same LogEvent for chaining. Call Send to finalize and emit the event. Send may be called once; an event must not be used after Send, and an event is not safe for concurrent use.
type Logger ¶
type Logger interface {
// Trace starts a log event at the TRACE level.
Trace(msg string) LogEvent
// Debug starts a log event at the DEBUG level.
Debug(msg string) LogEvent
// Info starts a log event at the INFO level.
Info(msg string) LogEvent
// Warn starts a log event at the WARN level.
Warn(msg string) LogEvent
// Error starts a log event at the ERROR level.
Error(msg string) LogEvent
// With returns a LoggerContext for building a sub-logger with persistent fields.
With() LoggerContext
// Flush writes any buffered log entries to the underlying output.
Flush()
}
Logger is the provider interface for structured logging in the Gas ecosystem. Implementations produce LogEvent values at a given severity level. Each method returns a LogEvent that can be enriched with typed fields before calling Send.
Use With to derive a sub-logger that carries persistent fields across all subsequent log events.
Call Flush to ensure all buffered log entries are written, such as before shutdown. A Worker flushes its Logger at the end of Shutdown, as described under Shutting down in the package documentation.
func LoggerFromContext ¶
LoggerFromContext returns the Logger stored in ctx, or nil if none is present.
type LoggerContext ¶
type LoggerContext interface {
Str(key, val string) LoggerContext
Int(key string, val int) LoggerContext
Int64(key string, val int64) LoggerContext
Float64(key string, val float64) LoggerContext
Bool(key string, val bool) LoggerContext
Err(key string, val error) LoggerContext
Duration(key string, val time.Duration) LoggerContext
Any(key string, val any) LoggerContext
// Logger returns a new Logger that includes all fields added to this context.
Logger() Logger
}
LoggerContext is a builder for deriving a sub-logger with persistent fields. Each field method returns the same LoggerContext for chaining. Call Logger to obtain the resulting Logger that carries all accumulated fields.
type Middleware ¶
type Middleware struct {
// contains filtered or unexported fields
}
Middleware is either a named middleware, resolved from the router's registry whenever the routing tree is built, or an inline middleware function.
func MiddlewareByName ¶
func MiddlewareByName(name string) Middleware
MiddlewareByName returns a Middleware resolved by name from the router's registry, as Router.Register put it there.
func MiddlewareFunc ¶
func MiddlewareFunc(fn func(http.Handler) http.Handler) Middleware
MiddlewareFunc returns an inline Middleware. It has no name, so Router.Routes and the route map leave it out; MiddlewareFuncWithName gives it one.
func MiddlewareFuncWithName ¶
MiddlewareFuncWithName returns an inline Middleware whose name Router.Routes and the route map show with the routes it applies to.
type MigrationManager ¶
type MigrationManager interface {
Service
// Register adds a migration and tracks which service owns it.
Register(service Service, m Migration)
// RegisterSlice adds multiple migrations at once for the given service.
RegisterSlice(service Service, migrations []Migration)
// RegisterFS reads .up.sql/.down.sql files from an fs.FS and registers
// them as migrations for the given service. Files must follow the naming
// convention: {version}_{description}.up.sql / {version}_{description}.down.sql
// (e.g. 20250216120000_create_users.up.sql). The version is the text
// before the first underscore.
RegisterFS(service Service, fsys fs.FS) error
// RunPendingContext applies all unapplied migrations in global version
// order, honoring ctx. If any migration is marked dirty, execution is
// blocked until the dirty state is manually resolved. Start calls it
// with its own ctx.
RunPendingContext(ctx context.Context) error
// DownContext reverses the last n applied migrations in reverse
// version order, honoring ctx.
DownContext(ctx context.Context, n int) error
}
MigrationManager registers and runs database migrations. Services call Register during Init to declare their migrations, and Start runs the pending ones once every service is initialized, as described under Starting in the package documentation. The gas/migrate module implements it.
type NopLogger ¶
type NopLogger struct{}
NopLogger is a Logger that discards everything. Its zero value is ready to use, and its events and contexts are shared values, so logging through it allocates nothing.
func (*NopLogger) With ¶
func (*NopLogger) With() LoggerContext
With returns a context that discards its fields and whose Logger is a NopLogger.
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option is a marker interface satisfied by both WorkerOption and AppOption. NewWorker and NewApp accept ...Option so callers can pass either type.
type Principal ¶
type Principal interface {
Subject() string
Scheme() string
CredentialID() string
Metadata() PrincipalMetadata
}
Principal represents an authenticated identity. Subject is the stable user identifier, Scheme identifies the authentication method (e.g. "jwt", "session", "apikey"), and CredentialID is the specific credential instance (session ID, JWT jti, API key ID).
func PrincipalFromContext ¶
PrincipalFromContext retrieves the Principal from the context and reports whether one is present. A nil Principal, or a typed-nil one (a nil pointer of a type implementing Principal), counts as absent: the result is nil and false, so a caller checking ok never gets a principal it cannot use.
type PrincipalMetadata ¶
PrincipalMetadata provides read-only access to arbitrary key-value metadata attached to a Principal at construction time.
type Problem ¶ added in v0.6.0
type Problem struct {
Type string `json:"type,omitempty"`
Title string `json:"title"`
Status int `json:"status"`
Detail string `json:"detail,omitempty"`
Code string `json:"code,omitempty"`
Errors []FieldError `json:"errors,omitempty"`
Details map[string]any `json:"details,omitempty"`
}
Problem is the wire shape of every JSON error response Gas writes: an RFC 9457 problem details object, served as ProblemContentType. It is exported so Go clients and tests can decode it.
Type is the problem type base joined with Code, and is left out, which means `about:blank`, when no base is set or the error has no code; see Router.SetProblemTypeBase. Title is the HTTP status phrase. Code, Errors and Details are extension members.
Field order is the JSON key order of the response body.
type ReadyProvider ¶
ReadyProvider aggregates readiness signals across all active services that implement ReadyReporter. The returned map keys are service names; a nil value indicates a ready service.
type ReadyReporter ¶
ReadyReporter is implemented by services that can report whether they are ready to accept traffic. Answers the question "should traffic be routed to me?", distinct from health, since a service can be alive but not yet ready (warming caches, pending migrations, dependency briefly unavailable, draining during shutdown). Maps to a Kubernetes readinessProbe. A nil return means ready; a non-nil error describes why the service is not ready.
type RegisteredRoute ¶
type RegisteredRoute struct {
// Method is the route's HTTP method.
Method string
// Path is the route's full path.
Path string
// Middleware are the names of the named middleware that apply to it,
// the top-level ones first.
Middleware []string
}
RegisteredRoute is a snapshot of a registered route.
type RequestLoggerOption ¶
type RequestLoggerOption func(*requestLoggerOptions)
RequestLoggerOption configures RequestLogger.
func WithRequestLoggerAppendRequestID ¶
func WithRequestLoggerAppendRequestID(val bool) RequestLoggerOption
WithRequestLoggerAppendRequestID sets whether the per-request logger carries chi's request ID as `request_id`, which needs chi's RequestID middleware upstream.
Default: true.
func WithRequestLoggerBuilder ¶
func WithRequestLoggerBuilder(fn func(Logger, *http.Request) Logger) RequestLoggerOption
WithRequestLoggerBuilder sets a function that derives the per-request logger from the resolved one, after the request ID is added, such as to add fields. A nil function adds nothing.
Default: nil.
type Resolver ¶
type Resolver interface {
// contains filtered or unexported methods
}
Resolver is implemented by ServiceContainer and Scope to provide dependency resolution. The unexported method restricts implementation to this package.
type RouteGroup ¶ added in v0.6.0
type RouteGroup struct {
// contains filtered or unexported fields
}
RouteGroup registers routes in a group made by Router.Group or Router.Route, as described under Routing in the package documentation. Everything registered through it, and through the groups made from it, is owned by the service the group was made for. It is valid only during the call of the function it is passed to, which runs again on every build of the routing tree.
func (*RouteGroup) Delete ¶ added in v0.6.0
func (g *RouteGroup) Delete(path string, handler any, middleware ...Middleware)
Delete registers a DELETE route via Handle.
func (*RouteGroup) Get ¶ added in v0.6.0
func (g *RouteGroup) Get(path string, handler any, middleware ...Middleware)
Get registers a GET route, which also serves HEAD, via Handle.
func (*RouteGroup) Group ¶ added in v0.6.0
func (g *RouteGroup) Group(fn func(g *RouteGroup))
Group registers an inline group inside g, as Router.Group does, owned by g's owner.
func (*RouteGroup) Handle ¶ added in v0.6.0
func (g *RouteGroup) Handle(method, path string, handler any, middleware ...Middleware)
Handle registers a route on g, owned by g's owner, as Router.Handle does. Its path is relative to the patterns of the enclosing Route calls.
func (*RouteGroup) Head ¶ added in v0.6.0
func (g *RouteGroup) Head(path string, handler any, middleware ...Middleware)
Head registers a HEAD route via Handle.
func (*RouteGroup) Patch ¶ added in v0.6.0
func (g *RouteGroup) Patch(path string, handler any, middleware ...Middleware)
Patch registers a PATCH route via Handle.
func (*RouteGroup) Post ¶ added in v0.6.0
func (g *RouteGroup) Post(path string, handler any, middleware ...Middleware)
Post registers a POST route via Handle.
func (*RouteGroup) Put ¶ added in v0.6.0
func (g *RouteGroup) Put(path string, handler any, middleware ...Middleware)
Put registers a PUT route via Handle.
func (*RouteGroup) Route ¶ added in v0.6.0
func (g *RouteGroup) Route(pattern string, fn func(g *RouteGroup))
Route registers a group under pattern inside g, as Router.Route does, owned by g's owner.
func (*RouteGroup) Use ¶ added in v0.6.0
func (g *RouteGroup) Use(middleware ...Middleware)
Use applies middleware to the group's routes, in order, outermost first, including those registered on the group before the call. A MiddlewareByName whose name is not registered panics with an error wrapping ErrUnknownMiddleware.
func (*RouteGroup) UseMiddlewareByName ¶ added in v0.6.0
func (g *RouteGroup) UseMiddlewareByName(middleware string)
UseMiddlewareByName is Use with MiddlewareByName(middleware).
func (*RouteGroup) UseMiddlewareFunc ¶ added in v0.6.0
func (g *RouteGroup) UseMiddlewareFunc(middleware func(http.Handler) http.Handler)
UseMiddlewareFunc is Use with MiddlewareFunc(middleware).
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router routes requests to handlers and tracks which service owns each route and named middleware, as described under Routing in the package documentation. It wraps chi. A top-level Router defers every registration until Seal, so services register middleware and routes in any order; once sealed, every change builds a new routing tree and publishes it atomically, so requests in flight keep the tree they started on. It is safe for concurrent use.
func NewRouter ¶
func NewRouter() *Router
NewRouter returns an unsealed Router with an empty middleware registry.
func (*Router) Delete ¶ added in v0.5.2
func (r *Router) Delete(service Service, path string, handler any, middleware ...Middleware)
Delete registers a DELETE route via Handle.
func (*Router) Get ¶ added in v0.5.2
func (r *Router) Get(service Service, path string, handler any, middleware ...Middleware)
Get registers a GET route, which also serves HEAD, via Handle.
func (*Router) Group ¶
func (r *Router) Group(service Service, fn func(g *RouteGroup))
Group registers an inline group owned by service, or by the router when service is nil: fn gets a RouteGroup whose Use applies to the routes fn registers on it only. fn runs on every build of the tree.
func (*Router) Handle ¶
func (r *Router) Handle( service Service, method, path string, handler any, middleware ...Middleware, )
Handle registers a route owned by service, or by the router when service is nil, as described under Routing and Handlers in the package documentation. handler is an http.HandlerFunc, a func(http.ResponseWriter, *http.Request), or a DI-aware func(Context, Deps...) error. A GET route also serves HEAD. middleware applies in order, outermost first.
It panics with an error wrapping ErrInvalidHandler for a handler of another shape, and ErrUnknownMiddleware for a MiddlewareByName whose name is not registered.
func (*Router) Head ¶ added in v0.5.2
func (r *Router) Head(service Service, path string, handler any, middleware ...Middleware)
Head registers a HEAD route via Handle.
func (*Router) Init ¶ added in v0.5.0
Init does nothing. It exists so the Router is a Service and can own routes.
func (*Router) NamedMiddleware ¶
NamedMiddleware returns a snapshot of the named middleware registry: the owner's name by middleware name.
func (*Router) NotFound ¶
NotFound sets the handler for a path no route matches, owned by service, or by the router when service is nil. handler takes the shapes Handle takes. Without one, a 404 *Error with code CodeNotFound is written in the unified error shape. The owner may set it again, such as when it restarts; another service setting it panics with an error wrapping ErrNotFoundHandlerSet. RemoveByService of the owner removes it.
func (*Router) Patch ¶ added in v0.5.2
func (r *Router) Patch(service Service, path string, handler any, middleware ...Middleware)
Patch registers a PATCH route via Handle.
func (*Router) Post ¶ added in v0.5.2
func (r *Router) Post(service Service, path string, handler any, middleware ...Middleware)
Post registers a POST route via Handle.
func (*Router) Put ¶ added in v0.5.2
func (r *Router) Put(service Service, path string, handler any, middleware ...Middleware)
Put registers a PUT route via Handle.
func (*Router) Register ¶
Register adds named middleware to the registry, owned by service, or by the router when service is nil. A name registered again is replaced; a name whose owner was removed is taken back.
func (*Router) RemoveByService ¶ added in v0.3.5
RemoveByService takes down what service registered, as described under Services in the package documentation: its routes answer 503 in the unified error shape, its named middleware answers 503 wherever it is used and its Use calls on every route, so a route guarded by it never runs unguarded, and its NotFound handler is removed. Its registrations are dropped, so when it registers again, such as on restart, only what it registers then is served. The new tree is published atomically.
func (*Router) Route ¶
func (r *Router) Route(service Service, pattern string, fn func(g *RouteGroup))
Route registers a group under pattern, owned by service, or by the router when service is nil: fn gets a RouteGroup whose paths are relative to pattern. Route with a pattern already used attaches to that mount; each call's body runs in a group of its own, so its Use applies to its own routes only. fn runs on every build of the tree.
func (*Router) Routes ¶
func (r *Router) Routes() map[string][]RegisteredRoute
Routes returns a snapshot of the registered routes by owner, in registration order. The HEAD routes registered alongside GET routes are left out.
func (*Router) Seal ¶
func (r *Router) Seal()
Seal builds the routing tree from the deferred registrations and publishes it: the top-level Use calls first, then the routes, in order. After Seal, every change builds and publishes a new tree. Sealing a sealed router does nothing.
func (*Router) ServeHTTP ¶
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP serves req from the published tree, read without a lock. It recovers a panic anywhere in the middleware or handlers, as described under Handlers in the package documentation.
func (*Router) SetErrorHandler ¶
func (r *Router) SetErrorHandler(h ErrorHandler)
SetErrorHandler sets the function that turns a handler's error into a response, for every route, including those registered before. A nil handler restores the default.
func (*Router) SetProblemTypeBase ¶ added in v0.6.0
SetProblemTypeBase sets the base URL of the problem types of the error responses written while the router serves a request: a Problem's type is base joined with the error's code, such as `https://api.example.com/problems/not_found`. An empty base, the default, leaves the type out, which means `about:blank`. A base that is not an absolute URL with a host, or that has a query or a fragment, returns an error wrapping ErrInvalidProblemTypeBase and changes nothing.
func (*Router) Use ¶
func (r *Router) Use(service Service, middleware ...Middleware)
Use applies middleware to every route, registered before or after it, in order, outermost first, owned by service, or by the router when service is nil. When RemoveByService removes the owner, the call's middleware answers 503 in the unified error shape on every route, so no route runs without it, until the owner calls Use again: each of its later calls takes the place of its earliest retired one, so a restarted service's middleware keeps its position and is not applied twice. A successful restart drops the calls its Init no longer makes. A MiddlewareByName whose name is not registered panics with an error wrapping ErrUnknownMiddleware.
func (*Router) UseMiddlewareByName ¶
UseMiddlewareByName is Use with MiddlewareByName(middleware).
type Rows ¶
Rows represents the result set of a query. Mirrors the standard database/sql Rows interface so implementations can wrap it directly.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope is a resolution context for scoped services, as described under Dependency injection in the package documentation. It is safe for concurrent use: goroutines resolving one scoped type share one build.
func RequestScope ¶
RequestScope returns the request's Scope, which an App gives every request. It panics when r has none, such as a request not served by an App's router.
func (*Scope) Close ¶
Close calls Close(ctx) on every scoped Service instance the scope built, in reverse resolution order, so a scoped service can still use the scoped dependencies it was built from, and returns their errors, joined. Resolving a scoped type afterwards fails with ErrScopeClosed. A second Close closes nothing.
func (*Scope) MustResolve ¶ added in v0.5.0
MustResolve is like Resolve but panics if the service cannot be resolved.
func (*Scope) Resolve ¶ added in v0.5.0
Resolve returns the instance registered for T within this scope. A scoped T is built once per Scope and cached for the life of the scope; singletons come from the owning container, and transients are built fresh. Scoped instances that implement Service are closed in reverse resolution order by Scope.Close.
type Secret ¶ added in v0.6.0
type Secret string
Secret is a string that is never printed, marshaled or logged, such as a password, a key or a DSN in a module's settings: String returns `[redacted]`, and Format writes it for every verb, so `%v`, `%+v` and `%#v` of a settings struct hide it; MarshalText returns it, so encoding/json and YAML encoders that use encoding.TextMarshaler write it; and LogValue returns it, so log/slog logs it. GobEncode returns an error, so encoding/gob fails rather than encode the value. Configuration binds into it as into a string, and string(s) is the value.
fmt calls no method on a value it reaches through an unexported struct field, so a Secret in a settings struct held in an unexported field prints as the raw string. Never print a struct that holds settings.
func (Secret) Format ¶ added in v0.6.0
Format writes `[redacted]`, never the value, whatever the verb.
func (Secret) GobEncode ¶ added in v0.6.0
GobEncode returns an error, never the value, so encoding/gob fails rather than encode it.
func (Secret) MarshalText ¶ added in v0.6.0
MarshalText returns `[redacted]`, never the value.
type SecurityHeadersOption ¶
type SecurityHeadersOption func(*securityHeadersOptions)
SecurityHeadersOption configures SecurityHeaders.
func WithSecurityHeadersContentSecurityPolicy ¶
func WithSecurityHeadersContentSecurityPolicy(val string) SecurityHeadersOption
WithSecurityHeadersContentSecurityPolicy sets Content-Security-Policy, whose value depends on the application.
Default: no header.
func WithSecurityHeadersContentTypeOptions ¶
func WithSecurityHeadersContentTypeOptions(val string) SecurityHeadersOption
WithSecurityHeadersContentTypeOptions sets X-Content-Type-Options. An empty value sets no header.
Default: `nosniff`.
func WithSecurityHeadersCrossOriginOpenerPolicy ¶
func WithSecurityHeadersCrossOriginOpenerPolicy(val string) SecurityHeadersOption
WithSecurityHeadersCrossOriginOpenerPolicy sets Cross-Origin-Opener-Policy.
Default: no header.
func WithSecurityHeadersCrossOriginResourcePolicy ¶
func WithSecurityHeadersCrossOriginResourcePolicy(val string) SecurityHeadersOption
WithSecurityHeadersCrossOriginResourcePolicy sets Cross-Origin-Resource-Policy.
Default: no header.
func WithSecurityHeadersFrameOptions ¶
func WithSecurityHeadersFrameOptions(val string) SecurityHeadersOption
WithSecurityHeadersFrameOptions sets X-Frame-Options. An empty value sets no header.
Default: `DENY`.
func WithSecurityHeadersPermissionsPolicy ¶
func WithSecurityHeadersPermissionsPolicy(val string) SecurityHeadersOption
WithSecurityHeadersPermissionsPolicy sets Permissions-Policy. An empty value sets no header.
Default: `camera=(), microphone=(), geolocation=()`.
func WithSecurityHeadersReferrerPolicy ¶
func WithSecurityHeadersReferrerPolicy(val string) SecurityHeadersOption
WithSecurityHeadersReferrerPolicy sets Referrer-Policy. An empty value sets no header.
Default: `strict-origin-when-cross-origin`.
func WithSecurityHeadersStrictTransportSecurity ¶
func WithSecurityHeadersStrictTransportSecurity(val string) SecurityHeadersOption
WithSecurityHeadersStrictTransportSecurity sets Strict-Transport-Security, which belongs only on a site served entirely over HTTPS.
Default: no header.
type ServerSettings ¶
type ServerSettings struct {
// Host is the address the server listens on: an IP address or an RFC
// 1123 hostname, at most 253 bytes. It is checked for shape only, with
// no DNS lookup.
Host string `validate:"required,max=253,ip|hostname_rfc1123"`
// Port is the port the server listens on, 0 to 65535. 0 picks a free
// port.
Port int
// ReadTimeout bounds reading a whole request, body included. 0 is no
// limit, as in net/http, so a server that streams request bodies can
// zero it and keep ReadHeaderTimeout.
ReadTimeout time.Duration
// ReadHeaderTimeout bounds reading a request's headers. It must be
// positive, so no configuration leaves a connection open to a client
// that trickles its headers. net/http bounds a TLS handshake by the
// smallest of ReadHeaderTimeout, ReadTimeout and WriteTimeout that is
// set.
ReadHeaderTimeout time.Duration
// WriteTimeout bounds writing a response, from the end of the request
// headers. 0 is no limit, as in net/http, for streaming responses.
WriteTimeout time.Duration
// IdleTimeout bounds the wait for the next request on a keep-alive
// connection. 0 falls back to ReadTimeout, as in net/http, so it must
// be positive when ReadTimeout is 0, or an idle connection would stay
// open forever.
IdleTimeout time.Duration
// ShutdownTimeout bounds App.Stop, as described under Shutting down in the
// package documentation: Stop returns within ShutdownTimeout plus up to
// three times ShutdownGracePeriod. It must be positive.
ShutdownTimeout time.Duration
// MaxJSONBodyBytes caps how many bytes of the request body
// Context.BindJSON reads; a larger body fails with a 413 *Error. It
// must be positive. It does not apply to handlers that read the body
// themselves; see BodyLimit for that.
MaxJSONBodyBytes int64
// MaxFormBodyBytes caps how many bytes of the request body
// Context.BindForm reads, as MaxJSONBodyBytes does for BindJSON. It
// must be positive.
MaxFormBodyBytes int64
}
ServerSettings holds the HTTP server's settings, as described under Configuration in the package documentation.
type Service ¶
type Service interface {
// Name returns the service's name, such as "gas/auth". Routes,
// middleware and subscriptions are owned by name, so the kill switch
// refuses a service whose name another service shares.
Name() string
// Init initializes the service. Called automatically by the DI container
// after construction. Services register routes, middleware, migrations,
// and event subscriptions here.
//
// ctx carries the service's init deadline. For a singleton it is added to
// the caller's startup context (or RestartService context); for a scoped
// service, to the scope's context. Init must honor ctx: when the deadline
// passes the container cancels ctx and waits for Init to return, so an
// Init that ignores it holds up startup or the resolution. ctx is
// canceled as soon as Init returns, so work that outlives Init, such as a
// background goroutine, must not derive from it.
Init(ctx context.Context) error
// Close gracefully shuts down the service. Called at shutdown
// (singletons), by CloseService, or when a Scope is closed (scoped
// services). ctx bounds the work: Close must return when ctx ends,
// since the shutdown deadline relies on it.
Close(ctx context.Context) error
}
Service is the core interface for lifecycle-managed services. Any type registered with the DI container that implements this interface will have Init(ctx) called after construction and Close(ctx) called at shutdown (singletons) or scope end (scoped). This holds however the value was registered: a pre-built instance passed to WithServiceInstance is initialized too. Transient services cannot implement this interface: registration will be rejected.
Implementing it partially is rejected too. Init and Close are the managed lifecycle, so a type declaring either one must implement all three. A type that declares one and stops is not a Service and would be silently inert: Init never runs, Close never runs, and the kill switch cannot see it. BuildAll returns an error naming the missing or mis-typed methods instead.
Name is not a trigger. Declaring only Name leaves a type an ordinary dependency, and the same registration options carry those (loggers, config providers, per-request values), which must keep working.
A type from another package that declares Close (an io.Closer such as *sql.DB) cannot be given the remaining methods; wrap it in a service of your own rather than registering it directly.
type ServiceContainer ¶
type ServiceContainer struct {
// contains filtered or unexported fields
}
ServiceContainer is a dependency injection container, as described under Dependency injection in the package documentation: it registers constructors and instances, builds them with their dependencies, and caches them by lifetime. It is safe for concurrent use.
func NewServiceContainer ¶
func NewServiceContainer(opts ...ServiceContainerOption) *ServiceContainer
NewServiceContainer returns an empty ServiceContainer with opts applied.
func (*ServiceContainer) BuildAll ¶
func (c *ServiceContainer) BuildAll(ctx context.Context) error
BuildAll initializes pre-registered Service instances, then eagerly resolves all singleton services in dependency order. Transient and scoped services are validated but not constructed.
Implementing Service means the container manages the lifecycle, so a pre-built instance is initialized like any other service. It is initialized before anything the container constructs, matching the order it became available, so reversing that order at shutdown stays correct.
func (*ServiceContainer) CanResolve ¶
func (c *ServiceContainer) CanResolve[T any]() bool
CanResolve reports whether the container has an instance or registration for exactly T, or T is an alias whose target has one, which is when Resolve can find it. An interface T is not resolvable just because a registered type implements it: register the constructor under the interface, or register T as an alias. It answers from the registry alone and builds nothing.
func (*ServiceContainer) EachInstance ¶
func (c *ServiceContainer) EachInstance(fn func(reflect.Value))
EachInstance iterates all built singleton instances (including pre-registered ones) in the order they became available. A dependency is always constructed and cached before the constructor that consumes it returns, so this order is a valid initialization order: reversing it is safe for shutdown.
func (*ServiceContainer) MustResolve ¶ added in v0.4.0
func (c *ServiceContainer) MustResolve[T any]() T
MustResolve is like Resolve but panics if the service cannot be resolved.
func (*ServiceContainer) NewScope ¶
func (c *ServiceContainer) NewScope(ctx context.Context) *Scope
NewScope returns a Scope of the container. Scoped services resolved within it share instances; singletons come from the container. ctx is passed to the Init of every scoped service the scope builds; in an App it is the request's context.
func (*ServiceContainer) RegisterScopedService ¶ added in v0.4.0
func (c *ServiceContainer) RegisterScopedService[T any](ctor any)
RegisterScopedService registers a constructor for type T with the Scoped lifetime, so one T is built per Scope and shared within it. Scoped services cannot be resolved from the container itself; see NewScope. See RegisterService for the accepted signatures.
func (*ServiceContainer) RegisterService ¶ added in v0.4.0
func (c *ServiceContainer) RegisterService[T any](ctor any, lifetime ServiceLifetime)
RegisterService registers a constructor for type T with the given lifetime. The constructor takes its dependencies as parameters and returns T, or T and an error:
func(DepA, DepB, ...) T func(DepA, DepB, ...) (T, error)
Dependencies are resolved from the container by their parameter types, so every one of them must itself be registered by the time the service is built.
Panics if ctor is not a function the container can call: it must be non-variadic, return one or two values, produce a first result assignable to T (or implementing T when T is an interface), and, when it returns two, have error as the second. See validateCtorShape.
Panics if lifetime is Transient and T implements Service, because transient services cannot have managed lifecycles. Use Singleton or Scoped instead.
A type that declares Init or Close but does not fully implement Service is rejected by BuildAll; see validateServiceShape.
Registering a type twice replaces the earlier registration. An instance registered for the same type with RegisterServiceInstance takes precedence over a constructor registration, whichever was registered last, and the constructor never runs.
func (*ServiceContainer) RegisterServiceAlias ¶ added in v0.6.0
func (c *ServiceContainer) RegisterServiceAlias[Target, Alias any]()
RegisterServiceAlias makes the service registered under Target resolvable as Alias too. Resolving Alias resolves Target, so both return the same instance for a singleton or within a scope, Init and Close run once, and the lifetime is Target's:
c.RegisterSingletonService[*SMTPMailer](NewSMTPMailer) c.RegisterServiceAlias[*SMTPMailer, Mailer]()
Target may be registered before or after the alias. BuildAll fails with an error wrapping ErrNotRegistered if it is still missing, and so does a Resolve of Alias. A singleton that depends on Alias is checked for captive dependencies against Target's lifetime.
Panics if Alias is not an interface, Target does not implement it, Alias and Target are the same type, Alias is already registered as a service or instance, Target is itself an alias, or Alias is the target of another alias. Registering the same alias again replaces its target.
func (*ServiceContainer) RegisterServiceInstance ¶ added in v0.4.0
func (c *ServiceContainer) RegisterServiceInstance[T any](val T)
RegisterServiceInstance registers an already-built value under T, the static type at the call site, not the dynamic type of val. Passing an interface-typed variable therefore makes the value resolvable as that interface and not as its concrete type; pass the concrete type, or name T explicitly, to control which one it registers under.
The value is treated as a singleton. The container owns its lifecycle from here on, so do not call Init yourself before registering, or it runs twice.
An instance wins over a constructor registration for the same type, whichever was registered last. Registering another instance for the same type replaces the earlier one.
A nil val, such as a nil interface or a nil pointer, map, channel or function, panics with an error wrapping ErrNilInstance.
func (*ServiceContainer) RegisterSingletonService ¶ added in v0.4.0
func (c *ServiceContainer) RegisterSingletonService[T any](ctor any)
RegisterSingletonService registers a constructor for type T with the Singleton lifetime, so one T is built and shared by every consumer. See RegisterService for the accepted signatures.
func (*ServiceContainer) RegisterTransientService ¶ added in v0.4.0
func (c *ServiceContainer) RegisterTransientService[T any](ctor any)
RegisterTransientService registers a constructor for type T with the Transient lifetime, so a fresh T is built on every resolution. T must not implement Service. See RegisterService for the accepted signatures.
func (*ServiceContainer) Resolve ¶ added in v0.4.0
func (c *ServiceContainer) Resolve[T any]() (T, error)
Resolve returns the instance registered for T, building it and its dependencies on first use and caching it when T is a singleton. Scoped registrations cannot be resolved here and return an error; call NewScope and resolve from the Scope instead.
type ServiceContainerOption ¶ added in v0.5.5
type ServiceContainerOption func(*serviceContainerOptions)
ServiceContainerOption configures a ServiceContainer at construction.
func WithServiceContainerServiceTimeout ¶ added in v0.5.5
func WithServiceContainerServiceTimeout(timeout time.Duration) ServiceContainerOption
WithServiceContainerServiceTimeout sets the deadline the container gives each service's Init, singleton or scoped. 0 disables the deadline, so only the caller's context applies. A service that implements ServiceInitTimeout overrides it for itself. A negative timeout makes BuildAll fail with an error wrapping ErrInvalidOption.
Default: DefaultServiceInitTimeout.
type ServiceInitTimeout ¶ added in v0.5.5
type ServiceInitTimeout interface {
// InitTimeout returns how long the container waits for Init.
InitTimeout() time.Duration
}
ServiceInitTimeout is implemented by a Service that needs a different Init deadline than the container default (see WithServiceInitTimeout). It applies to a singleton at startup and on RestartService, and to a scoped service on its first resolution in each scope. When the deadline passes, Init's context is canceled and, once Init returns, the call fails with an error that wraps context.DeadlineExceeded. If Init ignored the cancellation and returned nil, the service is closed again before the error is returned. 0 means no deadline; a negative value fails Init without running it.
type ServiceLifetime ¶
type ServiceLifetime uint8
ServiceLifetime controls how a service instance is created and cached.
const ( // ServiceLifetimeSingleton services are created once and shared across all consumers. ServiceLifetimeSingleton ServiceLifetime = iota // ServiceLifetimeScoped services are created once per Scope. ServiceLifetimeScoped // ServiceLifetimeTransient services are created fresh on every resolution. ServiceLifetimeTransient )
func (ServiceLifetime) String ¶
func (l ServiceLifetime) String() string
String returns the lowercase name of the lifetime ("singleton", "scoped", "transient"), or "unknown" for a value outside that set.
type SystemAllServicesInitialized ¶
type SystemAllServicesInitialized struct {
Event[SystemAllServicesInitializedPayload]
}
SystemAllServicesInitialized is emitted when all services have been successfully initialized.
type SystemAllServicesInitializedPayload ¶
type SystemAllServicesInitializedPayload struct{}
SystemAllServicesInitializedPayload is an empty payload for the all-services-initialized event.
type SystemServerShuttingDown ¶
type SystemServerShuttingDown struct {
Event[SystemServerShuttingDownPayload]
}
SystemServerShuttingDown is emitted when the HTTP server is shutting down. For code that should run on any shutdown (not just HTTP), subscribe to SystemShuttingDown instead.
type SystemServerShuttingDownPayload ¶
type SystemServerShuttingDownPayload struct{}
SystemServerShuttingDownPayload is an empty payload for the server-shutting-down event.
type SystemServiceClosed ¶
type SystemServiceClosed struct {
Event[SystemServiceClosedPayload]
}
SystemServiceClosed is emitted when a service is closed at runtime.
type SystemServiceClosedPayload ¶
type SystemServiceClosedPayload struct {
ServiceName string
}
SystemServiceClosedPayload carries the name of the closed service.
type SystemServiceInitialized ¶
type SystemServiceInitialized struct {
Event[SystemServiceInitializedPayload]
}
SystemServiceInitialized is emitted when a service is (re-)initialized at runtime.
type SystemServiceInitializedPayload ¶
type SystemServiceInitializedPayload struct {
ServiceName string
}
SystemServiceInitializedPayload carries the name of the initialized service.
type SystemShuttingDown ¶
type SystemShuttingDown struct {
Event[SystemShuttingDownPayload]
}
SystemShuttingDown is emitted when a Worker or App begins its shutdown sequence. It fires for both HTTP (App) and non-HTTP (Worker) workloads.
type SystemShuttingDownPayload ¶
type SystemShuttingDownPayload struct{}
SystemShuttingDownPayload is an empty payload for the shutting-down event.
type TemplateProvider ¶
type TemplateProvider interface {
// Get returns the raw template content by name.
Get(ctx context.Context, name string) ([]byte, error)
// List returns all available template names.
List(ctx context.Context) ([]string, error)
// Register adds or replaces a template by name and raw content.
Register(ctx context.Context, name string, content []byte) error
// RegisterFS walks an fs.FS and registers every template file found.
RegisterFS(ctx context.Context, fsys fs.FS) error
}
TemplateProvider abstracts template storage and retrieval. Implementations may be backed by a filesystem, database, memory, or any combination. Consumers such as the ui module (HTML rendering) and EmailProvider (email templating) resolve raw template content through this interface.
type TemplatedEmail ¶
type TemplatedEmail struct {
Data any
SubjectTemplate string
TextTemplate string
HTMLTemplate string
Email //nolint:embeddedstructfieldcheck // last, as govet's fieldalignment orders it
}
TemplatedEmail is an email message whose subject, text and HTML bodies are rendered from templates with Data.
type TimeoutOption ¶ added in v0.6.0
type TimeoutOption func(*timeoutOptions)
TimeoutOption configures Timeout.
func WithTimeoutMaxBuffer ¶ added in v0.6.0
func WithTimeoutMaxBuffer(n int64) TimeoutOption
WithTimeoutMaxBuffer sets the most bytes of a response Timeout buffers. A value that is not positive makes Timeout panic with an error wrapping ErrInvalidOption.
Default: DefaultTimeoutMaxBuffer.
type Worker ¶
type Worker struct {
// contains filtered or unexported fields
}
Worker runs services without an HTTP server, as described in the package documentation. Use it for non-HTTP environments such as AWS Lambda, background job processors or CLI tools; App embeds one and adds routing and an HTTP server. A Worker runs once: Start, then Shutdown. It is safe for concurrent use.
func NewWorker ¶
NewWorker returns a Worker with opts applied. Only WorkerOption values apply; an AppOption panics with an error wrapping ErrInvalidOption.
func (*Worker) ActiveServices ¶
ActiveServices returns the names of the active services, in the order they were initialized. Two active services with one name both appear.
func (*Worker) CheckHealth ¶
CheckHealth runs CheckHealth concurrently on every active service that implements HealthReporter and returns each result by service name, nil when healthy. Services that do not implement HealthReporter are left out; the results of two services with one name are joined. A reporter that panics reports an error wrapping ErrPanic.
func (*Worker) CheckReady ¶
CheckReady runs CheckReady concurrently on every active service that implements ReadyReporter and returns each result by service name, nil when ready, as CheckHealth does for health.
func (*Worker) CloseService ¶
CloseService runs the kill switch on the service registered under T, as described under Kill switch in the package documentation:
err := w.CloseService[*auth.Service](ctx)
Its routes and named middleware answer 503 and its event subscriptions are removed before its Close runs, so even a Close that fails leaves nothing of it reachable. ctx is passed to Close, and a Close error is returned, wrapped; the service counts as closed either way. SystemServiceClosed is emitted once Close returns.
It returns ErrNotStarted before Start succeeds, ErrShutDown once Shutdown begins, ErrNoBuiltInstance when the Worker started no service under T, ErrDuplicateServiceName when another started service has its name, and ErrServiceNotActive when it is not active. No lock is held while the service's code runs.
func (*Worker) ConfigProvider ¶
func (w *Worker) ConfigProvider() ConfigProvider
ConfigProvider returns the ConfigProvider registered in the container, building it if it is not built yet, or nil when none is registered or it cannot be built; Start reports a build failure.
func (*Worker) MigrationManager ¶
func (w *Worker) MigrationManager() MigrationManager
MigrationManager returns the MigrationManager registered in the container, building it if it is not built yet, or nil when none is registered or it cannot be built; Start reports a build failure.
func (*Worker) ReadyFunc ¶ added in v0.5.0
func (w *Worker) ReadyFunc(fn func(*ServiceContainer) error)
ReadyFunc registers a function that runs once every service is initialized and the migrations are applied, before Start returns. It is the imperative form of WithReadyFunc and may be called any number of times before Start runs the ready hooks; the funcs run in registration order and the first error fails Start.
Safe for concurrent use. It panics with ErrAlreadyStarted once Start has run the ready hooks, because the func would never run.
func (*Worker) RegisterScopedService ¶ added in v0.4.0
RegisterScopedService registers a scoped constructor for type T on the Worker's container; see ServiceContainer.RegisterScopedService.
func (*Worker) RegisterService ¶ added in v0.4.0
func (w *Worker) RegisterService[T any](ctor any, lifetime ServiceLifetime)
RegisterService registers a constructor for type T with the given lifetime on the Worker's container. It is the imperative form of WithService; see ServiceContainer.RegisterService.
func (*Worker) RegisterServiceAlias ¶ added in v0.6.0
RegisterServiceAlias makes the service registered under Target resolvable as Alias on the Worker's container; see ServiceContainer.RegisterServiceAlias.
func (*Worker) RegisterServiceInstance ¶ added in v0.4.0
RegisterServiceInstance registers val under T on the Worker's container; see ServiceContainer.RegisterServiceInstance.
func (*Worker) RegisterSingletonService ¶ added in v0.4.0
RegisterSingletonService registers a singleton constructor for type T on the Worker's container; see ServiceContainer.RegisterSingletonService.
func (*Worker) RegisterTransientService ¶ added in v0.4.0
RegisterTransientService registers a transient constructor for type T on the Worker's container; see ServiceContainer.RegisterTransientService.
func (*Worker) RestartService ¶
RestartService runs Init again on the closed service registered under T, so it registers its routes, middleware and subscriptions anew, as described under Kill switch in the package documentation:
err := w.RestartService[*auth.Service](ctx)
Init runs under the same deadline as at startup, with ctx. When it fails, what it had registered is removed again, the service stays closed, and the error is returned, wrapped. When it returns once Shutdown has begun, the restart closes the service, logs any Close error, and returns an error wrapping ErrShutDown. SystemServiceInitialized is emitted once it succeeds.
It returns the errors CloseService does, with ErrServiceAlreadyActive in place of ErrServiceNotActive, for a service that is not closed.
func (*Worker) Run ¶
Run starts the Worker, blocks until SIGINT, SIGTERM or the end of ctx, and then shuts it down within the shutdown timeout, as described under Running in the package documentation. Start runs under a context that ctx and the first signal both end, so either during startup fails Start through an Init that honors its ctx. Once the first signal arrives the Worker stops handling them, so a second one ends the process. Shutdown gets ctx's values but not its cancellation, under the timeout. Ending ctx is a normal stop: Run then returns Shutdown's error.
func (*Worker) ServiceContainer ¶
func (w *Worker) ServiceContainer() *ServiceContainer
ServiceContainer returns the Worker's dependency injection container.
func (*Worker) Shutdown ¶
Shutdown emits SystemShuttingDown, waits for its subscribers, and closes every active service newest first, passing ctx to each Close, as described under Shutting down in the package documentation. The Logger, when it is a service, is flushed and closed last, after the final log line. A failing Close does not stop the rest; every Close error is returned, joined.
Shutdown runs once. It waits for a Start or a CloseService or RestartService in progress to return first. A later call waits for the first to finish and returns nil. A Shutdown before Start closes nothing and makes Start return ErrShutDown.
func (*Worker) Start ¶
Start initializes every service, runs pending migrations and runs the ready hooks, as described under Starting in the package documentation. It does not block; call Shutdown when done. ctx is passed to the Init of every service built during startup and to the migrations; canceling it aborts startup.
A Worker starts once. A second Start returns ErrAlreadyStarted, a Start once Shutdown has begun returns ErrShutDown, and a Start after a failed one returns that failure again. A Start that fails leaves nothing running: it closes, newest first, every service it had initialized, with ctx stripped of its cancellation, and returns the error joined with any Close errors.
type WorkerOption ¶
type WorkerOption func(*Worker)
WorkerOption configures a Worker. It is the base option type for DI registration, ready hooks, and other non-HTTP concerns. Both NewWorker and NewApp accept WorkerOption values.
func WithReadyFunc ¶
func WithReadyFunc(fn func(*ServiceContainer) error) WorkerOption
WithReadyFunc registers a ready hook, as ReadyFunc does.
func WithScopedService ¶
func WithScopedService[T any](ctor any) WorkerOption
WithScopedService registers a scoped constructor for type T.
func WithService ¶
func WithService[T any](ctor any, lifetime ServiceLifetime) WorkerOption
WithService registers a constructor for type T with the given lifetime, as ServiceContainer.RegisterService does.
func WithServiceAlias ¶ added in v0.6.0
func WithServiceAlias[Target, Alias any]() WorkerOption
WithServiceAlias makes the service registered under Target resolvable as the interface Alias too, sharing Target's instance and lifetime. Target may be registered by an earlier or a later option. See ServiceContainer.RegisterServiceAlias.
func WithServiceInitTimeout ¶ added in v0.5.5
func WithServiceInitTimeout(timeout time.Duration) WorkerOption
WithServiceInitTimeout sets the deadline the Worker's container gives each service's Init: a singleton's at startup and on RestartService, and a scoped service's on its first resolution in a scope. When it passes, Init's context is canceled and startup, or the resolution, fails once Init returns. 0 disables the deadline, so only the caller's context applies. A service that implements ServiceInitTimeout overrides it for itself. A negative timeout makes Start fail with an error wrapping ErrInvalidOption.
Default: DefaultServiceInitTimeout.
func WithServiceInstance ¶
func WithServiceInstance[T any](val T) WorkerOption
WithServiceInstance registers val under T, as ServiceContainer.RegisterServiceInstance does.
Pre-built means pre-constructed, not pre-initialized: if the value implements Service, the container calls Init(ctx) on it during Start like any other service, and Close(ctx) at shutdown. Do not call Init yourself before registering, or it runs twice.
func WithShutdownTimeout ¶ added in v0.6.0
func WithShutdownTimeout(timeout time.Duration) WorkerOption
WithShutdownTimeout sets the deadline of the Shutdown that Worker.Run starts after a signal or the end of its ctx. In an App, Stop and App.Run use the Config's ShutdownTimeout instead, and this option has no effect. A timeout that is not positive makes Start fail with an error wrapping ErrInvalidOption.
Default: DefaultShutdownTimeout.
func WithSingletonService ¶
func WithSingletonService[T any](ctor any) WorkerOption
WithSingletonService registers a singleton constructor for type T.
func WithTransientService ¶
func WithTransientService[T any](ctor any) WorkerOption
WithTransientService registers a transient constructor for type T.
Source Files
¶
- app.go
- config.go
- context.go
- di_errors.go
- di_guard.go
- doc.go
- environment.go
- errors.go
- eventbus.go
- events.go
- handler.go
- log_route_map.go
- logger.go
- middleware.go
- middleware_cache.go
- middleware_limits.go
- middleware_requestlogger.go
- middleware_security.go
- migration.go
- nop_logger.go
- option.go
- principal.go
- providers.go
- request_scope.go
- router.go
- secret.go
- service.go
- service_management.go
- service_registry.go
- slog_logger.go
- validation.go
- worker.go