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