~marcopolo/di

8bf5b787b8413fb654b6d479a9958f9b143c6dcd — Marco Munizaga 9 months ago 8a9c929
Add explicit Optional types
2 files changed, 110 insertions(+), 6 deletions(-)

M di.go
M di_test.go
M di.go => di.go +24 -6
@@ 39,6 39,26 @@ type Provide[Out any] struct {
	fOrV any
}

// Optional is the canonical way of specifying a field is optional. The library
// does not handle this value in a special way.
type Optional[T any] struct {
	Val    T
	IsSome bool
}

// Unwrap returns the optional value T or panics if not present
func (o Optional[T]) Unwrap() T {
	if !o.IsSome {
		panic(fmt.Sprintf("expected optional value of type %T to be present", o.Val))
	}
	return o.Val
}

// Some returns a new Optional type supplied with the value.
func Some[T any](val T) Optional[T] {
	return Optional[T]{Val: val, IsSome: true}
}

type provideI interface {
	diOutType() reflect.Type
	diPayload() any


@@ 203,13 223,14 @@ func Build[C any, R any](config C, result R) error {
	}

	// Can we just resolve the direct result type?
	if v, err := c.resolve(resV.Elem().Type()); err == nil {
	v, err := c.resolve(resV.Elem().Type())
	if err == nil {
		resV.Elem().Set(v)
		return nil
	}

	if resV.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("couldn't build result direct, and can not fill result as it is not a struct")
		return fmt.Errorf("couldn't build result directly (%w), and can not fill result as it is not a struct", err)
	}

	// Populate result fields of the struct


@@ 370,10 391,7 @@ func (c *collector) collect(v reflect.Value, path string) error {
			}

		default:
			// preprovided singular instance (non-zero only)
			if !fv.IsZero() {
				c.values[fv.Type()] = fv
			}
			c.values[fv.Type()] = fv
		}
	}
	return nil

M di_test.go => di_test.go +86 -0
@@ 1,6 1,7 @@
package di

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net/http"


@@ 64,6 65,40 @@ func ExampleNew() {
	// Output: Hello, Alice. You've been around the sun 42 times!
}

func ExampleOptional() {
	type Config struct {
		// A *tls.Config type or Provide[*tls.Config] also works, but using the
		// Optional wrapper lets us convey the optionality explicitly
		TLSConfig Optional[*tls.Config]
		Server    Provide[*http.Server]
	}

	cfg := Config{
		Server: MustProvide[*http.Server](func(
			tlsConf Optional[*tls.Config],
		) *http.Server {
			s := &http.Server{
				Addr: ":8080",
			}
			if tlsConf.IsSome {
				s.TLSConfig = tlsConf.Val
			}
			return s
		}),
	}

	server, err := New[*http.Server](cfg)
	if err != nil {
		fmt.Println(err)
		return
	}
	if server.TLSConfig == nil {
		fmt.Println("No TLS configuration was provided")
	}

	// Output: No TLS configuration was provided
}

func ExampleSideEffect() {
	type Config struct {
		Server      *http.Server


@@ 232,6 267,57 @@ func TestBuildSuccess(t *testing.T) {
			},
		},
		{
			name: "pre-supplied nil values",
			config: struct {
				A *A
			}{
				A: nil,
			},
			result: &struct {
				A *A
			}{},
			verify: func(t *testing.T, result any) {
				res := result.(*struct {
					A *A
				})
				if res.A != nil {
					t.Fatalf("expected nil A, got %+v", res.A)
				}
			},
		},
		{
			name: "Explicit Optional Value",
			config: struct {
				A Optional[*A]
			}{},
			result: &struct {
				A Optional[*A]
			}{},
			verify: func(t *testing.T, result any) {
				res := result.(*struct {
					A Optional[*A]
				})
				if res.A.IsSome {
					t.Fatalf("expected none")
				}
			},
		},
		{
			name: "Explicit Provided Optional Value",
			config: struct {
				A Optional[*A]
			}{A: Some(&A{})},
			result: &struct {
				A Optional[*A]
			}{},
			verify: func(t *testing.T, result any) {
				res := result.(*struct {
					A Optional[*A]
				})
				_ = res.A.Unwrap()
			},
		},
		{
			name: "type aliases",
			config: struct {
				A ANum