package di import ( "errors" "fmt" "strings" "testing" ) func ExampleBuild() { type Username string type Config struct { User Username Age int Greeting func(Username, int) string } cfg := Config{ User: "Alice", Age: 42, Greeting: func(u Username, age int) string { return fmt.Sprintf("Hello, %s. You've been around the sun %d times!", string(u), age) }, } type Result struct { Greeting string } var res Result err := Build(cfg, &res) if err != nil { fmt.Println(err) return } fmt.Println(res.Greeting) // Output: Hello, Alice. You've been around the sun 42 times! } func ExampleNew() { type Username string type Config struct { User Username Age int Greeting func(Username, int) string } cfg := Config{ User: "Alice", Age: 42, Greeting: func(u Username, age int) string { return fmt.Sprintf("Hello, %s. You've been around the sun %d times!", string(u), age) }, } greeting, err := New[string](cfg) if err != nil { fmt.Println(err) return } fmt.Println(greeting) // Output: Hello, Alice. You've been around the sun 42 times! } func TestBuildSuccess(t *testing.T) { type A struct { val string } type C struct { val int } type B struct { a *A cs []C } type Config struct { MakeA Provide[*A] MakeB Provide[*B] MakeCs []Provide[C] } cfg := Config{ MakeA: MustProvide[*A](func() (*A, error) { return &A{val: "hello"}, nil }), MakeB: MustProvide[*B](func(a *A, cs []C) *B { return &B{a: a, cs: cs} }), MakeCs: []Provide[C]{ MustProvide[C](C{val: 1}), MustProvide[C](func() (C, error) { return C{val: 2}, nil })}, } type Result struct { A *A B *B } var res Result err := Build(cfg, &res) if err != nil { t.Fatalf("Build failed: %v", err) } if res.A == nil { t.Fatalf("expected res.A to be populated") } if res.B == nil { t.Fatalf("expected res.B to be populated") } if res.B.a != res.A { t.Fatalf("expected B.a to reference A instance") } if len(res.B.cs) != 2 { t.Fatalf("wrong count. Saw %d", len(res.B.cs)) } if res.B.cs[0].val != 1 { t.Fatalf("wrong value") } if res.B.cs[1].val != 2 { t.Fatalf("wrong value") } if res.A.val != "hello" { t.Fatalf("unexpected A value: %s", res.A.val) } } func TestBuildSuccess2(t *testing.T) { type A struct { val string } type B struct { a *A } type Config struct { MakeA func() (*A, error) MakeB func(*A) (*B, error) } type Result struct { A *A } cfg := Config{ MakeA: func() (*A, error) { return &A{val: "hello"}, nil }, MakeB: func(a *A) (*B, error) { panic("Unexpected call to MakeB") // (removed unreachable code after panic) }, } var res Result err := Build(cfg, &res) if err != nil { t.Fatalf("Build failed: %v", err) } if res.A == nil { t.Fatalf("expected res.A to be populated") } if res.A.val != "hello" { t.Fatalf("unexpected A value: %s", res.A.val) } } // Test that constructor error is propagated. func TestBuildConstructorError(t *testing.T) { type A struct{} sentinel := errors.New("boom") type Config struct { MakeA func() (*A, error) } type Result struct { A *A } cfg := Config{ MakeA: func() (*A, error) { return nil, sentinel }, } var res Result err := Build(cfg, &res) if err == nil { t.Fatalf("expected error") } if !strings.Contains(err.Error(), "MakeA") { t.Fatalf("expected error to mention constructor name, got: %v", err) } if !strings.Contains(err.Error(), "boom") { t.Fatalf("expected original error message, got: %v", err) } if res.A != nil { t.Fatalf("result A should not be populated on constructor failure") } } // Test missing dependency (constructor requires *A but *A not provided). func TestBuildMissingDependency(t *testing.T) { type A struct{} type B struct { a *A } type Config struct { MakeB func(*A) (*B, error) } type Result struct { B *B } cfg := Config{ MakeB: func(a *A) (*B, error) { return &B{a: a}, nil }, } var res Result err := Build(cfg, &res) if err == nil { t.Fatalf("expected missing dependency error") } // Parameter type string should appear (may be *di.A). if !strings.Contains(err.Error(), "*di.A") && !strings.Contains(err.Error(), "di.A") { t.Fatalf("expected error to mention missing type *di.A, got: %v", err) } if res.B != nil { t.Fatalf("result B should not be populated") } } // Test cycle detection between X and Y. func TestBuildCycleDetection(t *testing.T) { type X struct{} type Y struct{} type Config struct { MakeX func(*Y) *X MakeY func(*X) *Y } type Result struct { X *X Y *Y } cfg := Config{ MakeX: func(y *Y) *X { return &X{} }, MakeY: func(x *X) *Y { return &Y{} }, } var res Result err := Build(cfg, &res) if err == nil { t.Fatalf("expected cycle detection error") } // Both constructors should still be listed as remaining. if !strings.Contains(err.Error(), "MakeX") || !strings.Contains(err.Error(), "MakeY") { t.Fatalf("expected error to list remaining constructors MakeX and MakeY, got: %v", err) } if res.X != nil || res.Y != nil { t.Fatalf("cycle should prevent any construction; got X=%v Y=%v", res.X, res.Y) } } // Ensure that providing pre-supplied value satisfies dependency without constructor for it. func TestBuildWithPreSuppliedValue(t *testing.T) { type A struct { v int } type B struct { a *A } type Config struct { // Only constructor for B; A provided directly in config. A *A MB func(*A) (*B, error) } type Result struct { A *A B *B } cfg := Config{ A: &A{v: 42}, MB: func(a *A) (*B, error) { return &B{a: a}, nil }, } var res Result err := Build(cfg, &res) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.A == nil || res.A.v != 42 { t.Fatalf("expected pre-supplied A (42), got %+v", res.A) } if res.B == nil || res.B.a != res.A { t.Fatalf("expected B referencing A, got %+v", res.B) } } func TestTypeAlias(t *testing.T) { type ANum int type Config struct { A ANum B int } type Result struct { A ANum } var res Result err := Build(Config{3, 4}, &res) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.A != 3 { t.Fatalf("expected A=3, got %v", res.A) } } func TestReferenceConfig(t *testing.T) { type NestedConfig struct { OtherSetting bool NestedDecision func(c NestedConfig) uint } type Config struct { NestedConfig SomeSetting bool Inner func(c Config) int } type Result struct { A int B uint } var res Result err := Build(Config{ SomeSetting: true, Inner: func(c Config) int { if c.SomeSetting { return 1 } return 0 }, NestedConfig: NestedConfig{ OtherSetting: true, NestedDecision: func(c NestedConfig) uint { if c.OtherSetting { return 1 } return 0 }, }, }, &res) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.A != 1 { t.Fatalf("expected A=1, got %v", res.A) } if res.B != 1 { t.Fatalf("expected B=1, got %v", res.A) } } func TestNew(t *testing.T) { type Config struct { A int } type Result struct { A int } cfg := Config{A: 42} res, err := New[Result](cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if res.A != 42 { t.Fatalf("expected A=42, got %v", res.A) } } func TestSpecificTypes(t *testing.T) { type Config struct { A int } cfg := Config{A: 42} res, err := New[int](cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if res != 42 { t.Fatalf("expected A=42, got %v", res) } var res2 int err = Build(Config{A: 42}, &res2) if err != nil { t.Fatalf("unexpected error: %v", err) } if res2 != 42 { t.Fatalf("expected A=42, got %v", res2) } }