Documentation
¶
Index ¶
- func AddScoped[T any](c *Container, key serviceKey, factory func(s *Scope) T)
- func AddSingleton[T any](c *Container, key serviceKey, factory func(s *Scope) T)
- func AddTransient[T any](c *Container, key serviceKey, factory func(s *Scope) T)
- func ContextWithScope(ctx context.Context, scope *Scope) context.Context
- func GetService[T any](s *Scope, key serviceKey) T
- func Register[TReq, TRes any](r *Registry, topic Topic, handler Handler[TReq, TRes]) error
- func SetResponseHeader(ctx context.Context, name, value string) (ok bool)
- func TryAddScoped[T any](c *Container, key serviceKey, factory func(s *Scope) T)
- func TryAddSingleton[T any](c *Container, key serviceKey, factory func(s *Scope) T)
- func TryAddTransient[T any](c *Container, key serviceKey, factory func(s *Scope) T)
- func TryGetService[T any](s *Scope, key serviceKey) (T, bool)
- type App
- type ApplicationBuilder
- type Container
- type Handler
- type InvocationContext
- type Middleware
- type Pipeline
- type Registry
- type Result
- func Accepted[T any](payload T) Result[T]
- func BadRequest[T any](errors ...string) Result[T]
- func Conflict[T any](errors ...string) Result[T]
- func Created[T any](payload T) Result[T]
- func Deleted[T any](payload T) Result[T]
- func Fail[T any](status Status, errors ...string) Result[T]
- func Forbidden[T any](errors ...string) Result[T]
- func Ignored[T any](payload T) Result[T]
- func NotFound[T any](errors ...string) Result[T]
- func NotImplemented[T any](errors ...string) Result[T]
- func Ok[T any](payload T) Result[T]
- func ServiceUnavailable[T any](errors ...string) Result[T]
- func SetResult[T any](status Status, payload T, successful bool) Result[T]
- func Timeout[T any](errors ...string) Result[T]
- func TooManyRequests[T any](errors ...string) Result[T]
- func Unauthorized[T any](errors ...string) Result[T]
- func UnexpectedError[T any](errors ...string) Result[T]
- func Updated[T any](payload T) Result[T]
- func ValidationError[T any](errors ...string) Result[T]
- type ResultInfo
- type RouterOption
- type Scope
- type Status
- type Topic
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AddScoped ¶
AddScoped registers factory to be called once per invocation scope; the same instance is reused for the lifetime of that scope, then discarded.
func AddSingleton ¶
AddSingleton registers factory to be called at most once; the same instance is reused for every scope thereafter.
func AddTransient ¶
AddTransient registers factory to be called every time the service is resolved.
func ContextWithScope ¶
ContextWithScope returns a copy of ctx carrying scope, retrievable with ScopeFromContext. core-concepts.md §4 says invocation-scoped facts ride on the context "(or an accessor resolved from the invocation's scope)" - this is that accessor. RouterMiddleware calls this before invoking a handler, so a handler that needs a scoped or transient dependency (a singleton can simply be captured in the handler's closure at registration time) resolves it via ScopeFromContext(ctx) rather than needing Scope added to the Handler signature itself.
func GetService ¶
GetService resolves key, panicking if it has no registration - mirroring the spec's "required" resolution operation, which throws/panics rather than returning a zero value on a missing registration (a missing required dependency is a programming error, not a recoverable runtime condition).
Example ¶
ExampleGetService shows the DI-lite Container/Scope. Register a per-invocation (scoped) dependency under a typed key; a handler then resolves it from the context via ScopeFromContext + GetService. Resolving twice inside one scope returns the same instance. (For a singleton you don't need the container at all - capture it in the handler's closure at registration time.)
package main
import (
"fmt"
benzene "github.com/daniellepelley/benzene-go"
)
type greetingCount struct{ n int }
// countKey is a typed DI key (a struct key can't collide with another package's key the way a bare
// string could).
type countKey struct{}
func main() {
container := benzene.NewContainer()
benzene.AddScoped(container, countKey{}, func(*benzene.Scope) *greetingCount {
return &greetingCount{}
})
scope := container.NewScope() // one scope per invocation; a transport binding creates it for you
first := benzene.GetService[*greetingCount](scope, countKey{})
first.n++
second := benzene.GetService[*greetingCount](scope, countKey{})
fmt.Println(first == second, second.n)
}
Output: true 1
func Register ¶
Register adds handler for topic. Returns an error if topic is already registered - registering two handlers for the same (id, version) pair is a startup error, not a runtime dispatch ambiguity (core-concepts.md §2).
Example ¶
ExampleRegister shows the core loop: a handler is a plain func(context, TReq) Result[TRes]; Register binds it to a topic on a Registry; RouterMiddleware turns the Registry into a Pipeline that dispatches an incoming message to the matching handler. (In a real service a transport binding - httpbinding, awslambda, a queue Consumer - feeds the pipeline; here we drive it directly to show the moving parts.)
package main
import (
"context"
"fmt"
benzene "github.com/daniellepelley/benzene-go"
)
type greetReq struct {
Name string `json:"name"`
}
type greetResp struct {
Greeting string `json:"greeting"`
}
func main() {
registry := benzene.NewRegistry()
if err := benzene.Register(registry, benzene.NewTopic("greet"),
benzene.Handler[greetReq, greetResp](func(_ context.Context, req greetReq) benzene.Result[greetResp] {
return benzene.Ok(greetResp{Greeting: "Hello, " + req.Name + "!"})
})); err != nil {
panic(err)
}
pipeline := benzene.NewPipeline(benzene.RouterMiddleware(registry))
scope := benzene.NewContainer().NewScope()
ic := benzene.NewInvocationContext(benzene.NewTopic("greet"), nil, greetReq{Name: "World"}, scope)
if err := pipeline.Run(context.Background(), ic); err != nil {
panic(err)
}
fmt.Println(ic.Result.ResultStatus())
fmt.Println(ic.Result.ResultPayload().(greetResp).Greeting)
}
Output: ok Hello, World!
func SetResponseHeader ¶
SetResponseHeader records an outbound transport header for the invocation ctx belongs to - the handler-side counterpart of InvocationContext.SetResponseHeader, for handlers (whose signature carries no *InvocationContext). ok = false if ctx carries no invocation (e.g. in a unit test that calls a handler directly) - the header is then dropped, matching how a handler must keep working when a transport has nowhere to put response headers anyway.
func TryAddScoped ¶
TryAddScoped is TryAddSingleton's scoped-lifetime counterpart.
func TryAddSingleton ¶
TryAddSingleton registers factory as a singleton only if key has no registration yet - this is how framework defaults are made overridable (core-concepts.md §8): the framework tryAdds its defaults, and the application's own explicit Add* registration (applied first) wins.
func TryAddTransient ¶
TryAddTransient is TryAddSingleton's transient-lifetime counterpart.
func TryGetService ¶
TryGetService resolves key, returning ok = false if it has no registration instead of panicking.
Types ¶
type App ¶
type App[TConfig any] struct { GetConfiguration func() TConfig ConfigureServices func(registry *Registry, container *Container, config TConfig) Configure func(builder *ApplicationBuilder, config TConfig) }
App is a Benzene application definition: the three-phase lifecycle of core-concepts.md §7, run once, in order, at startup:
- GetConfiguration produces the configuration object. No service resolution is available yet.
- ConfigureServices registers handlers, middleware dependencies, and adapters with the registry/container.
- Configure builds the pipeline(s) against a platform-neutral ApplicationBuilder. Transport-specific entry points are attached by calling a transport binding's own constructor against the returned ApplicationBuilder.
TConfig is application-defined; Benzene itself doesn't prescribe its shape.
func (App[TConfig]) Run ¶
func (a App[TConfig]) Run() *ApplicationBuilder
Run executes the three-phase lifecycle once and returns the built ApplicationBuilder, ready for a transport binding to attach entry points to (e.g. an http.Handler for the HTTP binding). All three phases are optional: GetConfiguration, ConfigureServices, and Configure may each be left nil - an application with no configuration yields the zero value of TConfig, and one with no dependencies to register (or nothing further to configure beyond the defaults) simply skips that phase.
type ApplicationBuilder ¶
type ApplicationBuilder struct {
Registry *Registry
Container *Container
Pipeline *Pipeline
// ReservedNames overrides the reserved metadata/header names (wire-contracts.md §2). It is
// the single injectable value the spec calls for: set it once here and every inbound binding
// built off this builder reads it, so a service renames a colliding key in one place. Its
// zero value means the standard defaults. The same value MUST also be given to the service's
// outbound clients (the queue Client structs' ReservedNames field), since an override applies
// to both directions.
ReservedNames wire.ReservedNames
}
ApplicationBuilder is the platform-neutral application builder handed to App.Configure. A transport binding's `Use<Transport>(builder, ...)`-shaped constructor reads Registry/ Container/Pipeline off it to build that transport's native entry point (an http.Handler, a Lambda handler function, ...) - core-concepts.md §7's "one application definition can target several platforms" rule. Go typically compiles one binary per deployment target rather than runtime-detecting the host, so the "no-op on other platforms" half of that rule mostly falls out for free here; a future binding that DOES need runtime platform detection (e.g. a single binary that can run as either an HTTP server or a Lambda function depending on environment) can still check for its own platform indicators before activating, exactly as any other Go code would.
func (*ApplicationBuilder) UsePipeline ¶
func (b *ApplicationBuilder) UsePipeline(pipeline *Pipeline) *ApplicationBuilder
UsePipeline sets the middleware pipeline transport bindings will run invocations through. Call this from Configure before any binding constructor that needs it. Returns the builder so calls can be chained.
func (*ApplicationBuilder) UseReservedNames ¶
func (b *ApplicationBuilder) UseReservedNames(names wire.ReservedNames) *ApplicationBuilder
UseReservedNames overrides the reserved metadata/header names (wire-contracts.md §2) for every inbound binding built off this builder. Call it from Configure before the binding constructors. Returns the builder so calls can be chained. Remember to pass the same names to the service's outbound clients - an override applies to both directions.
type Container ¶
type Container struct {
// contains filtered or unexported fields
}
Container is the shared registration set an application configures once at startup (core-concepts.md §8): singleton/scoped/transient registrations, by factory. Languages without a DI culture (Go included) MAY implement the container-abstraction concept as an explicit registry/context object rather than a full framework - this Container is that explicit object, not a general-purpose reflection-based DI container.
type Handler ¶
Handler is a function from a request to a result (core-concepts.md §3). The handler never sees the transport - a transport binding maps its native payload to TReq and the Result[TRes] back to a native response.
type InvocationContext ¶
type InvocationContext struct {
Topic Topic
Headers map[string]string
// Request is the native/raw request payload for this invocation (e.g. a JSON body as
// []byte, or an already-typed value for zero-copy passthrough). The router middleware
// converts it into the resolved handler's declared TReq.
Request any
// Result is populated by the router middleware once the handler (or the NotFound /
// error fallback) has run. Middleware registered after the router can inspect or
// replace it; middleware registered before the router runs before it exists.
Result ResultInfo
// Scope is this invocation's per-invocation DI scope (scope.go).
Scope *Scope
// ResponseHeaders holds outbound transport headers set during this invocation - by
// middleware directly, or by a handler via SetResponseHeader(ctx, ...) (the router puts
// this invocation context on the handler's ctx, the same accessor pattern as
// ScopeFromContext). A binding merges these onto its response after dispatch: the wire
// envelope's headers for envelope-shaped transports, real response headers for the native
// HTTP binding. Nil until the first set - fire-and-forget transports never allocate it.
ResponseHeaders map[string]string
}
InvocationContext carries the state of a single pipeline invocation (core-concepts.md §6): the resolved topic, headers, the native/raw request payload (converted to a handler's declared TReq by the terminal router middleware - see convertRequest), and a slot for the result once a handler has run.
Cancellation, deadlines, and other invocation-scoped facts ride on the ctx.Context parameter threaded through Pipeline.Run and every Middleware call, per core-concepts.md §4 ("the pipeline carries no cancellation parameter... rides on the context") - Go's context.Context already carries that here, so nothing extra is needed on InvocationContext itself for that concern.
func NewInvocationContext ¶
func NewInvocationContext(topic Topic, headers map[string]string, request any, scope *Scope) *InvocationContext
NewInvocationContext builds an InvocationContext for one pipeline invocation. headers may be nil, in which case an empty map is used.
func (*InvocationContext) SetResponseHeader ¶
func (ic *InvocationContext) SetResponseHeader(name, value string)
SetResponseHeader records an outbound header on this invocation, to be merged onto the transport response by the binding. Names are lower-cased, matching wire-contracts.md §2's "SHOULD be written lower-case" and the inbound flattening every binding already does; a repeated name overwrites (last write wins).
type Middleware ¶
type Middleware func(ctx context.Context, ic *InvocationContext, next func(context.Context) error) error
Middleware wraps invocation handling in an ordered onion pipeline (core-concepts.md §4). A middleware that does not call next terminates the pipeline; everything after it (including the handler dispatch, if registered later) does not run - this is the mechanism behind features like health-check interception.
Cancellation/deadlines ride on ctx, not on this signature, so the shape is identical across transports that have no cancellation concept at all.
func RouterMiddleware ¶
func RouterMiddleware(registry *Registry, opts ...RouterOption) Middleware
RouterMiddleware returns the terminal middleware that resolves ic.Topic against registry and dispatches to the matching handler, storing the outcome on ic.Result. Conventionally registered last in a Pipeline (core-concepts.md §4).
It reads the message's payload schema version off the wire (wire-contracts.md §2 tier C, versioning.md §2.1) when a binding has not already resolved one: the version travels as a header on every transport (queues, the envelope, and the native HTTP binding's request headers all land it on ic.Headers), so reading it here covers them uniformly. A binding that resolves a version another way - e.g. an HTTP /v{version} route segment setting ic.Topic.Version before the pipeline runs - wins, since a version already on the topic is left untouched.
Handler selection stays exact-match (core-concepts.md §2), with one fallback: a signalled version that has no exact (id, version) handler routes to the unversioned (default-version) handler if one exists. That is core-concepts.md §2's absent-means-default applied to an unmatched version, and the guarantee that turning on the read path stays non-regressive - a stray version header on a service that registered only unversioned handlers still routes to them rather than falling to not-found. versioning.md §3's richer exact-else-highest-supported selection is a deliberate future addition (see the port ROADMAP), not implemented here.
Per core-concepts.md §2/§5, this middleware never returns a Go error for an application- level outcome - a missing topic, a missing handler, a request-conversion failure, or a handler panic all become a Result on ic.Result (ValidationError, NotFound, BadRequest, and ServiceUnavailable respectively), so every caller uniformly reads ic.Result rather than distinguishing "no handler" from "handler ran" via the Go error return. A handler panic specifically MUST NOT crash the transport adapter (§5) - recovered here and mapped to ServiceUnavailable, which wire-contracts.md §3 defines as "also the mapping for uncaught handler exceptions."
Example (Versioned) ¶
ExampleRouterMiddleware_versioned shows inbound handler-version dispatch. Two handlers register for the same topic id under different versions; the router reads the message's benzene-version header off the wire and dispatches to the exact match. A message with no version header routes to the unversioned handler (the default version), and so does one whose version has no exact handler - so turning versioning on for a topic never breaks a producer that doesn't send one.
package main
import (
"context"
"fmt"
benzene "github.com/daniellepelley/benzene-go"
)
type greetReq struct {
Name string `json:"name"`
}
type greetResp struct {
Greeting string `json:"greeting"`
}
func main() {
registry := benzene.NewRegistry()
mustRegister := func(topic benzene.Topic, greeting string) {
if err := benzene.Register(registry, topic,
benzene.Handler[greetReq, greetResp](func(_ context.Context, req greetReq) benzene.Result[greetResp] {
return benzene.Ok(greetResp{Greeting: greeting + req.Name})
})); err != nil {
panic(err)
}
}
mustRegister(benzene.NewTopic("greet"), "Hello ") // the default (unversioned) handler
mustRegister(benzene.NewTopic("greet").WithVersion("2"), "Hi ") // the v2 handler
pipeline := benzene.NewPipeline(benzene.RouterMiddleware(registry))
greet := func(headers map[string]string) string {
ic := benzene.NewInvocationContext(benzene.NewTopic("greet"), headers, greetReq{Name: "World"}, nil)
if err := pipeline.Run(context.Background(), ic); err != nil {
panic(err)
}
return ic.Result.ResultPayload().(greetResp).Greeting
}
fmt.Println(greet(map[string]string{"benzene-version": "2"})) // exact match -> v2
fmt.Println(greet(nil)) // no version -> default
fmt.Println(greet(map[string]string{"benzene-version": "9"})) // unknown version -> default (non-regressive)
}
Output: Hi World Hello World Hello World
type Pipeline ¶
type Pipeline struct {
// contains filtered or unexported fields
}
Pipeline is an ordered list of middleware. The first registered is outermost.
func NewPipeline ¶
func NewPipeline(middlewares ...Middleware) *Pipeline
NewPipeline builds a Pipeline from middlewares in registration order. The terminal message router (see RouterMiddleware) is an ordinary middleware and, per core-concepts.md §4, is conventionally registered last.
func (*Pipeline) Run ¶
func (p *Pipeline) Run(ctx context.Context, ic *InvocationContext) error
Run executes the pipeline exactly once for ic. One transport event (one HTTP request, one queue message, ...) is exactly one Run call, per core-concepts.md §4 - a batch delivery is one Run per message, each with its own InvocationContext/Scope; arranging that is the transport binding's responsibility, not Pipeline's.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds (topic -> handler) registrations.
The concept behind handler discovery is explicit registration (core-concepts.md §9); Register is that explicit path, and is the ONLY mechanism this Go port provides. Go has no reflection-based assembly-scanning culture equivalent to C#'s [Message("topic")] attribute scanning, and core-concepts §9 already requires explicit registration to be a first-class path in every language regardless - so there is nothing to defer to later here, this is simply the Go idiom.
func (*Registry) TopicTypes ¶
TopicTypes returns the request and response types captured when topic's handler was registered (reflect.TypeOf TReq and TRes), or ok = false when topic isn't registered. Startup-time introspection for service self-description - not a dispatch mechanism.
func (*Registry) Topics ¶
Topics returns every registered topic, sorted by ID then Version. This is the enumeration behind service self-description (the mesh package's Descriptor): explicit registration means the Registry is the complete, authoritative list of what this service serves, so a catalog derived from it cannot drift from the running code.
type Result ¶
type Result[T any] struct { Status Status // Payload is present on success (and optionally on failure). It's a pointer so // "absent" is representable without colliding with T's own zero value. Payload *T // Errors holds zero or more human-readable error messages, populated on failure. Errors []string // contains filtered or unexported fields }
Result is the outcome of a single handler invocation (docs/specification/core-concepts.md §5 in the main Benzene repo). Results are values, not exceptions - a transport binding translates a non-success Status into that transport's native failure signal.
Example ¶
ExampleResult shows how a handler signals its outcome with the shared, wire-level status vocabulary rather than a Go error: Ok for success and BadRequest/NotFound/... for the failure modes. Every transport maps the same status the same way (an HTTP code, a gRPC code, a queue ack/nack), so the handler names the outcome once and stays transport-agnostic.
package main
import (
"context"
"fmt"
benzene "github.com/daniellepelley/benzene-go"
)
type greetReq struct {
Name string `json:"name"`
}
type greetResp struct {
Greeting string `json:"greeting"`
}
func main() {
lookup := func(_ context.Context, req greetReq) benzene.Result[greetResp] {
switch req.Name {
case "":
return benzene.BadRequest[greetResp]("name is required")
case "nobody":
return benzene.NotFound[greetResp]("no such user")
default:
return benzene.Ok(greetResp{Greeting: "Hello, " + req.Name + "!"})
}
}
for _, name := range []string{"World", "", "nobody"} {
result := lookup(context.Background(), greetReq{Name: name})
fmt.Printf("%-8q -> %s\n", name, result.ResultStatus())
}
}
Output: "World" -> ok "" -> bad-request "nobody" -> not-found
func BadRequest ¶
BadRequest returns a failed Result with StatusBadRequest.
func Fail ¶
Fail returns a failed Result with the given status and error messages. The result is always unsuccessful - even for an application-defined status that IsFailure does not recognise - which is what makes a custom failure status nack/redeliver on a queue and render its errors rather than being mistaken for a success. Panics if status is in the framework success class, since that would produce a self-contradictory Result.
func Ignored ¶
Ignored returns a successful Result with StatusIgnored - handled deliberately, not an error.
func NotImplemented ¶
NotImplemented returns a failed Result with StatusNotImplemented.
func ServiceUnavailable ¶
ServiceUnavailable returns a failed Result with StatusServiceUnavailable - also the mapping used for uncaught handler panics and client-side send failures.
func SetResult ¶
SetResult builds a Result whose success classification is set explicitly, decoupled from the status class. The intended use is the reserved health check returning StatusServiceUnavailable - so an HTTP probe sees 503 and a load balancer drains the instance - while still rendering its report body (successful=true) rather than an error payload. For ordinary results prefer Ok/Fail and the status-derived default; reach for this only when the transport outcome and the body's meaning genuinely diverge.
func Timeout ¶
Timeout returns a failed Result with StatusTimeout - a downstream deadline elapsed; transient, but whether the operation was applied is unknown, so blind retries are only safe for idempotent operations (unlike StatusServiceUnavailable, WithRetry does not retry this status by default).
func TooManyRequests ¶
TooManyRequests returns a failed Result with StatusTooManyRequests - throttled/rate limited; transient, safe to retry after backing off.
func Unauthorized ¶
Unauthorized returns a failed Result with StatusUnauthorized.
func UnexpectedError ¶
UnexpectedError returns a failed Result with StatusUnexpectedError.
func ValidationError ¶
ValidationError returns a failed Result with StatusValidationError.
func (Result[T]) IsSuccessful ¶
IsSuccessful reports whether this result should be treated as a success. Unless an explicit flag was set via SetResult, it is derived from the status class as "not a failure" (core-concepts.md §5), so a framework success status and an application-defined status both count as successful and carry their payload, while only a framework failure status does not - the extensibility promise that custom statuses flow through untouched (design-principles.md).
func (Result[T]) ResultErrors ¶
func (Result[T]) ResultIsSuccessful ¶
ResultIsSuccessful exposes IsSuccessful on the type-erased ResultInfo path. A transport binding renders the payload vs an error body from the ResultInfo it holds, and this lets an explicit success flag (SetResult) survive type erasure; a binding checks for it via the optional interface { ResultIsSuccessful() bool } and falls back to the status otherwise.
func (Result[T]) ResultPayload ¶
func (Result[T]) ResultStatus ¶
type ResultInfo ¶
type ResultInfo interface {
ResultStatus() Status
ResultErrors() []string
// ResultPayload returns the payload as `any` (nil if absent) for generic serialization.
ResultPayload() any
}
ResultInfo is the type-erased view of a Result[T], implemented by every instantiation. The registry stores handlers behind a non-generic dispatch signature (Go generics can't hold heterogeneous Result[T] instantiations in one collection), so transport bindings and the pipeline recover status/errors/payload through this interface instead of the concrete generic type, which they can't name without knowing T.
type RouterOption ¶
type RouterOption func(*routerConfig)
RouterOption configures RouterMiddleware.
func WithVersionKeys ¶
func WithVersionKeys(keys ...string) RouterOption
WithVersionKeys sets the ordered fallback list of header names the inbound payload schema version is read from (versioning.md §2.1) - first present wins, matched case-insensitively. Pass a service's ApplicationBuilder.ReservedNames.Version() so a reserved-name override made once via UseReservedNames drives routing here too, or a literal list to narrow or replace it (e.g. WithVersionKeys("benzene-version") when a producer already emits a "version" header meaning something unrelated). Unset, RouterMiddleware uses wire.DefaultVersionKeys.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope resolves services for a single invocation. GetService/TryGetService are the only resolution operations (core-concepts.md §8).
type Status ¶
type Status string
Status is a Benzene result status: a wire-level string, not a closed enum, so applications can extend it (docs/specification/wire-contracts.md §3). The values below are the framework-defined vocabulary. Their wire strings are lowercase-kebab-case and case-sensitive (e.g. "not-found", "validation-error") - that casing is the wire contract shared by every Benzene port, so the Go identifier names are PascalCase per Go convention while the string values are held verbatim to the spec (never emit the identifier name as the wire value).
const ( StatusOk Status = "ok" StatusCreated Status = "created" StatusAccepted Status = "accepted" StatusUpdated Status = "updated" StatusDeleted Status = "deleted" StatusIgnored Status = "ignored" StatusBadRequest Status = "bad-request" StatusValidationError Status = "validation-error" StatusForbidden Status = "forbidden" StatusNotFound Status = "not-found" StatusConflict Status = "conflict" StatusTooManyRequests Status = "too-many-requests" StatusTimeout Status = "timeout" StatusNotImplemented Status = "not-implemented" StatusUnexpectedError Status = "unexpected-error" )
The framework-defined status vocabulary (wire-contracts.md §3). The string values are the case-sensitive lowercase-kebab-case wire contract - do not translate them to the Go identifier casing.
func (Status) IsFailure ¶
IsFailure reports whether status is one of the framework-defined failure statuses. It is false for success, unknown (application-defined), and empty statuses - an application-defined status is not assumed to be a failure, which is what keeps custom statuses flowing through the pipeline, envelope, and mesh untouched (design-principles.md).
func (Status) IsKnown ¶
IsKnown reports whether status is part of the framework-defined vocabulary (success or failure).
func (Status) IsSuccess ¶
IsSuccess reports whether status is one of the framework-defined success statuses (StatusOk, StatusCreated, StatusAccepted, StatusUpdated, StatusDeleted, StatusIgnored). It is false for failure, unknown (application-defined), and empty statuses. This is the narrow classifier the per-protocol mapping tables use for their generic-success row; for deciding whether an invocation succeeded, prefer IsFailure/Result.IsSuccessful, which do not treat an application-defined status as a failure (design-principles.md §"custom statuses").
type Topic ¶
Topic identifies a message type and routes it to a handler, per docs/specification/core-concepts.md §2 in the main Benzene repo (the spec this package implements).
A (ID, Version) pair maps to at most one handler. When a message arrives without a version, the unversioned handler (Version == "") handles it; versioned handlers are selected only by an exact match. RouterMiddleware reads an inbound message's version off the wire (wire-contracts.md §2 tier C) and, when a signalled version has no exact handler, falls back to the unversioned one - see its doc.
func (Topic) WithVersion ¶
WithVersion returns a copy of the topic with the given version.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package asyncapi derives an AsyncAPI 3.0 document from a Benzene service's registered topics - the event-driven sibling of the openapi package and the Go form of Benzene.Schema.OpenApi's AsyncAPI half.
|
Package asyncapi derives an AsyncAPI 3.0 document from a Benzene service's registered topics - the event-driven sibling of the openapi package and the Go form of Benzene.Schema.OpenApi's AsyncAPI half. |
|
Package auth is the authentication/authorization building block, matching Benzene.Auth.Core (+ Benzene.Auth.Basic and Benzene.Auth.OAuth2).
|
Package auth is the authentication/authorization building block, matching Benzene.Auth.Core (+ Benzene.Auth.Basic and Benzene.Auth.OAuth2). |
|
Package awsdynamodb is the DynamoDB Streams inbound binding: a Lambda function triggered by a DynamoDB stream event source mapping.
|
Package awsdynamodb is the DynamoDB Streams inbound binding: a Lambda function triggered by a DynamoDB stream event source mapping. |
|
awseventbridge
module
|
|
|
Package awskafka is the AWS Lambda Kafka inbound binding: a Lambda function triggered by an Amazon MSK or self-managed-Kafka event source mapping.
|
Package awskafka is the AWS Lambda Kafka inbound binding: a Lambda function triggered by an Amazon MSK or self-managed-Kafka event source mapping. |
|
Package awskinesis is the Kinesis Data Streams inbound binding: a Lambda function triggered by a Kinesis stream event source mapping.
|
Package awskinesis is the Kinesis Data Streams inbound binding: a Lambda function triggered by a Kinesis stream event source mapping. |
|
Package awslambda deploys a Benzene application to AWS Lambda.
|
Package awslambda deploys a Benzene application to AWS Lambda. |
|
awslambdaclient
module
|
|
|
Package awss3 is the S3 event-notification inbound binding: a Lambda function invoked by S3 when an object is created, removed, etc.
|
Package awss3 is the S3 event-notification inbound binding: a Lambda function invoked by S3 when an object is created, removed, etc. |
|
awssns
module
|
|
|
awssqs
module
|
|
|
Package azurefunctions is the Azure Functions custom-handler binding (https://learn.microsoft.com/azure/azure-functions/functions-custom-handlers): Azure has no native Go worker, so a Go function ships as a plain HTTP server that the Functions host forwards each invocation to, over a small JSON envelope (Data/Metadata in, Outputs/ReturnValue out) - a "raw HTTP request/response" contract in spirit, close enough to transport-bindings.md's HTTP binding entry that Handler here mirrors httpbinding.Handler's shape (an explicit Route table, real HTTP status codes) rather than inventing a new one.
|
Package azurefunctions is the Azure Functions custom-handler binding (https://learn.microsoft.com/azure/azure-functions/functions-custom-handlers): Azure has no native Go worker, so a Go function ships as a plain HTTP server that the Functions host forwards each invocation to, over a small JSON envelope (Data/Metadata in, Outputs/ReturnValue out) - a "raw HTTP request/response" contract in spirit, close enough to transport-bindings.md's HTTP binding entry that Handler here mirrors httpbinding.Handler's shape (an explicit Route table, real HTTP status codes) rather than inventing a new one. |
|
Package benzenetest is an in-process test host for applications built on benzene-go - the Go counterpart to the main daniellepelley/Benzene repo's Benzene.Testing / BenzeneTestHost.
|
Package benzenetest is an in-process test host for applications built on benzene-go - the Go counterpart to the main daniellepelley/Benzene repo's Benzene.Testing / BenzeneTestHost. |
|
Package cache is the caching building block, matching the essence of Benzene.Cache.Core: a pluggable Store plus a read-through (cache-aside) helper a handler calls around an expensive read.
|
Package cache is the caching building block, matching the essence of Benzene.Cache.Core: a pluggable Store plus a read-through (cache-aside) helper a handler calls around an expensive read. |
|
Package client provides the outbound-client decorators of daniellepelley/Benzene's docs/specification/transport-bindings.md §2: "Cross-cutting client behaviors (correlation ID injection, trace context, retry) are decorators over the same interface and therefore transport-agnostic." Sender is that one interface; WithCorrelationID and WithRetry are decorators over it - each wraps a Sender and returns another Sender, so they compose freely and work over any transport's outbound client (httpclient.Client already satisfies Sender structurally, with no changes needed there).
|
Package client provides the outbound-client decorators of daniellepelley/Benzene's docs/specification/transport-bindings.md §2: "Cross-cutting client behaviors (correlation ID injection, trace context, retry) are decorators over the same interface and therefore transport-agnostic." Sender is that one interface; WithCorrelationID and WithRetry are decorators over it - each wraps a Sender and returns another Sender, so they compose freely and work over any transport's outbound client (httpclient.Client already satisfies Sender structurally, with no changes needed there). |
|
Package clienthealthcheck is the consumer-side dependency health check, matching Benzene.Clients.HealthChecks.
|
Package clienthealthcheck is the consumer-side dependency health check, matching Benzene.Clients.HealthChecks. |
|
Package cloudevents maps the Benzene wire envelope onto CloudEvents 1.0 (https://github.com/cloudevents/spec) - the CNCF-graduated cross-cloud event format that AWS EventBridge, Azure Event Grid, Knative, and most modern event routers can emit or carry.
|
Package cloudevents maps the Benzene wire envelope onto CloudEvents 1.0 (https://github.com/cloudevents/spec) - the CNCF-graduated cross-cloud event format that AWS EventBridge, Azure Event Grid, Knative, and most modern event routers can emit or carry. |
|
Package cloudservice assembles a Benzene Cloud Service (docs/specification/cloud-service-profile.md) from a registry in one call, wiring the reserved /benzene/* HTTP surface and reporting which profile surfaces the wiring provides.
|
Package cloudservice assembles a Benzene Cloud Service (docs/specification/cloud-service-profile.md) from a registry in one call, wiring the reserved /benzene/* HTTP surface and reporting which profile surfaces the wiring provides. |
|
Package cloudserviceprobe is the external, black-box conformance checker for the Benzene Cloud Service Profile (docs/specification/cloud-service-profile.md §2, §5).
|
Package cloudserviceprobe is the external, black-box conformance checker for the Benzene Cloud Service Profile (docs/specification/cloud-service-profile.md §2, §5). |
|
Package cors is a portable, stdlib-only Cross-Origin Resource Sharing middleware for HTTP-fronted Benzene services - a Go port of the main daniellepelley/Benzene repo's own portable CORS middleware (src/Benzene.Http/Cors).
|
Package cors is a portable, stdlib-only Cross-Origin Resource Sharing middleware for HTTP-fronted Benzene services - a Go port of the main daniellepelley/Benzene repo's own portable CORS middleware (src/Benzene.Http/Cors). |
|
Package envelope dispatches a wire.Request through a benzene.Pipeline and produces a wire.Response - the shared glue transport-bindings.md calls "the raw BenzeneMessage envelope for direct invocation": used directly by any binding with no richer native contract (queues without attribute support, direct function invocation), and reused here by the conformance runner (which only needs to prove pipeline/status-mapping behavior, not a real network round-trip) and by httpbinding's EnvelopeHandler (which exposes it over HTTP for cross-service interop).
|
Package envelope dispatches a wire.Request through a benzene.Pipeline and produces a wire.Response - the shared glue transport-bindings.md calls "the raw BenzeneMessage envelope for direct invocation": used directly by any binding with no richer native contract (queues without attribute support, direct function invocation), and reused here by the conformance runner (which only needs to prove pipeline/status-mapping behavior, not a real network round-trip) and by httpbinding's EnvelopeHandler (which exposes it over HTTP for cross-service interop). |
|
examples
|
|
|
aws-dynamodb-helloworld
command
Command aws-dynamodb-helloworld is a DynamoDB Streams consumer Lambda: it reacts to writes on an `orders` table by handling the change records the stream delivers, one Benzene topic per change type ("orders:INSERT", "orders:MODIFY", "orders:REMOVE").
|
Command aws-dynamodb-helloworld is a DynamoDB Streams consumer Lambda: it reacts to writes on an `orders` table by handling the change records the stream delivers, one Benzene topic per change type ("orders:INSERT", "orders:MODIFY", "orders:REMOVE"). |
|
aws-kafka-helloworld
command
Command aws-kafka-helloworld is a Kafka consumer Lambda: it reacts to records on an `orders` Kafka topic by handling each one.
|
Command aws-kafka-helloworld is a Kafka consumer Lambda: it reacts to records on an `orders` Kafka topic by handling each one. |
|
aws-kinesis-helloworld
command
Command aws-kinesis-helloworld is a Kinesis Data Streams consumer Lambda: it reacts to records on an `orders` stream by handling each one.
|
Command aws-kinesis-helloworld is a Kinesis Data Streams consumer Lambda: it reacts to records on an `orders` stream by handling each one. |
|
aws-lambda-helloworld
command
Command aws-lambda-helloworld is the helloworld service deployed to AWS Lambda: the same greet handler, wired through awslambda instead of net/http.
|
Command aws-lambda-helloworld is the helloworld service deployed to AWS Lambda: the same greet handler, wired through awslambda instead of net/http. |
|
aws-s3-helloworld
command
Command aws-s3-helloworld is an S3 event-notification consumer Lambda: S3 invokes it whenever an object is created in an `uploads` bucket, and it handles the notification.
|
Command aws-s3-helloworld is an S3 event-notification consumer Lambda: S3 invokes it whenever an object is created in an `uploads` bucket, and it handles the notification. |
|
azure-functions-helloworld
command
Command azure-functions-helloworld is the helloworld greet handler deployed as an Azure Functions custom handler: a plain HTTP server the Functions host forwards invocations to.
|
Command azure-functions-helloworld is the helloworld greet handler deployed as an Azure Functions custom handler: a plain HTTP server the Functions host forwards invocations to. |
|
codegen-helloworld
command
Command codegen-helloworld (see main.go) dogfoods the client generator in the sibling `codegen` Go module (`codegen/cmd/benzene-codegen`) against a committed Contract Document (contracts/payments.spec.json - vendored verbatim from the .NET reference's Benzene.Descriptor-emitted example, examples/AwsMesh/Orders/contracts/payments.spec.json in daniellepelley/benzene-dotnet) - see docs/codegen-client.md for the full generator guide.
|
Command codegen-helloworld (see main.go) dogfoods the client generator in the sibling `codegen` Go module (`codegen/cmd/benzene-codegen`) against a committed Contract Document (contracts/payments.spec.json - vendored verbatim from the .NET reference's Benzene.Descriptor-emitted example, examples/AwsMesh/Orders/contracts/payments.spec.json in daniellepelley/benzene-dotnet) - see docs/codegen-client.md for the full generator guide. |
|
gcp-cloudrun-helloworld
command
Command gcp-cloudrun-helloworld is the helloworld greet handler deployed to Google Cloud Run.
|
Command gcp-cloudrun-helloworld is the helloworld greet handler deployed to Google Cloud Run. |
|
gcp-pubsub-helloworld
command
Command gcp-pubsub-helloworld is the helloworld greet handler consuming a Google Cloud Pub/Sub push subscription, deployed as a Cloud Run service.
|
Command gcp-pubsub-helloworld is the helloworld greet handler consuming a Google Cloud Pub/Sub push subscription, deployed as a Cloud Run service. |
|
helloworld
command
Command helloworld is a minimal end-to-end Benzene service: one handler behind a port interface (the hexagonal-architecture shape this whole project is named for), a health check, and both of the httpbinding package's HTTP entry points, wired through the three-phase App lifecycle of core-concepts.md §7.
|
Command helloworld is a minimal end-to-end Benzene service: one handler behind a port interface (the hexagonal-architecture shape this whole project is named for), a health check, and both of the httpbinding package's HTTP entry points, wired through the three-phase App lifecycle of core-concepts.md §7. |
|
http-helloworld
command
Command http-helloworld hosts the greet handler on a standalone net/http server via the httpbinding package - the Go counterpart of the .NET repo's examples/Asp (hosting a Benzene service on the framework's own web server).
|
Command http-helloworld hosts the greet handler on a standalone net/http server via the httpbinding package - the Go counterpart of the .NET repo's examples/Asp (hosting a Benzene service on the framework's own web server). |
|
k8s-mesh-helloworld/cmd/mesh
command
Command k8s-mesh-collector is the mesh service of the k8s-mesh-helloworld example: a thin wrapper around meshd.Collector, the Go counterpart of benzene-dotnet's examples/K8sMesh/Mesh — with one deliberate, documented divergence.
|
Command k8s-mesh-collector is the mesh service of the k8s-mesh-helloworld example: a thin wrapper around meshd.Collector, the Go counterpart of benzene-dotnet's examples/K8sMesh/Mesh — with one deliberate, documented divergence. |
|
k8s-mesh-helloworld/cmd/service
command
Command k8s-mesh-service is one of three domain services — orders, payments, shipping — selected at startup by the MESH_SERVICE env var: the Go counterpart of benzene-dotnet's examples/K8sMesh/Service.
|
Command k8s-mesh-service is one of three domain services — orders, payments, shipping — selected at startup by the MESH_SERVICE env var: the Go counterpart of benzene-dotnet's examples/K8sMesh/Service. |
|
k8s-mesh-helloworld/domain
Package domain holds the three tiny domain handlers the k8s-mesh-helloworld example deploys as one shared binary: orders, payments, shipping — the Go counterpart of benzene-dotnet's examples/K8sMesh/Service/Domain.cs.
|
Package domain holds the three tiny domain handlers the k8s-mesh-helloworld example deploys as one shared binary: orders, payments, shipping — the Go counterpart of benzene-dotnet's examples/K8sMesh/Service/Domain.cs. |
|
mesh-helloworld
command
Command mesh-helloworld runs the whole Benzene Mesh story (docs/design/mesh.md and the promoted spec, docs/specification/mesh.md in the main repo) in one process: a meshd collector and three services demonstrating every mesh feature.
|
Command mesh-helloworld runs the whole Benzene Mesh story (docs/design/mesh.md and the promoted spec, docs/specification/mesh.md in the main repo) in one process: a meshd collector and three services demonstrating every mesh feature. |
|
Package gcppubsub is the inbound half of the Google Cloud Pub/Sub binding: an HTTP handler for a push subscription (https://cloud.google.com/pubsub/docs/push), typically mounted on a Cloud Run service.
|
Package gcppubsub is the inbound half of the Google Cloud Pub/Sub binding: an HTTP handler for a push subscription (https://cloud.google.com/pubsub/docs/push), typically mounted on a Cloud Run service. |
|
Package grpcstatus implements the Benzene<->gRPC status mapping tables of daniellepelley/Benzene's docs/specification/wire-contracts.md §4.2.
|
Package grpcstatus implements the Benzene<->gRPC status mapping tables of daniellepelley/Benzene's docs/specification/wire-contracts.md §4.2. |
|
Package healthcheck implements the health-check interception feature of daniellepelley/Benzene's docs/specification/core-concepts.md §5 ("intercept the reserved benzene:healthcheck topic (plus an app-chosen alias), run registered checks, respond with the standard response format") and the response shape of wire-contracts.md §5.
|
Package healthcheck implements the health-check interception feature of daniellepelley/Benzene's docs/specification/core-concepts.md §5 ("intercept the reserved benzene:healthcheck topic (plus an app-chosen alias), run registered checks, respond with the standard response format") and the response shape of wire-contracts.md §5. |
|
Package httpbinding is the HTTP transport binding described by daniellepelley/Benzene's docs/specification/transport-bindings.md §2 ("HTTP (ASP.NET Core)" entry, ported to Go's net/http): topic resolved from route/method conventions, headers both directions, status via httpstatus's wire-contracts.md §4.1 table, one DI scope per request, cancellation from the request's context.
|
Package httpbinding is the HTTP transport binding described by daniellepelley/Benzene's docs/specification/transport-bindings.md §2 ("HTTP (ASP.NET Core)" entry, ported to Go's net/http): topic resolved from route/method conventions, headers both directions, status via httpstatus's wire-contracts.md §4.1 table, one DI scope per request, cancellation from the request's context. |
|
Package httpclient is the HTTP outbound client of daniellepelley/Benzene's docs/specification/transport-bindings.md §2 ("Outbound clients"): one interface - sendMessage(topic, headers, message) -> result - over HTTP, talking the wire-contracts.md envelope to a target service's envelope endpoint (e.g.
|
Package httpclient is the HTTP outbound client of daniellepelley/Benzene's docs/specification/transport-bindings.md §2 ("Outbound clients"): one interface - sendMessage(topic, headers, message) -> result - over HTTP, talking the wire-contracts.md envelope to a target service's envelope endpoint (e.g. |
|
Package httpstatus implements the Benzene<->HTTP status mapping tables of daniellepelley/Benzene's docs/specification/wire-contracts.md §4.1.
|
Package httpstatus implements the Benzene<->HTTP status mapping tables of daniellepelley/Benzene's docs/specification/wire-contracts.md §4.1. |
|
Package idempotency de-duplicates redelivered messages on an at-least-once transport.
|
Package idempotency de-duplicates redelivered messages on an at-least-once transport. |
|
Package inprocess dispatches an outbound send straight to a handler pipeline built in the same runtime, in the shared []byte/json.RawMessage envelope every client.Sender uses, without going over any wire (no SQS/SNS/HTTP/socket - not even loopback).
|
Package inprocess dispatches an outbound send straight to a handler pipeline built in the same runtime, in the shared []byte/json.RawMessage envelope every client.Sender uses, without going over any wire (no SQS/SNS/HTTP/socket - not even loopback). |
|
Package logging is the basic request logging/timing middleware ROADMAP.md's "zero new dependencies" list describes: one structured log line per pipeline invocation, using only the standard library's log/slog.
|
Package logging is the basic request logging/timing middleware ROADMAP.md's "zero new dependencies" list describes: one structured log line per pipeline invocation, using only the standard library's log/slog. |
|
Package mesh implements the Benzene Mesh design (the main repo's docs/specification/mesh.md, originally extracted from this package's earlier docs/design/mesh.md): a service's self-description (Descriptor) derived from its live Registry (what it provides, §2) and its live OutboundRegistry (what it consumes, §2.3) - including per-topic request/response JSON Schemas derived at startup from the registered types, and the contract hash that makes drift detectable (schema.go) - a reserved-topic interception middleware that serves that descriptor, and a trace middleware (trace.go) that turns every pipeline invocation into a semantic TraceEvent handed to an Exporter - either the zero-setup LogExporter (exporter.go) or the batching PushExporter (push.go) that feeds a collector over the mesh:* wire topics (wire.go), with span propagation for cross-service trace joins (span.go).
|
Package mesh implements the Benzene Mesh design (the main repo's docs/specification/mesh.md, originally extracted from this package's earlier docs/design/mesh.md): a service's self-description (Descriptor) derived from its live Registry (what it provides, §2) and its live OutboundRegistry (what it consumes, §2.3) - including per-topic request/response JSON Schemas derived at startup from the registered types, and the contract hash that makes drift detectable (schema.go) - a reserved-topic interception middleware that serves that descriptor, and a trace middleware (trace.go) that turns every pipeline invocation into a semantic TraceEvent handed to an Exporter - either the zero-setup LogExporter (exporter.go) or the batching PushExporter (push.go) that feeds a collector over the mesh:* wire topics (wire.go), with span propagation for cross-service trace joins (span.go). |
|
Package meshd implements the Benzene Mesh collector (originally Phases 3-4 of this repo's own docs/design/mesh.md, now the main repo's docs/specification/mesh.md §§4-6).
|
Package meshd implements the Benzene Mesh collector (originally Phases 3-4 of this repo's own docs/design/mesh.md, now the main repo's docs/specification/mesh.md §§4-6). |
|
Package openapi derives an OpenAPI 3.0 document from a Benzene service's registered topics - the Go form of Benzene.Schema.OpenApi.
|
Package openapi derives an OpenAPI 3.0 document from a Benzene service's registered topics - the Go form of Benzene.Schema.OpenApi. |
|
Package ratelimiting is a best-effort, per-instance rate-limiting middleware: each message tries to acquire its permit cost from a Limiter without queuing, and a message the limiter rejects is short-circuited with a too-many-requests result (HTTP 429 via the standard status mapping) before the handler runs.
|
Package ratelimiting is a best-effort, per-instance rate-limiting middleware: each message tries to acquire its permit cost from a Limiter without queuing, and a message the limiter rejects is short-circuited with a too-many-requests result (HTTP 429 via the standard status mapping) before the handler runs. |
|
Package resilience provides resilience middleware for the Benzene pipeline that needs no third-party library:
|
Package resilience provides resilience middleware for the Benzene pipeline that needs no third-party library: |
|
Package responseevents republishes a handler's response payload as a follow-up event on a fire-and-forget transport - the *response-as-event* pattern, matching Benzene.ResponseEvents.
|
Package responseevents republishes a handler's response payload as a follow-up event on a fire-and-forget transport - the *response-as-event* pattern, matching Benzene.ResponseEvents. |
|
Package saga is an in-code saga orchestrator for a distributed transaction: an ordered list of stages, each a group of steps run concurrently, that either completes in full or rolls back in full - leaving no orphaned records, so the whole operation can be safely retried.
|
Package saga is an in-code saga orchestrator for a distributed transaction: an ordered list of stages, each a group of steps run concurrently, that either completes in full or rolls back in full - leaving no orphaned records, so the whole operation can be safely retried. |
|
Package validation is the request-validation building block: a typed wrapper that runs a validator before a handler and short-circuits with a validation-error result when the request is invalid, so the handler only ever sees a valid request.
|
Package validation is the request-validation building block: a typed wrapper that runs a validator before a handler and short-circuits with a validation-error result when the request is invalid, so the handler only ever sees a valid request. |
|
Package wire implements the transport-neutral message envelope and status vocabulary defined in daniellepelley/Benzene's docs/specification/wire-contracts.md.
|
Package wire implements the transport-neutral message envelope and status vocabulary defined in daniellepelley/Benzene's docs/specification/wire-contracts.md. |