diff --git a/config/config2.go b/config/config2.go index c92d75226..6b5c09542 100644 --- a/config/config2.go +++ b/config/config2.go @@ -2,6 +2,7 @@ package config import ( "crypto/rand" + "fmt" "net" "slices" "time" @@ -16,7 +17,6 @@ import ( "github.com/libp2p/go-libp2p/core/protocol" "github.com/libp2p/go-libp2p/core/sec" "github.com/libp2p/go-libp2p/core/transport" - "github.com/libp2p/go-libp2p/di" bhost "github.com/libp2p/go-libp2p/p2p/host/basic" blankhost "github.com/libp2p/go-libp2p/p2p/host/blank" "github.com/libp2p/go-libp2p/p2p/host/eventbus" @@ -40,6 +40,8 @@ import ( ma "github.com/multiformats/go-multiaddr" manet "github.com/multiformats/go-multiaddr/net" "github.com/quic-go/quic-go" + + "git.sr.ht/~marcopolo/di" ) type ListenAddrs []ma.Multiaddr @@ -106,7 +108,7 @@ type AutoNatPeerStore peerstore.Peerstore type AutoNATHost host.Host type AutoNatConfig struct { PrivateKey func() (AutoNatPrivKey, error) - PeerStore di.Provide[AutoNatPeerStore] + Peerstore di.Provide[AutoNatPeerStore] AutoNATV2Host di.Provide[AutoNATHost] } @@ -331,7 +333,9 @@ var DefaultConfig = Config2{ ) }), }, - DialConfig: DialConfig{}, + DialConfig: DialConfig{ + DialTimeout: di.MustProvide[time.Duration](10 * time.Second), + }, MetricsConfig: MetricsConfig{ PrometheusRegisterer: prometheus.DefaultRegisterer, }, @@ -341,7 +345,7 @@ var DefaultConfig = Config2{ priv, _, err := crypto.GenerateEd25519Key(rand.Reader) return AutoNatPrivKey(priv), err }, - PeerStore: di.MustProvide[AutoNatPeerStore]( + Peerstore: di.MustProvide[AutoNatPeerStore]( func() (AutoNatPeerStore, error) { ps, err := pstoremem.NewPeerstore() return AutoNatPeerStore(ps), err @@ -349,14 +353,59 @@ var DefaultConfig = Config2{ ), AutoNATV2Host: di.MustProvide[AutoNATHost]( - func(k AutoNatPrivKey, ps AutoNatPeerStore, swarmCfg SwarmConfig, tptConfig TransportsConfig) (AutoNATHost, error) { - panic("todo") + func( + config Config2, + k AutoNatPrivKey, + ps AutoNatPeerStore, + l *Lifecycle, + ) (AutoNATHost, error) { + // Use the same settings as the normal host, but we override some + autonatCfg := config + autonatCfg.ListenAddrs = nil + autonatCfg.Peerstore = func() (peerstore.Peerstore, error) { + return ps, nil + } + autonatCfg.PrivateKey = func() (crypto.PrivKey, error) { + return k, nil + } + autonatCfg.Lifecycle = func() *Lifecycle { + // Use the same lifecycle as our parent config + return l + } + // TODO: add ability to get swarm read only state + autonatCfg.Host = di.MustProvide[host.Host](func( + l *Lifecycle, + swarm *swarm.Swarm, + ) host.Host { + mux := mstream.NewMultistreamMuxer[protocol.ID]() + h := &blankhost.BlankHost{ + N: swarm, + M: mux, + ConnMgr: connmgr.NullConnMgr{}, + E: nil, + // Don't need this for autonat + SkipInitSignedRecord: true, + } + l.OnStart(func() error { + fmt.Println("Starting autonat host") + return h.Start() + }) + l.OnClose(h) + return h + }) + type Result struct { + Host host.Host + _ []di.SideEffect + } + + res, err := di.New[Result](autonatCfg) + return res.Host, err }, ), }, SideEffects: []di.Provide[di.SideEffect]{ - di.MustSideEffect(func(s *swarm.Swarm, tpts []transport.Transport) (di.SideEffect, error) { + di.MustProvide[di.SideEffect](func(s *swarm.Swarm, tpts []transport.Transport) (di.SideEffect, error) { for _, t := range tpts { err := s.AddTransport(t) if err != nil { @@ -371,8 +420,10 @@ var DefaultConfig = Config2{ l *Lifecycle, network *swarm.Swarm, connmgr connmgr.ConnManager, + autonatHost AutoNATHost, b event.Bus, ) (host.Host, error) { + _ = autonatHost mux := mstream.NewMultistreamMuxer[protocol.ID]() h := &blankhost.BlankHost{ N: network, diff --git a/config/config_test.go b/config/config_test.go index 271303182..178d075f7 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -5,10 +5,10 @@ import ( "io" "testing" + "git.sr.ht/~marcopolo/di" "github.com/libp2p/go-libp2p/core/host" "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/di" ) func TestNilOption(t *testing.T) { diff --git a/di/di.go b/di/di.go deleted file mode 100644 index 2f58448c7..000000000 --- a/di/di.go +++ /dev/null @@ -1,568 +0,0 @@ -// A minimal reflection-based dependency injection helper. -// -// Usage pattern: -// -// type Config struct { -// Logger func() (*zap.Logger, error) -// Repo func(*zap.Logger) (*Repo, error) -// // Pre-supplied dependency (non-func, non-zero fields are treated as already available): -// Clock time.Clock -// } -// -// type Result struct { -// Logger *zap.Logger -// Repo *Repo -// } -// -// var cfg Config -// var res Result -// if err := di.Build(cfg, &res); err != nil { ... } -// -// The Build function: -// - Treats every function field in the config struct as a constructor. -// - A constructor must return either (T) or (T, error). -// - Constructor parameters are resolved from already constructed / supplied values. -// - Non-function, non-zero fields in the config struct are treated as pre-supplied instances. -// - Pre-populated (non-zero) fields in the result struct are also treated as pre-supplied. -// - After a constructor runs, the produced value is stored; any zero field in the result -// struct whose type is assignable from the produced value's type will be filled. -// - Reuses singletons: one instance per produced type. Duplicate producers of the -// exact same concrete type are rejected. -// - Detects unsatisfied dependencies / cycles (when no progress can be made). -// -// This is intentionally minimal and opinionated; it is not a full-featured DI container. -package di - -import ( - "errors" - "fmt" - "reflect" - "strings" -) - -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. -type SideEffect struct{} - -func NewSideEffect(f any) (Provide[SideEffect], error) { - return NewProvide[SideEffect](f) - // t := reflect.TypeOf(f) - // if t.Kind() == reflect.Func && t.NumOut() == 1 { - // if isErrorType(t.Out(0)) { - // return Provide[SideEffect]{fOrV: f}, nil - // } - // } - - // return Provide[SideEffect]{fOrV: f}, fmt.Errorf("NewSideEffect: Invalid signature. Requires a function that returns only an error") -} - -func MustSideEffect(f any) Provide[SideEffect] { - return Must(NewSideEffect(f)) -} - -type provideI interface { - diOutType() reflect.Type - diPayload() any -} - -func (p Provide[Out]) diOutType() reflect.Type { return typeOf[Out]() } -func (p Provide[Out]) diPayload() any { return p.fOrV } - -func Must[T any](t T, err error) T { - if err != nil { - panic(err) - } - return t -} - -func MustProvide[Out any](ctorOrVal any) Provide[Out] { - return Must(NewProvide[Out](ctorOrVal)) -} - -func NewProvide[Out any](ctorOrVal any) (Provide[Out], error) { - outType := typeOf[Out]() - - if ctorOrVal == nil { - if canBeNil(outType) { - return Provide[Out]{fOrV: nil}, nil - } - return Provide[Out]{}, fmt.Errorf("Provide[%v]: nil not valid for non-nilable type", outType) - } - - t := reflect.TypeOf(ctorOrVal) - - // Case 1: function returning Out or (Out, error). Arbitrary args allowed. - if t.Kind() == reflect.Func { - nout := t.NumOut() - switch nout { - case 1: - if !t.Out(0).AssignableTo(outType) { - return Provide[Out]{}, fmt.Errorf("Provide[%v]: function return %v is not assignable to %v", - outType, t.Out(0), outType) - } - return Provide[Out]{fOrV: ctorOrVal}, nil - - case 2: - if !t.Out(0).AssignableTo(outType) { - return Provide[Out]{}, fmt.Errorf("Provide[%v]: first return %v is not assignable to %v", - outType, t.Out(0), outType) - } - if !isErrorType(t.Out(1)) { - return Provide[Out]{}, fmt.Errorf("Provide[%v]: second return must be error, got %v", - outType, t.Out(1)) - } - return Provide[Out]{fOrV: ctorOrVal}, nil - - default: - return Provide[Out]{}, fmt.Errorf("Provide[%v]: function must return Out or (Out, error); got %d returns", - outType, nout) - } - } - - // Case 2: value assignable to Out (covers interface satisfaction) - if !t.AssignableTo(outType) { - return Provide[Out]{}, fmt.Errorf("Provide[%v]: value of type %v is not assignable to %v", outType, t, outType) - } - return Provide[Out]{fOrV: ctorOrVal}, nil -} - -// Helpers - -func typeOf[T any]() reflect.Type { - return reflect.TypeOf((*T)(nil)).Elem() -} - -func canBeNil(t reflect.Type) bool { - switch t.Kind() { - case reflect.Interface, reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.Slice: - return true - default: - return false - } -} - -type ctor struct { - name string - fn reflect.Value - out reflect.Type -} - -type collector struct { - // Singular instances & providers - values map[reflect.Type]reflect.Value // exact type -> instance - providers map[reflect.Type][]ctor // out type -> ctors - - // List providers for []T - listValues map[reflect.Type][]reflect.Value // elem dynamic type -> instances - listProviders map[reflect.Type][]ctor // elem out type -> ctors - listPresence map[reflect.Type]bool // elem T present explicitly (even if empty) - - // Cycle detection - resolving map[reflect.Type]bool -} - -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. -// Now supports arbitrarily nested provider namespaces inside config -// (exported struct / *struct fields are recursively visited). -// -// 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. -// Pre-populated (non-zero) fields in result are also prebound. -// result must be a pointer to a struct. -func Build[C any, R any](config C, result R) error { - cfgV := reflect.ValueOf(config) - for cfgV.IsValid() && cfgV.Kind() == reflect.Pointer { - if cfgV.IsNil() { - return errors.New("config pointer is nil") - } - cfgV = cfgV.Elem() - } - if !cfgV.IsValid() || cfgV.Kind() != reflect.Struct { - return errors.New("config must be a struct or pointer to struct") - } - - resV := reflect.ValueOf(result) - if !resV.IsValid() || resV.Kind() != reflect.Pointer { - return errors.New("result must be a pointer to struct or a pointer to a value") - } - - c := &collector{ - values: make(map[reflect.Type]reflect.Value), - providers: make(map[reflect.Type][]ctor), - listValues: make(map[reflect.Type][]reflect.Value), - listProviders: make(map[reflect.Type][]ctor), - listPresence: make(map[reflect.Type]bool), - resolving: make(map[reflect.Type]bool), - } - - // Provide access to the Config value itself - c.values[cfgV.Type()] = cfgV - if err := c.collect(cfgV, ""); err != nil { - return err - } - - // Can we just resolve the direct result type? - if v, err := c.resolve(resV.Elem().Type()); 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") - } - - // Populate result fields of the struct - var missing []string - resStruct := resV.Elem() - resT := resStruct.Type() - for i := 0; i < resT.NumField(); i++ { - sf := resT.Field(i) - if sf.Name != "_" && sf.PkgPath != "" { - continue - } - fv := resStruct.Field(i) - if !fv.IsZero() { - continue - } - v, err := c.resolve(sf.Type) - if err != nil { - missing = append(missing, fmt.Sprintf("%s (%s): %v", sf.Name, sf.Type, err)) - continue - } - - // Evaluate, but do not set underscore field names - if sf.Name != "_" { - fv.Set(v) - } - } - if len(missing) > 0 { - return fmt.Errorf("failed to build result fields:\n - %s", strings.Join(missing, "\n - ")) - } - return nil -} - -func (c *collector) collect(v reflect.Value, path string) error { - if !v.IsValid() { - return nil - } - // Deref pointers - for v.Kind() == reflect.Pointer { - if v.IsNil() { - return nil - } - v = v.Elem() - } - if v.Kind() != reflect.Struct { - return nil - } - - t := v.Type() - for i := 0; i < t.NumField(); i++ { - sf := t.Field(i) - if sf.PkgPath != "" { // unexported - continue - } - fv := v.Field(i) - name := sf.Name - if path != "" { - name = path + "." + sf.Name - } - - // Provide[T] (singular) must be recognized BEFORE treating structs as namespaces. - if pi, ok := asProvide(fv); ok { - outT := pi.diOutType() - payload := pi.diPayload() - if payload == nil { - c.values[outT] = reflect.Zero(outT) - continue - } - pt := reflect.TypeOf(payload) - if pt.Kind() == reflect.Func { - if err := validateCtorSignature(pt, name); err != nil { - return err - } - c.providers[pt.Out(0)] = append(c.providers[pt.Out(0)], ctor{name: name, fn: reflect.ValueOf(payload), out: pt.Out(0)}) - } else { - c.values[pt] = reflect.ValueOf(payload) - } - continue - } - - // Namespace recursion for embedded structs - if sf.Anonymous && sf.Type.Kind() == reflect.Struct { - // Provide access to the nested config value itself - c.values[sf.Type] = fv - - if err := c.collect(fv, name); err != nil { - return err - } - continue - } - - // []Provide[T] (list) - if fv.Kind() == reflect.Slice && fv.Type().Elem().Kind() == reflect.Struct { - if provideElem, ok := reflect.New(fv.Type().Elem()).Elem().Interface().(provideI); ok { - if fv.Len() == 0 && !fv.IsNil() { - // Set presence of empty list - outT := provideElem.diOutType() - c.listPresence[outT] = true - } - - for j := 0; j < fv.Len(); j++ { - pi := fv.Index(j).Interface().(provideI) - outT := pi.diOutType() - c.listPresence[outT] = true - payload := pi.diPayload() - if payload == nil { - c.listValues[outT] = append(c.listValues[outT], reflect.Zero(outT)) - continue - } - pt := reflect.TypeOf(payload) - if pt.Kind() == reflect.Func { - if err := validateCtorSignature(pt, fmt.Sprintf("%s[%d]", name, j)); err != nil { - return err - } - c.listProviders[pt.Out(0)] = append(c.listProviders[pt.Out(0)], ctor{ - name: fmt.Sprintf("%s[%d]", name, j), - fn: reflect.ValueOf(payload), - out: pt.Out(0), - }) - } else { - c.listValues[pt] = append(c.listValues[pt], reflect.ValueOf(payload)) - } - } - continue - } - } - - // Fallback to earlier forms (singular func/value; list of funcs/values) - switch sf.Type.Kind() { - case reflect.Func: - ft := fv.Type() - if err := validateCtorSignature(ft, name); err != nil { - return err - } - c.providers[ft.Out(0)] = append(c.providers[ft.Out(0)], ctor{name: name, fn: fv, out: ft.Out(0)}) - - case reflect.Slice: - elemT := sf.Type.Elem() - if elemT.Kind() == reflect.Func { - for j := 0; j < fv.Len(); j++ { - fn := fv.Index(j) - ft := fn.Type() - if err := validateCtorSignature(ft, fmt.Sprintf("%s[%d]", name, j)); err != nil { - return err - } - c.listProviders[ft.Out(0)] = append(c.listProviders[ft.Out(0)], ctor{ - name: fmt.Sprintf("%s[%d]", name, j), - fn: fn, out: ft.Out(0), - }) - } - } else { - if fv.Len() == 0 { - c.listPresence[elemT] = true // explicit empty list present - } - for j := 0; j < fv.Len(); j++ { - vj := fv.Index(j) - c.listValues[vj.Type()] = append(c.listValues[vj.Type()], vj) - } - } - - default: - // preprovided singular instance (non-zero only) - if !fv.IsZero() { - c.values[fv.Type()] = fv - } - } - } - return nil -} - -func (c *collector) resolve(t reflect.Type) (reflect.Value, error) { - // Cached exact? - if v, ok := c.values[t]; ok { - return v, nil - } - - // Slice resolution []T - if t.Kind() == reflect.Slice { - elem := t.Elem() - var elems []reflect.Value - found := false - - // Explicit list values (concrete types assignable to elem) - for haveT, vals := range c.listValues { - if isAssignableOrImpl(haveT, elem) { - found = true - elems = append(elems, vals...) - } - } - // From list constructors whose out is assignable to elem - for outT, ctors := range c.listProviders { - if !isAssignableOrImpl(outT, elem) { - continue - } - found = true - for _, ctor := range ctors { - if c.resolving[t] { - return reflect.Value{}, fmt.Errorf("dependency cycle detected at %s", t) - } - c.resolving[t] = true - ft := ctor.fn.Type() - args := make([]reflect.Value, ft.NumIn()) - for i := 0; i < ft.NumIn(); i++ { - paramT := ft.In(i) - arg, err := c.resolve(paramT) - if err != nil { - delete(c.resolving, t) - return reflect.Value{}, fmt.Errorf("%s depends on %s: %w", ctor.name, paramT, err) - } - if !isAssignableOrImpl(arg.Type(), paramT) { - delete(c.resolving, t) - return reflect.Value{}, fmt.Errorf("%s: cannot use %s as %s", ctor.name, arg.Type(), paramT) - } - args[i] = arg - } - outs := ctor.fn.Call(args) - delete(c.resolving, t) - if len(outs) == 2 && !outs[1].IsNil() { - return reflect.Value{}, fmt.Errorf("%s error: %w", ctor.name, outs[1].Interface().(error)) - } - elems = append(elems, outs[0]) - } - } - - // If an explicit provider for elem T exists (e.g., []Provide[T] or []T present but empty), - // we should still succeed with an empty slice. - if !found { - if c.listPresence[elem] { - c.values[t] = reflect.MakeSlice(t, 0, 0) - return c.values[t], nil - } - return reflect.Value{}, fmt.Errorf("no provider for %s", t) - } - - slice := reflect.MakeSlice(t, 0, len(elems)) - for _, e := range elems { - slice = reflect.Append(slice, e) - } - c.values[t] = slice - return slice, nil - } - - // Try existing instances for interface targets (singular) - if t.Kind() == reflect.Interface { - for haveT, v := range c.values { - if haveT.Implements(t) { - return v, nil - } - } - } - - // Cycle detection (singular) - if c.resolving[t] { - return reflect.Value{}, fmt.Errorf("dependency cycle detected at %s", t) - } - c.resolving[t] = true - defer delete(c.resolving, t) - - // Pick singular provider(s) - var candidates []ctor - if ps, ok := c.providers[t]; ok { - candidates = append(candidates, ps...) - } else if t.Kind() == reflect.Interface { - for outT, ps := range c.providers { - if outT.Implements(t) { - candidates = append(candidates, ps...) - } - } - } - - if len(candidates) == 0 { - return reflect.Value{}, fmt.Errorf("no provider for %s", t) - } - if len(candidates) > 1 { - var names []string - for _, candidate := range candidates { - names = append(names, candidate.name+" -> "+candidate.out.String()) - } - return reflect.Value{}, fmt.Errorf("ambiguous providers for %s: %s", t, strings.Join(names, ", ")) - } - - impl := candidates[0] - ft := impl.fn.Type() - args := make([]reflect.Value, ft.NumIn()) - for i := 0; i < ft.NumIn(); i++ { - paramT := ft.In(i) - arg, err := c.resolve(paramT) - if err != nil { - return reflect.Value{}, fmt.Errorf("%s depends on %s: %w", impl.name, paramT, err) - } - if !isAssignableOrImpl(arg.Type(), paramT) { - return reflect.Value{}, fmt.Errorf("%s: cannot use %s as %s", impl.name, arg.Type(), paramT) - } - args[i] = arg - } - outs := impl.fn.Call(args) - if len(outs) == 2 { - if !outs[1].IsNil() { - return reflect.Value{}, fmt.Errorf("%s error: %w", impl.name, outs[1].Interface().(error)) - } - c.values[impl.out] = outs[0] - return outs[0], nil - } - c.values[impl.out] = outs[0] - 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) - } - nout := ft.NumOut() - if nout == 1 { - return nil - } - if nout == 2 && isErrorType(ft.Out(1)) { - return nil - } - return fmt.Errorf("constructor %q: must return (T) or (T, error); got %d returns", name, nout) -} - -func isAssignableOrImpl(have, want reflect.Type) bool { - return have.AssignableTo(want) || (want.Kind() == reflect.Interface && have.Implements(want)) -} - -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 - } - x := v.Interface() - pi, ok := x.(provideI) - return pi, ok -} diff --git a/di/di_test.go b/di/di_test.go deleted file mode 100644 index 4fa8274bf..000000000 --- a/di/di_test.go +++ /dev/null @@ -1,364 +0,0 @@ -package di - -import ( - "errors" - "strings" - "testing" -) - -// Test successful build with a simple dependency chain A -> B. -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) - } -} - -// Test successful build with a simple dependency chain A -> B. -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) - } -} diff --git a/go.mod b/go.mod index d3488bc01..e5ae5757d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/libp2p/go-libp2p -go 1.23.8 +go 1.24.0 retract v0.26.1 // Tag was applied incorrectly due to a bug in the release workflow. @@ -69,6 +69,7 @@ require ( ) require ( + git.sr.ht/~marcopolo/di v0.0.3 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 33df5c254..b54768de7 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,8 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +git.sr.ht/~marcopolo/di v0.0.3 h1:ocEl4T3+M4vGYe509gOTXygEfONRdEXrrEOpcj/H32c= +git.sr.ht/~marcopolo/di v0.0.3/go.mod h1:lLURtWN1LBR3r9P+VA9O3SCJ7hBxYDv55YMLP997M7Q= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= diff --git a/p2p/host/blank/blank.go b/p2p/host/blank/blank.go index e87d27161..b1f65b9ee 100644 --- a/p2p/host/blank/blank.go +++ b/p2p/host/blank/blank.go @@ -170,7 +170,11 @@ func (bh *BlankHost) Addrs() []ma.Multiaddr { } func (bh *BlankHost) Close() error { - return bh.N.Close() + var err error + if bh.onStop != nil { + err = bh.Stop() + } + return errors.Join(err, bh.N.Close()) } func (bh *BlankHost) Connect(ctx context.Context, ai peer.AddrInfo) error {