Skip to content

Commit

Permalink
Feature/support non interface types (#10)
Browse files Browse the repository at this point in the history
Adds support for non-interface types (both registration and resolving)

* Allows reflect.Pointer type for registration and activation
* Refactors service_type.go module (adds struct with name and reflected type to replace all usages of refect.Type)
* Adds new tests
  • Loading branch information
matzefriedrich authored Jul 26, 2024
1 parent 3be652b commit 5899c46
Show file tree
Hide file tree
Showing 16 changed files with 185 additions and 57 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

* Adds the `Activate[T]` method which can resolve an instance from an unregistered activator func
* Allows registration and activation of pointer types

### Changes

* Renames the `ServiceType[T]` method to `MakeServiceType[T]`; adds service type representing the reflected type and typename
* Replaces all usages of `reflect.Type` by `ServiceType` in all Parsley interfaces


## v0.5.0 - 2024-07-16
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Though dependency injection is less prevalent in Golang (compared to other langu
## Features

- ✔️ Register types via constructor functions
- ✔️ Resolve objects by interface
- ✔️ Resolve objects by type (both interface and pointer type)
- ✔️ Constructor injection
- ⏳ Injection via field initialization (requires annotation)
- ❌ Injection via setter methods
Expand Down
30 changes: 21 additions & 9 deletions internal/core/function_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package core

import (
"errors"
"fmt"
"github.com/matzefriedrich/parsley/pkg/types"
"reflect"
"strings"
)

type functionInfo struct {
funcType reflect.Type
returnType reflect.Type
parameterTypes []reflect.Type
returnType types.ServiceType
parameterTypes []types.ServiceType
}

var _ types.FunctionInfo = &functionInfo{}
Expand All @@ -35,29 +37,39 @@ func (f functionInfo) Name() string {
return f.funcType.Name()
}

func (f functionInfo) ParameterTypes() []reflect.Type {
func (f functionInfo) ParameterTypes() []types.ServiceType {
return f.parameterTypes
}

func (f functionInfo) ReturnType() reflect.Type {
func (f functionInfo) ReturnType() types.ServiceType {
return f.returnType
}

func returnType(funcType reflect.Type) (reflect.Type, error) {
func (f functionInfo) String() string {
parameterTypeNames := make([]string, len(f.parameterTypes))
for _, t := range f.parameterTypes {
parameterTypeNames = append(parameterTypeNames, t.Name())
}
funcTypeName := f.returnType.ReflectedType().Elem().String()
return fmt.Sprintf("f(%s) %s", strings.Join(parameterTypeNames, ","), funcTypeName)
}

func returnType(funcType reflect.Type) (types.ServiceType, error) {
numReturnValues := funcType.NumOut()
if numReturnValues != 1 {
return nil, errors.New("return type has to have exactly one return value")
}
serviceType := funcType.Out(0)
return serviceType, nil
return types.ServiceTypeFrom(serviceType), nil
}

func parameterTypes(funcType reflect.Type) []reflect.Type {
parameters := make([]reflect.Type, 0)
func parameterTypes(funcType reflect.Type) []types.ServiceType {
parameters := make([]types.ServiceType, 0)
numParameters := funcType.NumIn()
for i := 0; i < numParameters; i++ {
parameterType := funcType.In(i)
parameters = append(parameters, parameterType)
serviceType := types.ServiceTypeFrom(parameterType)
parameters = append(parameters, serviceType)
}
return parameters
}
63 changes: 63 additions & 0 deletions internal/tests/registry_register_instance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package tests

import (
"context"
"github.com/matzefriedrich/parsley/pkg/registration"
"github.com/matzefriedrich/parsley/pkg/resolving"
"github.com/stretchr/testify/assert"
"testing"
)

func Test_Registry_RegisterInstance_accepts_pointer(t *testing.T) {

// Arrange
registry := registration.NewServiceRegistry()

options := NewOptions("value")

// Act
err := registration.RegisterInstance(registry, options)
resolver := resolving.NewResolver(registry)
actual, _ := resolving.ResolveRequiredService[*someOptions](resolver, resolving.NewScopedContext(context.Background()))

// Assert
assert.NoError(t, err)
assert.NotNil(t, actual)
assert.Equal(t, options.value, actual.value)
}

func Test_Registry_RegisterInstance_resolve_object_with_pointer_dependency(t *testing.T) {

// Arrange
registry := registration.NewServiceRegistry()

options := NewOptions("value")
_ = registration.RegisterInstance(registry, options)
_ = registration.RegisterTransient(registry, NewOptionsConsumer)

resolver := resolving.NewResolver(registry)

// Act
actual, _ := resolving.ResolveRequiredService[*optionsConsumer](resolver, resolving.NewScopedContext(context.Background()))

// Assert
assert.NotNil(t, actual)
}

type someOptions struct {
value string
}

func NewOptions(value string) *someOptions {
return &someOptions{value}
}

type optionsConsumer struct {
options *someOptions
}

func NewOptionsConsumer(options *someOptions) *optionsConsumer {
return &optionsConsumer{
options: options,
}
}
10 changes: 5 additions & 5 deletions internal/tests/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ func Test_ServiceRegistry_register_types_with_different_lifetime_behavior(t *tes
_ = registration.RegisterSingleton(sut, NewFoo)
_ = registration.RegisterTransient(sut, NewFooConsumer)

fooRegistered := sut.IsRegistered(registration.ServiceType[Foo]())
fooConsumerRegistered := sut.IsRegistered(registration.ServiceType[FooConsumer]())
fooRegistered := sut.IsRegistered(types.MakeServiceType[Foo]())
fooConsumerRegistered := sut.IsRegistered(types.MakeServiceType[FooConsumer]())

// Assert
assert.True(t, fooRegistered)
Expand Down Expand Up @@ -115,8 +115,8 @@ func Test_Registry_RegisterModule_registers_collection_of_services(t *testing.T)

_ = sut.RegisterModule(fooModule)

fooRegistered := sut.IsRegistered(registration.ServiceType[Foo]())
fooConsumerRegistered := sut.IsRegistered(registration.ServiceType[FooConsumer]())
fooRegistered := sut.IsRegistered(types.MakeServiceType[Foo]())
fooConsumerRegistered := sut.IsRegistered(types.MakeServiceType[FooConsumer]())

// Assert
assert.True(t, fooRegistered)
Expand All @@ -133,7 +133,7 @@ func Test_Registry_RegisterInstance_registers_singleton_service_from_object(t *t
// Act
_ = registration.RegisterInstance(sut, instance)

fooRegistered := sut.IsRegistered(registration.ServiceType[Foo]())
fooRegistered := sut.IsRegistered(types.MakeServiceType[Foo]())

r := resolving.NewResolver(sut)

Expand Down
3 changes: 2 additions & 1 deletion internal/tests/resolver_options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"github.com/matzefriedrich/parsley/pkg/registration"
"github.com/matzefriedrich/parsley/pkg/resolving"
"github.com/matzefriedrich/parsley/pkg/types"
"github.com/stretchr/testify/assert"
"testing"
)
Expand All @@ -20,7 +21,7 @@ func Test_Resolver_ResolveWithOptions_inject_unregistered_service_instance(t *te
r := resolving.NewResolver(sut)

parsleyContext := resolving.NewScopedContext(context.Background())
consumers, _ := r.ResolveWithOptions(parsleyContext, registration.ServiceType[FooConsumer](), resolving.WithInstance[Foo](NewFoo()))
consumers, _ := r.ResolveWithOptions(parsleyContext, types.MakeServiceType[FooConsumer](), resolving.WithInstance[Foo](NewFoo()))
assert.Equal(t, 1, len(consumers))

consumer1 := consumers[0]
Expand Down
2 changes: 1 addition & 1 deletion internal/tests/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func Test_Resolver_Resolve_returns_err_if_circular_dependency_detected(t *testin
scope := resolving.NewScopedContext(context.Background())

// Act
_, err := r.Resolve(scope, registration.ServiceType[fooBar]())
_, err := r.Resolve(scope, types.MakeServiceType[fooBar]())

// Assert
assert.ErrorIs(t, err, types.ErrCircularDependencyDetected)
Expand Down
1 change: 1 addition & 0 deletions pkg/registration/activator.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func CreateServiceActivatorFrom[T any](instance T) (func() T, error) {
switch t.Kind() {
case reflect.Func:
case reflect.Interface:
case reflect.Pointer:
default:
return nil, types.NewRegistryError(types.ErrorActivatorFunctionInvalidReturnType)
}
Expand Down
3 changes: 1 addition & 2 deletions pkg/registration/dependency.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package registration

import (
"github.com/matzefriedrich/parsley/pkg/types"
"reflect"
"sync"
)

Expand Down Expand Up @@ -40,7 +39,7 @@ func (d *dependencyInfo) AddRequiredServiceInfo(child types.DependencyInfo) {
d.children = append(d.children, child)
}

func (d *dependencyInfo) RequiredServiceTypes() []reflect.Type {
func (d *dependencyInfo) RequiredServiceTypes() []types.ServiceType {
return d.registration.RequiredServiceTypes()
}

Expand Down
16 changes: 8 additions & 8 deletions pkg/registration/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ func RegisterSingleton(registry types.ServiceRegistry, activatorFunc any) error
return registry.Register(activatorFunc, types.LifetimeSingleton)
}

func (s *serviceRegistry) addOrUpdateServiceRegistrationListFor(serviceType reflect.Type) types.ServiceRegistrationList {
list, exists := s.registrations[serviceType]
func (s *serviceRegistry) addOrUpdateServiceRegistrationListFor(serviceType types.ServiceType) types.ServiceRegistrationList {
list, exists := s.registrations[serviceType.ReflectedType()]
if exists {
return list
}
list = NewServiceRegistrationList(s.identifierSource)
s.registrations[serviceType] = list
s.registrations[serviceType.ReflectedType()] = list
return list
}

Expand Down Expand Up @@ -60,23 +60,23 @@ func (s *serviceRegistry) RegisterModule(modules ...types.ModuleFunc) error {
return nil
}

func (s *serviceRegistry) IsRegistered(serviceType reflect.Type) bool {
_, found := s.registrations[serviceType]
func (s *serviceRegistry) IsRegistered(serviceType types.ServiceType) bool {
_, found := s.registrations[serviceType.ReflectedType()]
return found
}

func (s *serviceRegistry) TryGetServiceRegistrations(serviceType reflect.Type) (types.ServiceRegistrationList, bool) {
func (s *serviceRegistry) TryGetServiceRegistrations(serviceType types.ServiceType) (types.ServiceRegistrationList, bool) {
if s.IsRegistered(serviceType) == false {
return nil, false
}
list, found := s.registrations[serviceType]
list, found := s.registrations[serviceType.ReflectedType()]
if found && list.IsEmpty() == false {
return list, true
}
return nil, false
}

func (s *serviceRegistry) TryGetSingleServiceRegistration(serviceType reflect.Type) (types.ServiceRegistration, bool) {
func (s *serviceRegistry) TryGetSingleServiceRegistration(serviceType types.ServiceType) (types.ServiceRegistration, bool) {
list, found := s.TryGetServiceRegistrations(serviceType)
if found && list.IsEmpty() == false {
registrations := list.Registrations()
Expand Down
5 changes: 2 additions & 3 deletions pkg/registration/registry_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ package registration

import (
"github.com/matzefriedrich/parsley/pkg/types"
"reflect"
)

type multiRegistryAccessor struct {
registries []types.ServiceRegistryAccessor
}

func (m *multiRegistryAccessor) TryGetSingleServiceRegistration(serviceType reflect.Type) (types.ServiceRegistration, bool) {
func (m *multiRegistryAccessor) TryGetSingleServiceRegistration(serviceType types.ServiceType) (types.ServiceRegistration, bool) {
for _, registry := range m.registries {
registration, ok := registry.TryGetSingleServiceRegistration(serviceType)
if ok {
Expand All @@ -19,7 +18,7 @@ func (m *multiRegistryAccessor) TryGetSingleServiceRegistration(serviceType refl
return nil, false
}

func (m *multiRegistryAccessor) TryGetServiceRegistrations(serviceType reflect.Type) (types.ServiceRegistrationList, bool) {
func (m *multiRegistryAccessor) TryGetServiceRegistrations(serviceType types.ServiceType) (types.ServiceRegistrationList, bool) {
for _, registry := range m.registries {
registration, ok := registry.TryGetServiceRegistrations(serviceType)
if ok {
Expand Down
16 changes: 9 additions & 7 deletions pkg/registration/service_registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ type serviceRegistration struct {
}

type typeInfo struct {
t reflect.Type
t types.ServiceType
name string
}

func newTypeInfo(t reflect.Type) typeInfo {
func newTypeInfo(t types.ServiceType) typeInfo {
return typeInfo{
t: t,
name: t.Name(),
Expand Down Expand Up @@ -66,15 +66,15 @@ func (s *serviceRegistration) LifetimeScope() types.LifetimeScope {
return s.lifetimeScope
}

func (s *serviceRegistration) RequiredServiceTypes() []reflect.Type {
requiredTypes := make([]reflect.Type, len(s.parameters))
func (s *serviceRegistration) RequiredServiceTypes() []types.ServiceType {
requiredTypes := make([]types.ServiceType, len(s.parameters))
for i, p := range s.parameters {
requiredTypes[i] = p.t
}
return requiredTypes
}

func (s *serviceRegistration) ServiceType() reflect.Type {
func (s *serviceRegistration) ServiceType() types.ServiceType {
return s.serviceType.t
}

Expand All @@ -99,9 +99,11 @@ func CreateServiceRegistration(activatorFunc any, lifetimeScope types.LifetimeSc
}

serviceType := info.ReturnType()
switch serviceType.Kind() {
switch serviceType.ReflectedType().Kind() {
case reflect.Func:
return newServiceRegistration(serviceType, lifetimeScope, value), nil
case reflect.Pointer:
fallthrough
case reflect.Interface:
requiredTypes := info.ParameterTypes()
return newServiceRegistration(serviceType, lifetimeScope, value, requiredTypes...), nil
Expand All @@ -110,7 +112,7 @@ func CreateServiceRegistration(activatorFunc any, lifetimeScope types.LifetimeSc
}
}

func newServiceRegistration(serviceType reflect.Type, scope types.LifetimeScope, activatorFunc reflect.Value, parameters ...reflect.Type) *serviceRegistration {
func newServiceRegistration(serviceType types.ServiceType, scope types.LifetimeScope, activatorFunc reflect.Value, parameters ...types.ServiceType) *serviceRegistration {
parameterTypeInfos := make([]typeInfo, len(parameters))
for i, p := range parameters {
parameterTypeInfos[i] = newTypeInfo(p)
Expand Down
7 changes: 0 additions & 7 deletions pkg/registration/service_type.go

This file was deleted.

7 changes: 4 additions & 3 deletions pkg/resolving/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ func ResolveRequiredServices[T any](resolver types.Resolver, ctx context.Context
switch t.Kind() {
case reflect.Func:
case reflect.Interface:
case reflect.Pointer:
default:
return []T{}, types.NewResolverError(types.ErrorActivatorFunctionInvalidReturnType)
}
resolvedInstances, err := resolver.Resolve(ctx, registration.ServiceType[T]())
resolvedInstances, err := resolver.Resolve(ctx, types.MakeServiceType[T]())
if err != nil {
return []T{}, err
}
Expand Down Expand Up @@ -87,11 +88,11 @@ func (r *resolver) createResolverRegistryAccessor(resolverOptions ...types.Resol
return r.registry, nil
}

func (r *resolver) Resolve(ctx context.Context, serviceType reflect.Type) ([]interface{}, error) {
func (r *resolver) Resolve(ctx context.Context, serviceType types.ServiceType) ([]interface{}, error) {
return r.ResolveWithOptions(ctx, serviceType)
}

func (r *resolver) ResolveWithOptions(ctx context.Context, serviceType reflect.Type, resolverOptions ...types.ResolverOptionsFunc) ([]interface{}, error) {
func (r *resolver) ResolveWithOptions(ctx context.Context, serviceType types.ServiceType, resolverOptions ...types.ResolverOptionsFunc) ([]interface{}, error) {

registry, registryErr := r.createResolverRegistryAccessor(resolverOptions...)
if registryErr != nil {
Expand Down
Loading

0 comments on commit 5899c46

Please sign in to comment.