Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ watch-test:

bench:
go test -benchmem -count 3 -bench ./...

watch-bench:
reflex -t 50ms -s -- sh -c 'go test -benchmem -count 3 -bench ./...'

Expand All @@ -36,6 +37,7 @@ tools:

lint:
golangci-lint run --timeout 60s --max-same-issues 50 ./...

lint-fix:
golangci-lint run --timeout 60s --max-same-issues 50 --fix ./...

Expand Down
43 changes: 31 additions & 12 deletions di.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package do

import (
"context"
"fmt"
)

func Provide[T any](i *Injector, provider Provider[T]) {
name := generateServiceName[T]()
name := generateServiceNameFromInjector[T](i)

ProvideNamed[T](i, name, provider)
ProvideNamed(i, name, provider)
}

func ProvideNamed[T any](i *Injector, name string, provider Provider[T]) {
Expand All @@ -23,9 +24,9 @@ func ProvideNamed[T any](i *Injector, name string, provider Provider[T]) {
}

func ProvideValue[T any](i *Injector, value T) {
name := generateServiceName[T]()
name := generateServiceNameFromInjector[T](i)

ProvideNamedValue[T](i, name, value)
ProvideNamedValue(i, name, value)
}

func ProvideNamedValue[T any](i *Injector, name string, value T) {
Expand All @@ -41,9 +42,9 @@ func ProvideNamedValue[T any](i *Injector, name string, value T) {
}

func Override[T any](i *Injector, provider Provider[T]) {
name := generateServiceName[T]()
name := generateServiceNameFromInjector[T](i)

OverrideNamed[T](i, name, provider)
OverrideNamed(i, name, provider)
}

func OverrideNamed[T any](i *Injector, name string, provider Provider[T]) {
Expand All @@ -56,9 +57,9 @@ func OverrideNamed[T any](i *Injector, name string, provider Provider[T]) {
}

func OverrideValue[T any](i *Injector, value T) {
name := generateServiceName[T]()
name := generateServiceNameFromInjector[T](i)

OverrideNamedValue[T](i, name, value)
OverrideNamedValue(i, name, value)
}

func OverrideNamedValue[T any](i *Injector, name string, value T) {
Expand All @@ -71,7 +72,7 @@ func OverrideNamedValue[T any](i *Injector, name string, value T) {
}

func Invoke[T any](i *Injector) (T, error) {
name := generateServiceName[T]()
name := generateServiceNameFromInjector[T](i)
return InvokeNamed[T](i, name)
}

Expand Down Expand Up @@ -117,7 +118,7 @@ func invokeImplem[T any](i *Injector, name string) (T, error) {
}

func HealthCheck[T any](i *Injector) error {
name := generateServiceName[T]()
name := generateServiceNameFromInjector[T](i)
return getInjectorOrDefault(i).healthcheckImplem(name)
}

Expand All @@ -126,12 +127,12 @@ func HealthCheckNamed(i *Injector, name string) error {
}

func Shutdown[T any](i *Injector) error {
name := generateServiceName[T]()
name := generateServiceNameFromInjector[T](i)
return getInjectorOrDefault(i).shutdownImplem(name)
}

func MustShutdown[T any](i *Injector) {
name := generateServiceName[T]()
name := generateServiceNameFromInjector[T](i)
must(getInjectorOrDefault(i).shutdownImplem(name))
}

Expand All @@ -142,3 +143,21 @@ func ShutdownNamed(i *Injector, name string) error {
func MustShutdownNamed(i *Injector, name string) {
must(getInjectorOrDefault(i).shutdownImplem(name))
}

func ShutdownContext[T any](ctx context.Context, i *Injector) error {
name := generateServiceNameFromInjector[T](i)
return getInjectorOrDefault(i).shutdownContextImplem(ctx, name)
}

func MustShutdownContext[T any](ctx context.Context, i *Injector) {
name := generateServiceNameFromInjector[T](i)
must(getInjectorOrDefault(i).shutdownContextImplem(ctx, name))
}

func ShutdownNamedContext(ctx context.Context, i *Injector, name string) error {
return getInjectorOrDefault(i).shutdownContextImplem(ctx, name)
}

func MustShutdownNamedContext(ctx context.Context, i *Injector, name string) {
must(getInjectorOrDefault(i).shutdownContextImplem(ctx, name))
}
203 changes: 203 additions & 0 deletions di_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package do

import (
"context"
"fmt"
"testing"

Expand Down Expand Up @@ -153,6 +154,71 @@ func TestInvoke(t *testing.T) {
is.Errorf(err2, "do: service not found")
}

func TestInvokeCircularDependency(t *testing.T) {
t.Run("simple circular dependency", func(t *testing.T) {
type test struct{}

is := assert.New(t)
i := New()

Provide(i, func(i *Injector) (test, error) {
return MustInvoke[test](i), nil
})

is.Panics(func() {
_ = MustInvoke[test](i)
}, "circular dependency was not detected (this message will only be read in here when the test never finishes because of infinite recursion)")

})

t.Run("subtle circular dependency", func(t *testing.T) {
type itest any

type test4 struct {
t itest
}
type test3 struct {
t itest
}
type test2 struct {
t itest
}
type test1 struct {
t itest
}

is := assert.New(t)
i := New()

// test1 -> test2 -> test3 -> test4 --
// ^______________________________|
Provide(i, func(i *Injector) (test1, error) {
return test1{
t: MustInvoke[test2](i),
}, nil
})
Provide(i, func(i *Injector) (test2, error) {
return test2{
t: MustInvoke[test3](i),
}, nil
})
Provide(i, func(i *Injector) (test3, error) {
return test3{
t: MustInvoke[test4](i),
}, nil
})
Provide(i, func(i *Injector) (test4, error) {
return test4{
t: MustInvoke[test1](i),
}, nil
})

is.Panics(func() {
_ = MustInvoke[test1](i)
}, "circular dependency was not detected (this message will only be read in here when the test never finishes because of infinite recursion)")
})
}

func TestInvokeNamed(t *testing.T) {
is := assert.New(t)

Expand Down Expand Up @@ -335,6 +401,143 @@ func TestMustShutdownNamed(t *testing.T) {
})
}

type test struct {
waitForCtx bool
}

func (t test) Shutdown(ctx context.Context) error {
if t.waitForCtx {
<-ctx.Done()
return ctx.Err()
}

return nil
}

func TestShutdownContext(t *testing.T) {
t.Run("context without cancellation returns nil", func(t *testing.T) {
is := assert.New(t)

i := New()

Provide(i, func(i *Injector) (test, error) {
return test{waitForCtx: false}, nil
})

is.NotPanics(func() {
MustInvoke[test](i)
})

ctx := context.Background()
err := ShutdownContext[test](ctx, i)
is.NoError(err)

instance, err := Invoke[test](i)
is.Empty(instance)
is.Error(err)
})

t.Run("cancelled context returns an error", func(t *testing.T) {
is := assert.New(t)

i := New()

Provide(i, func(i *Injector) (test, error) {
return test{waitForCtx: true}, nil
})

is.NotPanics(func() {
MustInvoke[test](i)
})

ctx, cancel := context.WithCancel(context.Background())
cancel()
err := ShutdownContext[test](ctx, i)
is.Error(err)
})
}

func TestMustShutdownContext(t *testing.T) {
t.Run("context without cancellation returns nil", func(t *testing.T) {
is := assert.New(t)

i := New()

Provide(i, func(i *Injector) (test, error) {
return test{waitForCtx: false}, nil
})

is.NotPanics(func() {
MustInvoke[test](i)
})

ctx := context.Background()
is.NotPanics(func() {
MustShutdownContext[test](ctx, i)
})
})

t.Run("cancelled context returns an error", func(t *testing.T) {
is := assert.New(t)

i := New()

Provide(i, func(i *Injector) (test, error) {
return test{waitForCtx: true}, nil
})

is.NotPanics(func() {
MustInvoke[test](i)
})

ctx, cancel := context.WithCancel(context.Background())
cancel()
is.Panics(func() {
MustShutdownContext[test](ctx, i)
})
})
}

func TestShutdownContextNamed(t *testing.T) {
t.Run("cancelled context returns an error", func(t *testing.T) {
is := assert.New(t)

i := New()

ProvideNamedValue(i, "foobar", test{waitForCtx: true})

is.NotPanics(func() {
MustInvokeNamed[test](i, "foobar")
})

ctx, cancel := context.WithCancel(context.Background())
cancel()
err := ShutdownNamedContext(ctx, i, "foobar")
is.Error(err)

})

t.Run("context without cancellation returns nil", func(t *testing.T) {
is := assert.New(t)

i := New()

ProvideNamedValue(i, "foobar", test{waitForCtx: false})

is.NotPanics(func() {
MustInvokeNamed[test](i, "foobar")
})

ctx := context.Background()
err := ShutdownNamedContext(ctx, i, "foobar")
is.NoError(err)

instance, err := InvokeNamed[test](i, "foobar")
is.Empty(instance)
is.Error(err)
})
}

func TestDoubleInjection(t *testing.T) {
is := assert.New(t)

Expand Down
Loading