Documentation
¶
Index ¶
- func LogicFunc[request, response any](fn func(context.Context, request) (response, error)) func(struct{}) funcImpl[request, response]
- func MustValidate(r *grr.Registry)
- func RegisterStateless[request, response any, impl Logical[request, response]](l *Logic[request, response, struct{}, impl])
- func RegisterStatelessIn[request, response any, impl Logical[request, response]](r *grr.Registry, l *Logic[request, response, struct{}, impl])
- func Validate(r *grr.Registry) error
- func ValidateLogics(r *grr.Registry, names ...string) error
- type Logic
- func (l *Logic[request, response, model, impl]) Do(ctx context.Context, req request) (response, error)
- func (l *Logic[request, response, model, impl]) IsRegistered(ctx context.Context) bool
- func (l *Logic[request, response, model, impl]) Register(factory func(ctx context.Context) impl)
- func (l *Logic[request, response, model, impl]) RegisterIn(r *grr.Registry, factory func(ctx context.Context) impl)
- func (l *Logic[request, response, model, impl]) RegisterScoped(newModel func(ctx context.Context) model)
- func (l *Logic[request, response, model, impl]) RegisterScopedIn(r *grr.Registry, newModel func(ctx context.Context) model)
- func (l *Logic[request, response, model, impl]) RegisterSingleton(newModel func() model)
- func (l *Logic[request, response, model, impl]) RegisterSingletonIn(r *grr.Registry, newModel func() model)
- func (l *Logic[request, response, model, impl]) RegisterTransient(newModel func(ctx context.Context) model)
- func (l *Logic[request, response, model, impl]) RegisterTransientIn(r *grr.Registry, newModel func(ctx context.Context) model)
- func (l *Logic[request, response, model, impl]) TryDo(ctx context.Context, req request) (resp response, ok bool, err error)
- type Logical
- type MissingError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func LogicFunc ¶
func LogicFunc[request, response any](fn func(context.Context, request) (response, error)) func(struct{}) funcImpl[request, response]
LogicFunc adapts fn into a newLogic-compatible constructor for stateless logic that needs no model struct:
var Ping = gold.NewLogic("Ping", gold.LogicFunc(
func(ctx context.Context, req PingRequest) (*PingResponse, error) {
return &PingResponse{Message: "pong"}, nil
},
))
Register it with RegisterStateless (no model factory needed).
Example ¶
LogicFunc lets you write stateless logic as a plain function — no model struct needed. Register it with RegisterStatelessIn (no factory needed).
package main
import (
"context"
"fmt"
"github.com/arpaad/grr"
gold "github.com/arpaad/gold"
)
type greetRequest string
type greetResponse struct{ Text string }
func main() {
r := grr.New()
Ping := gold.NewLogic("Ping", gold.LogicFunc(
func(_ context.Context, req greetRequest) (*greetResponse, error) {
return &greetResponse{Text: "pong: " + string(req)}, nil
},
))
gold.RegisterStatelessIn(r, Ping)
ctx := grr.WithRegistry(context.Background(), r)
resp, _ := Ping.Do(ctx, greetRequest("hello"))
fmt.Println(resp.Text)
}
Output: pong: hello
func MustValidate ¶
MustValidate is Validate but panics on a non-nil error — for use in main() at startup, where an unwired logic should stop the process before it serves traffic.
func RegisterStateless ¶
func RegisterStateless[request, response any, impl Logical[request, response]](l *Logic[request, response, struct{}, impl])
RegisterStateless is sugar for RegisterStatelessIn(grr.Default, l).
func RegisterStatelessIn ¶
func RegisterStatelessIn[request, response any, impl Logical[request, response]](r *grr.Registry, l *Logic[request, response, struct{}, impl])
RegisterStatelessIn registers a logic built with LogicFunc — no model factory needed since LogicFunc's model is always struct{}. Only compiles for logics shaped that way, enforced by the type signature.
func Validate ¶
Validate checks that every logic declared with NewLogic has an implementation registered in r (parent chain included), returning a *MissingError naming any that don't, or nil if all are wired.
Call it in main(), after all init()/wiring has run, to turn the blank-import footgun (a forgotten `import _ "myapp/di"`) into a fail-fast startup check instead of a panic on the first request that hits an unwired logic.
Validate assumes a single app-wide registry chain. For multi-registry setups (e.g. per-tenant registries that each wire a different subset), use ValidateLogics with the explicit set instead.
Types ¶
type Logic ¶
type Logic[request, response, model any, impl Logical[request, response]] struct { // contains filtered or unexported fields }
Logic is a typed business logic unit: it carries a name and a constructor (model -> impl). The actual implementation instance lives in a grr.Registry, wired up separately from the definition.
All 4 type parameters are normally inferred from the newImpl constructor passed to NewLogic — they don't need to be written out, but LSP hover still shows the full type, which doubles as documentation.
func NewLogic ¶
func NewLogic[request, response, model any, impl Logical[request, response]](name string, newImpl func(model) impl) *Logic[request, response, model, impl]
NewLogic defines a logic unit. It does not register anything — wiring happens separately via Register/RegisterIn (and friends).
Example ¶
NewLogic defines the logic unit without wiring it anywhere. Wiring happens separately — here via RegisterSingletonIn.
package main
import (
"context"
"fmt"
"github.com/arpaad/grr"
gold "github.com/arpaad/gold"
)
type greetRequest string
type greetResponse struct{ Text string }
type greeter interface {
Greet() string
}
type greetLogic struct{ greeter }
func newGreet(m greeter) *greetLogic { return &greetLogic{greeter: m} }
func (l *greetLogic) Do(_ context.Context, req greetRequest) (*greetResponse, error) {
return &greetResponse{Text: l.Greet() + string(req)}, nil
}
type staticGreeter struct{ msg string }
func (s *staticGreeter) Greet() string { return s.msg }
func main() {
r := grr.New()
Greet := gold.NewLogic("Greet", newGreet)
Greet.RegisterSingletonIn(r, func() greeter {
return &staticGreeter{msg: "Hello, "}
})
ctx := grr.WithRegistry(context.Background(), r)
resp, _ := Greet.Do(ctx, greetRequest("world"))
fmt.Println(resp.Text)
}
Output: Hello, world
func (*Logic[request, response, model, impl]) Do ¶
func (l *Logic[request, response, model, impl]) Do(ctx context.Context, req request) (response, error)
Do resolves the registered implementation from ctx's registry (falling back to grr.Default) and runs it. Panics if no implementation is registered for this logic — that's a developer error, not a runtime condition callers should branch on (use IsRegistered to check first if registration is conditional).
func (*Logic[request, response, model, impl]) IsRegistered ¶
IsRegistered reports whether an implementation is registered for this logic in ctx's registry (or its parent chain).
Example ¶
IsRegistered lets call sites check registration before calling Do — useful for optional, feature-flagged logic that may not be wired in all deployments.
package main
import (
"context"
"fmt"
"github.com/arpaad/grr"
gold "github.com/arpaad/gold"
)
type greetRequest string
type greetResponse struct{ Text string }
type greeter interface {
Greet() string
}
type greetLogic struct{ greeter }
func newGreet(m greeter) *greetLogic { return &greetLogic{greeter: m} }
func (l *greetLogic) Do(_ context.Context, req greetRequest) (*greetResponse, error) {
return &greetResponse{Text: l.Greet() + string(req)}, nil
}
type staticGreeter struct{ msg string }
func (s *staticGreeter) Greet() string { return s.msg }
func main() {
r := grr.New()
Greet := gold.NewLogic("Greet", newGreet)
ctx := grr.WithRegistry(context.Background(), r)
fmt.Println(Greet.IsRegistered(ctx))
Greet.RegisterSingletonIn(r, func() greeter { return &staticGreeter{msg: "Hi"} })
fmt.Println(Greet.IsRegistered(ctx))
}
Output: false true
func (*Logic[request, response, model, impl]) Register ¶
Register is sugar for RegisterIn(grr.Default, factory).
func (*Logic[request, response, model, impl]) RegisterIn ¶
func (l *Logic[request, response, model, impl]) RegisterIn(r *grr.Registry, factory func(ctx context.Context) impl)
RegisterIn is the base registration primitive — full freedom over how impl is produced per resolve (external cache, A/B testing, tenant routing, etc). Register* sugar below all reduce to this.
func (*Logic[request, response, model, impl]) RegisterScoped ¶
func (l *Logic[request, response, model, impl]) RegisterScoped(newModel func(ctx context.Context) model)
RegisterScoped is sugar for RegisterScopedIn(grr.Default, newModel).
func (*Logic[request, response, model, impl]) RegisterScopedIn ¶
func (l *Logic[request, response, model, impl]) RegisterScopedIn(r *grr.Registry, newModel func(ctx context.Context) model)
RegisterScopedIn builds the model (and impl) once per active scope (see grr.Registry.BeginScope) and reuses it for the rest of that scope. Resolving without an active scope panics — never register scoped logic against grr.Default unless every caller guarantees a scope.
Example ¶
RegisterScopedIn builds the model once per active scope. Two calls within the same scope share an instance; a new scope gets a new build.
package main
import (
"context"
"fmt"
"github.com/arpaad/grr"
gold "github.com/arpaad/gold"
)
type greetRequest string
type greetResponse struct{ Text string }
type greeter interface {
Greet() string
}
type greetLogic struct{ greeter }
func newGreet(m greeter) *greetLogic { return &greetLogic{greeter: m} }
func (l *greetLogic) Do(_ context.Context, req greetRequest) (*greetResponse, error) {
return &greetResponse{Text: l.Greet() + string(req)}, nil
}
type staticGreeter struct{ msg string }
func (s *staticGreeter) Greet() string { return s.msg }
func main() {
r := grr.New()
Greet := gold.NewLogic("Greet", newGreet)
builds := 0
Greet.RegisterScopedIn(r, func(_ context.Context) greeter {
builds++
return &staticGreeter{msg: "Hi"}
})
base := grr.WithRegistry(context.Background(), r)
ctx1, end1 := r.BeginScope(base)
_, _ = Greet.Do(ctx1, greetRequest(""))
_, _ = Greet.Do(ctx1, greetRequest(""))
end1()
fmt.Println(builds) // 1 — reused within scope
ctx2, end2 := r.BeginScope(base)
defer end2()
_, _ = Greet.Do(ctx2, greetRequest(""))
fmt.Println(builds) // 2 — new scope, new build
}
Output: 1 2
func (*Logic[request, response, model, impl]) RegisterSingleton ¶
func (l *Logic[request, response, model, impl]) RegisterSingleton(newModel func() model)
RegisterSingleton is sugar for RegisterSingletonIn(grr.Default, newModel).
func (*Logic[request, response, model, impl]) RegisterSingletonIn ¶
func (l *Logic[request, response, model, impl]) RegisterSingletonIn(r *grr.Registry, newModel func() model)
RegisterSingletonIn builds the model once (guarded by sync.Once) and reuses the resulting impl for every Do call resolved against r.
Example ¶
RegisterSingletonIn builds the model exactly once regardless of how many times Do is called.
package main
import (
"context"
"fmt"
"github.com/arpaad/grr"
gold "github.com/arpaad/gold"
)
type greetRequest string
type greetResponse struct{ Text string }
type greeter interface {
Greet() string
}
type greetLogic struct{ greeter }
func newGreet(m greeter) *greetLogic { return &greetLogic{greeter: m} }
func (l *greetLogic) Do(_ context.Context, req greetRequest) (*greetResponse, error) {
return &greetResponse{Text: l.Greet() + string(req)}, nil
}
type staticGreeter struct{ msg string }
func (s *staticGreeter) Greet() string { return s.msg }
func main() {
r := grr.New()
Greet := gold.NewLogic("Greet", newGreet)
builds := 0
Greet.RegisterSingletonIn(r, func() greeter {
builds++
return &staticGreeter{msg: "Hi"}
})
ctx := grr.WithRegistry(context.Background(), r)
_, _ = Greet.Do(ctx, greetRequest(""))
_, _ = Greet.Do(ctx, greetRequest(""))
fmt.Println(builds)
}
Output: 1
func (*Logic[request, response, model, impl]) RegisterTransient ¶
func (l *Logic[request, response, model, impl]) RegisterTransient(newModel func(ctx context.Context) model)
RegisterTransient is sugar for RegisterTransientIn(grr.Default, newModel).
func (*Logic[request, response, model, impl]) RegisterTransientIn ¶
func (l *Logic[request, response, model, impl]) RegisterTransientIn(r *grr.Registry, newModel func(ctx context.Context) model)
RegisterTransientIn builds a fresh model (and impl) on every Do call.
Example ¶
RegisterTransientIn builds a fresh model on every Do call.
package main
import (
"context"
"fmt"
"github.com/arpaad/grr"
gold "github.com/arpaad/gold"
)
type greetRequest string
type greetResponse struct{ Text string }
type greeter interface {
Greet() string
}
type greetLogic struct{ greeter }
func newGreet(m greeter) *greetLogic { return &greetLogic{greeter: m} }
func (l *greetLogic) Do(_ context.Context, req greetRequest) (*greetResponse, error) {
return &greetResponse{Text: l.Greet() + string(req)}, nil
}
type staticGreeter struct{ msg string }
func (s *staticGreeter) Greet() string { return s.msg }
func main() {
r := grr.New()
Greet := gold.NewLogic("Greet", newGreet)
builds := 0
Greet.RegisterTransientIn(r, func(_ context.Context) greeter {
builds++
return &staticGreeter{msg: "Hi"}
})
ctx := grr.WithRegistry(context.Background(), r)
_, _ = Greet.Do(ctx, greetRequest(""))
_, _ = Greet.Do(ctx, greetRequest(""))
fmt.Println(builds)
}
Output: 2
func (*Logic[request, response, model, impl]) TryDo ¶
func (l *Logic[request, response, model, impl]) TryDo(ctx context.Context, req request) (resp response, ok bool, err error)
TryDo is Do but reports a missing implementation with ok == false instead of panicking — for genuinely optional, feature-flagged logic that may or may not be wired. ok is false ONLY when no implementation is registered. A registered-but-wrong-type implementation still panics (a wiring bug), and a business-logic error still flows through err.
type Logical ¶
type Logical[request, response any] interface { Do(ctx context.Context, req request) (response, error) }
Logical is what a concrete logic implementation must satisfy.
type MissingError ¶
type MissingError struct {
Names []string
}
MissingError reports logics that were declared with NewLogic but have no implementation registered in the validated registry. Names is sorted.
func (*MissingError) Error ¶
func (e *MissingError) Error() string