From b7800377e94ecda18cc94dbccac6610533d054eb Mon Sep 17 00:00:00 2001 From: Matthias Friedrich <1573457+matzefriedrich@users.noreply.github.com> Date: Thu, 11 Jul 2024 21:33:34 +0200 Subject: [PATCH] Feature/register di services (#4) * Registers resolver instance * Adds ResolveRequiredService convenience function to resolve and safe-cast objects * Adds test to cover new functionality --- CHANGELOG.md | 2 ++ README.md | 4 +++- pkg/registry.go | 4 +++- pkg/registry_test.go | 1 - pkg/resolver.go | 13 +++++++++++ pkg/resolver_factory_test.go | 42 ++++++++++++++++++++++++++++++++++++ pkg/types/resolver_error.go | 2 ++ 7 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 pkg/resolver_factory_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index bb33d32..65e8078 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Service registrations can be bundled in a `ModuleFunc` to register related types as a unit * Service registry accepts object instances as singleton service registrations +* Adds the `ResolveRequiredService[T]` convenience function that resolves and safe-casts objects +* Registers resolver instance with the registry so that the `Resolver` object can be injected into factory and constructor methods ## v0.2.0 - 2024-07-11 diff --git a/README.md b/README.md index cd635c3..f83a7ef 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This dependency injection package may become your favorite ingredient for your G - ✔️ Constructor injection - ⏳ Injection via field initialization (requires annotation) - ❌ Injection via setter methods + - ✔️ Convenience function to resolve and safe-cast objects: `ResolveRequiredService[T]` - ✔️ Register types with a certain lifetime - ✔️ Singleton - ✔️ Register objects as singletons; use `RegisterInstance[T]` whereby `T` must be an interface type @@ -20,8 +21,9 @@ This dependency injection package may become your favorite ingredient for your G - ✔️ Transient - ✔️ Bundle type registrations as modules to register them via `RegisterModule` as a unit - ✔️ Resolve objects on-demand - - ⏳ Allow consumption of `Resolver` in favor of custom factories + - ✔️ Allow consumption of `Resolver` in favor of custom factories - ⏳ Validate registered services; fail early during application startup if missing registrations are encountered + - ⏳ Provide parameters for non-registered types and data - ⏳ Support multiple service registrations for the same interface - ⏳ Register named services (mutiple services), resolve via `func(key string) any` - ⏳ Resolve list of service diff --git a/pkg/registry.go b/pkg/registry.go index 037b23e..62ac8d1 100644 --- a/pkg/registry.go +++ b/pkg/registry.go @@ -96,7 +96,9 @@ func NewServiceRegistry() types.ServiceRegistry { } func (s *serviceRegistry) BuildResolver() types.Resolver { - return NewResolver(s) + r := NewResolver(s) + _ = RegisterInstance(s, r) + return r } var _ types.ServiceRegistry = &serviceRegistry{} diff --git a/pkg/registry_test.go b/pkg/registry_test.go index e0a510f..c787c58 100644 --- a/pkg/registry_test.go +++ b/pkg/registry_test.go @@ -144,7 +144,6 @@ func Test_Registry_RegisterInstance_registers_object(t *testing.T) { assert.True(t, ok) assert.NotNil(t, actual) assert.Equal(t, reflect.ValueOf(instance).Pointer(), reflect.ValueOf(actual).Pointer()) - } type Foo interface { diff --git a/pkg/resolver.go b/pkg/resolver.go index 5f83a63..2cfb3ce 100644 --- a/pkg/resolver.go +++ b/pkg/resolver.go @@ -12,6 +12,19 @@ type resolver struct { globalInstances *internal.InstanceBag } +func ResolveRequiredService[T any](resolver types.Resolver, ctx context.Context) (T, error) { + var nilInstance T + t := reflect.TypeOf((*T)(nil)).Elem() + if t.Kind() != reflect.Interface { + return nilInstance, types.NewResolverError(types.ErrorActivatorFunctionsMustReturnAnInterface) + } + resolve, err := resolver.Resolve(ctx, types.ServiceType[T]()) + if err != nil { + return nilInstance, err + } + return resolve.(T), err +} + func NewResolver(registry types.ServiceRegistryAccessor) types.Resolver { return &resolver{ registry: registry, diff --git a/pkg/resolver_factory_test.go b/pkg/resolver_factory_test.go new file mode 100644 index 0000000..1291a6e --- /dev/null +++ b/pkg/resolver_factory_test.go @@ -0,0 +1,42 @@ +package pkg + +import ( + "context" + "github.com/matzefriedrich/parsley/internal" + "github.com/matzefriedrich/parsley/pkg/types" + "github.com/stretchr/testify/assert" + "reflect" + "testing" +) + +func Test_Resolver_ResolveRequiredService_factory_function_receives_current_resolver(t *testing.T) { + + // Arrange + sut := NewServiceRegistry() + _ = RegisterSingleton(sut, NewFactory) + + r := sut.BuildResolver() + ctx := internal.NewScopedContext(context.Background()) + + // Act + serviceFactory, _ := ResolveRequiredService[FactoryService](r, ctx) + f := serviceFactory.(*factory) + actual := f.resolver + + // Assert + assert.NotNil(t, serviceFactory) + + assert.NotNil(t, actual) + assert.Equal(t, reflect.ValueOf(r).Pointer(), reflect.ValueOf(actual).Pointer()) +} + +type factory struct { + resolver types.Resolver +} + +type FactoryService interface { +} + +func NewFactory(resolver types.Resolver) FactoryService { + return &factory{resolver: resolver} +} diff --git a/pkg/types/resolver_error.go b/pkg/types/resolver_error.go index 5397b15..4d0407a 100644 --- a/pkg/types/resolver_error.go +++ b/pkg/types/resolver_error.go @@ -10,6 +10,7 @@ const ( ErrorCircularDependencyDetected = "circular dependency detected" ErrorCannotBuildDependencyGraph = "failed to build dependency graph" ErrorInstanceCannotBeNil = "instance cannot be nil" + ErrorServiceTypeMustBeInterface = "service type must be an interface" ) var ( @@ -18,6 +19,7 @@ var ( ErrCannotBuildDependencyGraph = errors.New(ErrorCannotBuildDependencyGraph) ErrCircularDependencyDetected = errors.New(ErrorCircularDependencyDetected) ErrInstanceCannotBeNil = errors.New(ErrorInstanceCannotBeNil) + ErrServiceTypeMustBeInterface = errors.New(ErrorServiceTypeMustBeInterface) ) type ResolverError struct {