~marcopolo/di

88a7eea7fa88388d535f76825ea67138e9786caa — Marco Munizaga 9 months ago 1b53f1a
Doc rewriting
2 files changed, 65 insertions(+), 38 deletions(-)

M di.go
M di_test.go
M di.go => di.go +33 -38
@@ 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

M di_test.go => di_test.go +32 -0
@@ 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