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 MustRegister[TReq, TRes any](r *Registry, topic Topic, handler Handler[TReq, TRes])
- 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 Error
- type Handler
- type InvocationContext
- type Middleware
- type Pipeline
- type Problem
- type ProblemDocumentInfo
- type ProblemInfo
- 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 FailWith[T any](status Status, errors ...Error) 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 ProblemResult[T any](problem Problem) 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]
- func ValidationErrorWith[T any](errors ...Error) Result[T]
- func (r Result[T]) IsSuccessful() bool
- func (r Result[T]) ResultErrors() []string
- func (r Result[T]) ResultIsSuccessful() bool
- func (r Result[T]) ResultPayload() any
- func (r Result[T]) ResultProblemDocument() *Problem
- func (r Result[T]) ResultProblems() []Error
- func (r Result[T]) ResultStatus() Status
- 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 MustRegister ¶
MustRegister is Register with the error handling a composition root would otherwise write by hand: it calls Register and panics if registration fails. The explicit form is Register - reach for it when the caller has somewhere better to send the error than a panic (a registration loop that collects failures, a plugin host, a test).
The panic is a start-up check, not a runtime hazard: the only way Register fails is a duplicate (id, version) topic, which is a wiring mistake, and ConfigureServices runs once at boot before any message is handled (core-concepts.md §7). The panic therefore names the offending topic and happens at start-up, never on the message path - the same trade regexp.MustCompile and template.Must make.
Explicit:
if err := benzene.Register(registry, benzene.NewTopic("greet"), greetHandler); err != nil {
return err
}
Shorthand:
benzene.MustRegister(registry, benzene.NewTopic("greet"), greetHandler)
In both forms TReq/TRes are inferred from handler's signature; the explicit benzene.Handler[TReq, TRes](fn) conversion is never required for a function that already has the handler shape.
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.
Pipeline default: if Configure left no pipeline on the builder - it was nil, or it registered services and routes but never called UsePipeline - Run installs the default pipeline before returning. The default is exactly UseDefaultPipeline, i.e. NewPipeline(RouterMiddleware(builder.Registry)): route every message to its registered handler, and do nothing else. Any UsePipeline call in Configure wins, so declining the steer costs one line and never costs the layers below it (design-principles.md §1). Run applies it at start-up, so a service that never states a pipeline routes from its first message rather than discovering the omission on the message path.
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) UseDefaultPipeline ¶
func (b *ApplicationBuilder) UseDefaultPipeline() *ApplicationBuilder
UseDefaultPipeline sets the pipeline a service with no cross-cutting concerns of its own wants: the terminal message router alone, so every registered handler is reachable and nothing else runs. It is composed from the public explicit form and is exactly equivalent to writing that form yourself:
builder.UsePipeline(benzene.NewPipeline(benzene.RouterMiddleware(builder.Registry)))
Drop to that line the moment the service needs a second middleware - health-check interception, auth, idempotency, resilience - since the router is conventionally registered last (core-concepts.md §4) and everything else goes in front of it:
builder.UsePipeline(benzene.NewPipeline( healthcheck.Middleware(checks), benzene.RouterMiddleware(builder.Registry), ))
App.Run calls this for you when Configure left the pipeline unset, so the common case needs no Configure phase at all; call it explicitly when you want the default stated in the composition root rather than implied. Returns the builder so calls can be chained.
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 Error ¶
type Error = wire.ProblemError
Error is one structured error on a failed Result (wire-contracts.md §1.3): Message is the only required member, and Field and Code are the producer's own property path and machine-readable rule identifier, emitted verbatim - the framework never normalizes or rewords them.
A schema validator that knows a message, the field it came from and the rule that rejected it can say all three, and the problem document carries them through to the caller. Without that a consumer gets prose it has to parse, which is the difference between an error a UI can attach to an input and an error it can only print.
It is an alias, not a copy, of wire.ProblemError: the value a handler builds IS the value that reaches the wire, so there is no second shape to keep in step and no conversion that can quietly drop a member. benzene.Error is simply the name to use from application code, where importing a package called "wire" to describe a validation failure would read oddly.
func ProblemsOf ¶
func ProblemsOf(result ResultInfo) []Error
ProblemsOf returns the structured errors of a type-erased result: the ProblemInfo view when the implementation offers one, and its messages wrapped as Message-only errors when it does not.
Every place that rebuilds a typed Result from a ResultInfo - the in-process client, the test host, each outbound client - needs exactly this, and each of them writing its own type assertion is how one of them ends up quietly flattening field and code back to prose.
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.
A nil *Pipeline is a wiring mistake, not a runtime condition, so Run reports it as an error naming the fix rather than dereferencing nil - a binding handed a builder with no pipeline would otherwise crash the transport on its first message, which the bindings' "never let a panic reach the caller" rule forbids. App.Run cannot produce this (it installs the default pipeline when Configure sets none); a hand-built ApplicationBuilder can.
type Problem ¶
type Problem = wire.ErrorPayload
Problem is an application-authored problem document (wire-contracts.md §1.3) - the escape hatch for a service that owns its own problem vocabulary and wants its own `type` URI on the wire instead of the registry URI Benzene would derive from the status. Build one and hand it to ProblemResult.
Also an alias of the wire type, for the same reason as Error. Note that Status (the HTTP status number) is not something an application authors: an HTTP binding sets it to the code it is actually sending, and it is absent on every other transport, so leave it nil.
type ProblemDocumentInfo ¶
type ProblemDocumentInfo interface {
ResultProblemDocument() *Problem
}
ProblemDocumentInfo is the optional interface a binding checks for an application-authored problem document (see ProblemResult). A binding that finds one must emit it as-is; one that does not, or that finds nil, derives the document from the status as usual.
type ProblemInfo ¶
type ProblemInfo interface {
ResultProblems() []Error
}
ProblemInfo is the optional interface a binding checks for structured errors, alongside the ResultInfo it already holds. Optional, and checked with a type assertion, for the same reason ResultIsSuccessful is: adding a method to ResultInfo itself would break every external implementation of it, and a binding that only renders messages needs nothing new.
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 structured errors, populated on failure. The zero-ceremony // constructors (Fail, ValidationError, ...) take plain strings and fill in Message only; // reach for FailWith/ValidationErrorWith when the producer knows a Field or a Code. Errors []Error // 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 FailWith ¶
FailWith is Fail with structured errors: the same status rules, but each error may carry the Field it came from and the Code of the rule that rejected it, and both travel all the way to the caller's problem document. Prefer plain Fail when there is nothing to add beyond the message.
func Ignored ¶
Ignored returns a successful Result with StatusIgnored - handled deliberately, not an error.
func NotImplemented ¶
NotImplemented returns a failed Result with StatusNotImplemented.
func ProblemResult ¶
ProblemResult returns a failed Result carrying an application-authored problem document, which the wire edge emits verbatim rather than deriving one from the status. Use it when the service owns its own problem vocabulary and wants its own `type` URI to reach the caller; for everything else Fail and FailWith derive the right document from the §3.1 registry.
Panics if problem.BenzeneStatus is empty: a problem document with no status cannot be classified by anything downstream, so there is no sensible result to build from it.
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 ValidationErrorWith ¶
ValidationErrorWith returns a failed Result with StatusValidationError and structured errors. Validation is where Field and Code are nearly always known - a schema validator produces exactly this shape - so it gets the shorthand; any other status goes through FailWith.
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 ¶
ResultErrors returns the error messages, flattening the structured errors. The type-erased interface deliberately keeps returning []string: every binding that only ever wanted messages is unaffected by structured errors existing, and the ones that want more ask via ProblemInfo.
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]) ResultProblemDocument ¶
ResultProblemDocument returns the application-authored problem document, or nil when the result carries none and the wire edge should derive one from the status. See ProblemDocumentInfo.
func (Result[T]) ResultProblems ¶
ResultProblems exposes the structured errors on the type-erased path, so a binding building a problem document (wire-contracts.md §1.3) can carry field and code through instead of flattening to prose. See ProblemInfo.
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 (work/archive/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 (work/archive/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 work/archive/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 work/archive/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 work/archive/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 work/archive/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. |