@@ 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
@@ 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