From d10410a445b715128eeb0f5048dde064f2d774a8 Mon Sep 17 00:00:00 2001 From: Kirill Trofimov Date: Wed, 15 Apr 2026 15:52:51 +0300 Subject: [PATCH] feature: add interface return support --- container.go | 10 +++++-- tests/container_test.go | 63 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/container.go b/container.go index 5331c04..18dc28e 100644 --- a/container.go +++ b/container.go @@ -156,8 +156,8 @@ func (c *Container) analyzeFunction(fnType reflect.Type) (fnSignature, error) { } firstOut := fnType.Out(0) - if firstOut.Kind() != reflect.Pointer { - return fnSignature{}, fmt.Errorf("constructor must return pointer value as first result") + if firstOut.Kind() != reflect.Pointer && firstOut.Kind() != reflect.Interface { + return fnSignature{}, fmt.Errorf("constructor must return pointer or interface value as first result") } if fnType.NumOut() == 2 { @@ -247,6 +247,12 @@ func (c *Container) resolveInterfaces() error { interfaceType := signature.args[i] + // If there's already a constructor that directly returns this interface type, skip resolution + if _, exists := c.typesCtors[interfaceType]; exists { + c.debugf("interface %s has a direct constructor, skipping resolution", interfaceType) + continue + } + // Find implementation implementations := c.findImplementations(interfaceType) if len(implementations) == 0 { diff --git a/tests/container_test.go b/tests/container_test.go index f58d12e..5b794b6 100644 --- a/tests/container_test.go +++ b/tests/container_test.go @@ -219,6 +219,69 @@ var _ = Describe("Container", func() { }) }) + Describe("Interface-Returning Constructors", func() { + It("should support constructor returning interface", func() { + newStorage := func() Storage { + return &FileStorage{path: "/data"} + } + + Expect(container.Provide(newStorage)).To(Succeed()) + + var storage Storage + Expect(container.Resolve(&storage)).To(Succeed()) + Expect(storage).ToNot(BeNil()) + + fs, ok := storage.(*FileStorage) + Expect(ok).To(BeTrue()) + Expect(fs.path).To(Equal("/data")) + }) + + It("should support constructor returning (interface, error)", func() { + newStorage := func() (Storage, error) { + return &FileStorage{path: "/data"}, nil + } + + Expect(container.Provide(newStorage)).To(Succeed()) + + var storage Storage + Expect(container.Resolve(&storage)).To(Succeed()) + Expect(storage).ToNot(BeNil()) + + fs, ok := storage.(*FileStorage) + Expect(ok).To(BeTrue()) + Expect(fs.path).To(Equal("/data")) + }) + + It("should propagate error from (interface, error) constructor", func() { + newStorage := func() (Storage, error) { + return nil, errors.New("storage init failed") + } + + Expect(container.Provide(newStorage)).To(Succeed()) + + var storage Storage + Expect(container.Resolve(&storage)).To(MatchError(ContainSubstring("storage init failed"))) + }) + + It("should use interface-returning constructor as dependency", func() { + newStorage := func() Storage { + return &FileStorage{path: "/injected"} + } + + Expect(container.Provide(newStorage)).To(Succeed()) + Expect(container.Provide(NewDataProcessor)).To(Succeed()) + + var processor *DataProcessor + Expect(container.Resolve(&processor)).To(Succeed()) + Expect(processor).ToNot(BeNil()) + Expect(processor.storage).ToNot(BeNil()) + + fs, ok := processor.storage.(*FileStorage) + Expect(ok).To(BeTrue()) + Expect(fs.path).To(Equal("/injected")) + }) + }) + Describe("Error Handling", func() { It("should handle constructor errors", func() { Expect(container.Provide(NewErrorDatabase)).To(Succeed())