From 88a7eea7fa88388d535f76825ea67138e9786caa Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 25 Aug 2025 16:26:07 -0700 Subject: [PATCH] Doc rewriting --- di.go | 71 +++++++++++++++++++++++++----------------------------- di_test.go | 32 ++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/di.go b/di.go index 057138b050f66fd7b7bfee1cfadc1b17290b7d30..04b0ec0c761f170c5c1304dc7b3a05aa59d6f995 100644 --- a/di.go +++ b/di.go @@ -1,6 +1,12 @@ // Package di is a minimal reflection-based dependency injection helper. // -// Usage pattern: +// The general usage pattern is: +// 1. Define a configuration struct. This defines everything that is required to +// build your result. +// 2. Define your result struct or result type. This is the resulting objects +// created from `Build(..)` or `New(..)` +// +// Example Usage: // // type Config struct { // Logger di.Provide[*slog.Logger] @@ -13,13 +19,13 @@ // // cfg := Config{ /* constructors here ... */ } // var res Result -// if err := di.Build(cfg, &res); err != nil { ... }\ +// if err := di.Build(cfg, &res); err != nil { ... } // -// Or with New(cfg): +// Or: // -// server, err := di.New[*http.Server](cfg) +// server, err := di.New[*http.Server](cfg) // -// See the doc comments for Build and New for more details +// See the doc comments for Build for more details package di import ( @@ -33,23 +39,6 @@ type Provide[Out any] struct { fOrV any } -// SideEffect is a sentinel value representing a constructor used only for side -// effects such as introducing two components together without a circular -// dependency. -// -// It's idiomatic to have your config declare a SideEffects field of type -// []di.MustProvide[di.SideEffect], and have a "_" field in your result struct -// of type []di.SideEffect. -type SideEffect struct{} - -func NewSideEffect(f any) (Provide[SideEffect], error) { - return NewProvide[SideEffect](f) -} - -func MustSideEffect(f any) Provide[SideEffect] { - return Must(NewSideEffect(f)) -} - type provideI interface { diOutType() reflect.Type diPayload() any @@ -58,6 +47,17 @@ type provideI interface { func (p Provide[Out]) diOutType() reflect.Type { return typeOf[Out]() } func (p Provide[Out]) diPayload() any { return p.fOrV } +// SideEffect is a canonical value representing a constructor used only for side +// effects such as introducing two components together without a circular +// dependency. There is nothing special about this struct. Any empty struct +// would work just as well. +// +// It's idiomatic to have your config declare a SideEffects field of type +// []di.Provide[di.SideEffect], and have a "_" field in your result struct +// of type []di.SideEffect. +type SideEffect struct{} + +// Must is a helper that returns T if err == nil, or panics otherwise. func Must[T any](t T, err error) T { if err != nil { panic(err) @@ -150,28 +150,26 @@ type collector struct { resolving map[reflect.Type]bool } +// New is identical to build, except it allocates R itself. Refer to Build for +// documentation. func New[R any, C any](config C) (R, error) { var r R err := Build(config, &r) return r, err } -// Build resolves only what's needed to populate exported fields in result. -// Supports arbitrarily nested provider namespaced structs inside config. +// Build builds the result of type R from the provided config of values or +// constructors. // -// # TODO rewrite this doc +// Supported providers in config: +// - T // A simple "provide this value". Used for simple settings. +// - func(...Deps) T / (T,error) // A constructor with well defined types for T. +// - []func(...Deps) T/(T,error) // A list of constructors that contributes to []T. +// - Provide[T] // A runtime-checked value or constructor value that can accept arbitrary arguments. Must return T or (T, error). +// - []Provide[T] // A list of constructors/values contributing to []T // -// Supported providers in config (at any nesting depth): -// - Provide[T] // single constructor or value for T -// - []Provide[T] // list of constructors/values contributing to []T -// - func(...Deps) T / (T,error) // singular constructor for T -// - value of type T // preprovided singular value -// - []func(...Deps) T/(T,error) // contributes to []T -// - []T // contributes to []T -// -// Non-func, non-zero exported fields remain prebound instances. // result must be a pointer to a struct or value. If it is a value the config -// must define how to construct it. +// must define how to construct that value. func Build[C any, R any](config C, result R) error { cfgV := reflect.ValueOf(config) for cfgV.IsValid() && cfgV.Kind() == reflect.Pointer { @@ -518,8 +516,6 @@ func (c *collector) resolve(t reflect.Type) (reflect.Value, error) { return outs[0], nil } -// --- helpers --- - func validateCtorSignature(ft reflect.Type, name string) error { if ft.IsVariadic() { return fmt.Errorf("constructor %q: variadics not supported", name) @@ -542,7 +538,6 @@ func isErrorType(t reflect.Type) bool { return t == reflect.TypeOf((*error)(nil)).Elem() } -// asProvide tries to view v as a Provide[*]. Returns (iface, true) if so. func asProvide(v reflect.Value) (provideI, bool) { if !v.IsValid() { return nil, false diff --git a/di_test.go b/di_test.go index 005a293ee5df00a534cf7a7e2b5069b08b448904..29d4812447d25955d040dcec6511d98ce14748ff 100644 --- a/di_test.go +++ b/di_test.go @@ -3,6 +3,7 @@ package di import ( "errors" "fmt" + "net/http" "strings" "testing" ) @@ -63,6 +64,37 @@ func ExampleNew() { // Output: Hello, Alice. You've been around the sun 42 times! } +func ExampleSideEffect() { + type Config struct { + Server *http.Server + SideEffects []Provide[SideEffect] + } + type Result struct { + StartedServer *http.Server + _ []SideEffect + } + + res, err := New[Result](&Config{ + Server: &http.Server{ + Addr: ":8080", + }, + SideEffects: []Provide[SideEffect]{ + MustProvide[SideEffect](func() SideEffect { + fmt.Println("Starting server...") + go http.ListenAndServe(":8080", nil) + return SideEffect{} + }), + }, + }) + if err != nil { + fmt.Println(err) + return + } + defer res.StartedServer.Close() + + // Output: Starting server... +} + func TestBuildSuccess(t *testing.T) { type A struct { val string