mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-25 22:21:49 +02:00
fix: add multi provider
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
@@ -27,7 +27,6 @@ import (
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
L "github.com/IBM/fp-go/lazy"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
RIOE "github.com/IBM/fp-go/readerioeither"
|
||||
R "github.com/IBM/fp-go/record"
|
||||
T "github.com/IBM/fp-go/tuple"
|
||||
|
||||
@@ -38,12 +37,21 @@ func providerToEntry(p Provider) T.Tuple2[string, ProviderFactory] {
|
||||
return T.MakeTuple2(p.Provides().Id(), p.Factory())
|
||||
}
|
||||
|
||||
var missingProviderError = F.Flow3(
|
||||
func itemProviderToMap(p Provider) map[string][]ProviderFactory {
|
||||
return R.Singleton(p.Provides().Id(), A.Of(p.Factory()))
|
||||
}
|
||||
|
||||
var missingProviderError = F.Flow4(
|
||||
Dependency.String,
|
||||
errors.OnSome[string]("no provider for dependency [%s]"),
|
||||
RIOE.Left[InjectableFactory, any, error],
|
||||
F.Constant[ProviderFactory],
|
||||
IOE.Left[any, error],
|
||||
F.Constant1[InjectableFactory, IOE.IOEither[error, any]],
|
||||
)
|
||||
|
||||
var emptyMulti any = A.Empty[any]()
|
||||
|
||||
var emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOE.Of[error](emptyMulti)))
|
||||
|
||||
func logEntryExit(name string, token Dependency) func() {
|
||||
log.Printf("Entry: [%s] -> [%s]:[%s]", name, token.Id(), token.String())
|
||||
return func() {
|
||||
@@ -51,6 +59,55 @@ func logEntryExit(name string, token Dependency) func() {
|
||||
}
|
||||
}
|
||||
|
||||
// isMultiDependency tests if a dependency is a container dependency
|
||||
func isMultiDependency(dep Dependency) bool {
|
||||
return dep.Type() == Multi
|
||||
}
|
||||
|
||||
var handleMissingProvider = F.Flow2(
|
||||
F.Ternary(isMultiDependency, emptyMultiDependency, missingProviderError),
|
||||
F.Constant[ProviderFactory],
|
||||
)
|
||||
|
||||
// isItemProvider tests if a provivder provides a single item
|
||||
func isItemProvider(provider Provider) bool {
|
||||
return provider.Provides().Type() == Item
|
||||
}
|
||||
|
||||
// itemProviderFactory combines multiple factories into one, returning an array
|
||||
func itemProviderFactory(fcts []ProviderFactory) ProviderFactory {
|
||||
return func(inj InjectableFactory) IOE.IOEither[error, any] {
|
||||
return F.Pipe2(
|
||||
fcts,
|
||||
IOE.TraverseArray(I.Flap[IOE.IOEither[error, any]](inj)),
|
||||
IOE.Map[error](F.ToAny[[]any]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var mergeItemProviders = R.UnionMonoid[string](A.Semigroup[ProviderFactory]())
|
||||
|
||||
// collectItemProviders create a provider map for item providers
|
||||
var collectItemProviders = F.Flow2(
|
||||
A.FoldMap[Provider](mergeItemProviders)(itemProviderToMap),
|
||||
R.Map[string](itemProviderFactory),
|
||||
)
|
||||
|
||||
// collectProviders collects non-item providers
|
||||
var collectProviders = F.Flow2(
|
||||
A.Map(providerToEntry),
|
||||
R.FromEntries[string, ProviderFactory],
|
||||
)
|
||||
|
||||
var mergeProviders = R.UnionLastMonoid[string, ProviderFactory]()
|
||||
|
||||
// assembleProviders constructs the provider map for item and non-item providers
|
||||
var assembleProviders = F.Flow3(
|
||||
A.Partition(isItemProvider),
|
||||
T.Map2(collectProviders, collectItemProviders),
|
||||
T.Tupled2(mergeProviders.Concat),
|
||||
)
|
||||
|
||||
func MakeInjector(providers []Provider) InjectableFactory {
|
||||
|
||||
type Result = IOE.IOEither[error, any]
|
||||
@@ -61,11 +118,7 @@ func MakeInjector(providers []Provider) InjectableFactory {
|
||||
var resolved sync.Map
|
||||
|
||||
// provide a mapping for all providers
|
||||
factoryById := F.Pipe2(
|
||||
providers,
|
||||
A.Map(providerToEntry),
|
||||
R.FromEntries[string, ProviderFactory],
|
||||
)
|
||||
factoryById := assembleProviders(providers)
|
||||
|
||||
// the actual factory, we need lazy initialization
|
||||
var injFct InjectableFactory
|
||||
@@ -91,10 +144,7 @@ func MakeInjector(providers []Provider) InjectableFactory {
|
||||
Dependency.Id,
|
||||
R.Lookup[ProviderFactory, string],
|
||||
I.Ap[O.Option[ProviderFactory]](factoryById),
|
||||
), F.Flow2(
|
||||
Dependency.String,
|
||||
missingProviderError,
|
||||
)),
|
||||
), handleMissingProvider),
|
||||
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
|
||||
IG.Ap[ProviderFactory](injFct),
|
||||
IOE.Memoize[error, any],
|
||||
|
||||
@@ -27,12 +27,12 @@ import (
|
||||
IOO "github.com/IBM/fp-go/iooption"
|
||||
Int "github.com/IBM/fp-go/number/integer"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
RIOE "github.com/IBM/fp-go/readerioeither"
|
||||
R "github.com/IBM/fp-go/record"
|
||||
)
|
||||
|
||||
type InjectableFactory = RIOE.ReaderIOEither[Dependency, error, any]
|
||||
type ProviderFactory = RIOE.ReaderIOEither[InjectableFactory, error, any]
|
||||
type InjectableFactory = func(Dependency) IOE.IOEither[error, any]
|
||||
type ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any]
|
||||
|
||||
type paramIndex = map[int]int
|
||||
|
||||
type Provider interface {
|
||||
|
||||
@@ -20,12 +20,17 @@ import "fmt"
|
||||
type TokenType int
|
||||
|
||||
const (
|
||||
Identity TokenType = iota
|
||||
Option
|
||||
IOEither
|
||||
IOOption
|
||||
Identity TokenType = iota // required dependency
|
||||
Option // optional dependency
|
||||
IOEither // lazy and required
|
||||
IOOption // lazy and optional
|
||||
Multi // array of implementations
|
||||
Item // item of a multi token
|
||||
IOMulti // lazy and multi
|
||||
Unknown
|
||||
)
|
||||
|
||||
// Dependency describes the relationship to a service
|
||||
type Dependency interface {
|
||||
fmt.Stringer
|
||||
// Id returns a unique identifier for a token that can be used as a cache key
|
||||
|
||||
@@ -28,6 +28,6 @@ import (
|
||||
func Resolve[T any](token InjectionToken[T]) RIOE.ReaderIOEither[DIE.InjectableFactory, error, T] {
|
||||
return F.Flow2(
|
||||
IG.Ap[DIE.InjectableFactory](DIE.AsDependency(token)),
|
||||
IOE.ChainEitherK(toType[T]()),
|
||||
IOE.ChainEitherK(token.Unerase),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -116,3 +116,8 @@ func MakeProvider2[T1, T2, R any](
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// ConstProvider simple implementation for a provider with a constant value
|
||||
func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider {
|
||||
return MakeProvider0[R](token, F.Constant(IOE.Of[error](value)))
|
||||
}
|
||||
|
||||
@@ -231,3 +231,48 @@ func TestEagerAndLazyProvider(t *testing.T) {
|
||||
assert.Equal(t, 1, dynamicCount)
|
||||
assert.Equal(t, 1, lazyEagerCount)
|
||||
}
|
||||
|
||||
func TestItemProvider(t *testing.T) {
|
||||
// define a multi token
|
||||
injMulti := MakeMultiToken[string]("configs")
|
||||
|
||||
// provide some values
|
||||
v1 := ConstProvider(injMulti.Item(), "Value1")
|
||||
v2 := ConstProvider(injMulti.Item(), "Value2")
|
||||
// mix in non-multi values
|
||||
p1 := ConstProvider(INJ_KEY1, "Value3")
|
||||
p2 := ConstProvider(INJ_KEY2, "Value4")
|
||||
|
||||
// populate the injector
|
||||
inj := DIE.MakeInjector(A.From(p1, v1, p2, v2))
|
||||
|
||||
// access the multi value
|
||||
multi := Resolve(injMulti.Container())
|
||||
|
||||
multiInj := multi(inj)
|
||||
|
||||
value := multiInj()
|
||||
|
||||
assert.Equal(t, E.Of[error](A.From("Value1", "Value2")), value)
|
||||
}
|
||||
|
||||
func TestEmptyItemProvider(t *testing.T) {
|
||||
// define a multi token
|
||||
injMulti := MakeMultiToken[string]("configs")
|
||||
|
||||
// mix in non-multi values
|
||||
p1 := ConstProvider(INJ_KEY1, "Value3")
|
||||
p2 := ConstProvider(INJ_KEY2, "Value4")
|
||||
|
||||
// populate the injector
|
||||
inj := DIE.MakeInjector(A.From(p1, p2))
|
||||
|
||||
// access the multi value
|
||||
multi := Resolve(injMulti.Container())
|
||||
|
||||
multiInj := multi(inj)
|
||||
|
||||
value := multiInj()
|
||||
|
||||
assert.Equal(t, E.Of[error](A.Empty[string]()), value)
|
||||
}
|
||||
|
||||
54
di/token.go
54
di/token.go
@@ -55,6 +55,14 @@ type InjectionToken[T any] interface {
|
||||
IOOption() Dependency[IOO.IOOption[T]]
|
||||
}
|
||||
|
||||
// MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name
|
||||
type MultiInjectionToken[T any] interface {
|
||||
// Container returns the injection token used to request an array of all provided items
|
||||
Container() InjectionToken[[]T]
|
||||
// Item returns the injection token used to provide an item
|
||||
Item() InjectionToken[T]
|
||||
}
|
||||
|
||||
// makeID creates a generator of unique string IDs
|
||||
func makeId() IO.IO[string] {
|
||||
var count atomic.Int64
|
||||
@@ -100,6 +108,11 @@ type injectionToken[T any] struct {
|
||||
iooption Dependency[IOO.IOOption[T]]
|
||||
}
|
||||
|
||||
type multiInjectionToken[T any] struct {
|
||||
container *injectionToken[[]T]
|
||||
item *injectionToken[T]
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) Identity() Dependency[T] {
|
||||
return i
|
||||
}
|
||||
@@ -116,13 +129,46 @@ func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] {
|
||||
return i.iooption
|
||||
}
|
||||
|
||||
func (m *multiInjectionToken[T]) Container() InjectionToken[[]T] {
|
||||
return m.container
|
||||
}
|
||||
|
||||
func (m *multiInjectionToken[T]) Item() InjectionToken[T] {
|
||||
return m.item
|
||||
}
|
||||
|
||||
// MakeToken create a unique `InjectionToken` for a specific type
|
||||
func MakeToken[T any](name string) InjectionToken[T] {
|
||||
id := genId()
|
||||
toIdentity := toType[T]()
|
||||
return &injectionToken[T]{
|
||||
token[T]{name, id, DIE.Identity, toType[T]()},
|
||||
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType[T]()),
|
||||
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType[T]()),
|
||||
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType[T]()),
|
||||
token[T]{name, id, DIE.Identity, toIdentity},
|
||||
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity)),
|
||||
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity)),
|
||||
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType(toIdentity)),
|
||||
}
|
||||
}
|
||||
|
||||
func MakeMultiToken[T any](name string) MultiInjectionToken[T] {
|
||||
id := genId()
|
||||
toItem := toType[T]()
|
||||
toContainer := toArrayType(toItem)
|
||||
containerName := fmt.Sprintf("Container[%s]", name)
|
||||
itemName := fmt.Sprintf("Item[%s]", name)
|
||||
// container
|
||||
container := &injectionToken[[]T]{
|
||||
token[[]T]{containerName, id, DIE.Multi, toContainer},
|
||||
makeToken[O.Option[[]T]](fmt.Sprintf("Option[%s]", containerName), id, DIE.Unknown, toOptionType(toContainer)),
|
||||
makeToken[IOE.IOEither[error, []T]](fmt.Sprintf("IOEither[%s]", containerName), id, DIE.IOMulti, toIOEitherType(toContainer)),
|
||||
makeToken[IOO.IOOption[[]T]](fmt.Sprintf("IOOption[%s]", containerName), id, DIE.Unknown, toIOOptionType(toContainer)),
|
||||
}
|
||||
// item
|
||||
item := &injectionToken[T]{
|
||||
token[T]{itemName, id, DIE.Item, toItem},
|
||||
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", itemName), id, DIE.Unknown, toOptionType(toItem)),
|
||||
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Unknown, toIOEitherType(toItem)),
|
||||
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", itemName), id, DIE.Unknown, toIOOptionType(toItem)),
|
||||
}
|
||||
// returns the token
|
||||
return &multiInjectionToken[T]{container, item}
|
||||
}
|
||||
|
||||
22
di/utils.go
22
di/utils.go
@@ -23,19 +23,19 @@ import (
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// toType converts and any to a T
|
||||
// toType converts an any to a T
|
||||
func toType[T any]() func(t any) E.Either[error, T] {
|
||||
return E.ToType[T](errors.OnSome[any]("Value of type [%T] cannot be converted."))
|
||||
}
|
||||
|
||||
// toOptionType converts an any to an Option[any] and then to an Option[T]
|
||||
func toOptionType[T any]() func(t any) E.Either[error, O.Option[T]] {
|
||||
func toOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, O.Option[T]] {
|
||||
return F.Flow2(
|
||||
toType[O.Option[any]](),
|
||||
E.Chain(O.Fold(
|
||||
F.Nullary2(O.None[T], E.Of[error, O.Option[T]]),
|
||||
F.Flow2(
|
||||
toType[T](),
|
||||
item,
|
||||
E.Map[error](O.Of[T]),
|
||||
),
|
||||
)),
|
||||
@@ -43,20 +43,28 @@ func toOptionType[T any]() func(t any) E.Either[error, O.Option[T]] {
|
||||
}
|
||||
|
||||
// toIOEitherType converts an any to an IOEither[error, any] and then to an IOEither[error, T]
|
||||
func toIOEitherType[T any]() func(t any) E.Either[error, IOE.IOEither[error, T]] {
|
||||
func toIOEitherType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOE.IOEither[error, T]] {
|
||||
return F.Flow2(
|
||||
toType[IOE.IOEither[error, any]](),
|
||||
E.Map[error](IOE.ChainEitherK(toType[T]())),
|
||||
E.Map[error](IOE.ChainEitherK(item)),
|
||||
)
|
||||
}
|
||||
|
||||
// toIOOptionType converts an any to an IOOption[any] and then to an IOOption[T]
|
||||
func toIOOptionType[T any]() func(t any) E.Either[error, IOO.IOOption[T]] {
|
||||
func toIOOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOO.IOOption[T]] {
|
||||
return F.Flow2(
|
||||
toType[IOO.IOOption[any]](),
|
||||
E.Map[error](IOO.ChainOptionK(F.Flow2(
|
||||
toType[T](),
|
||||
item,
|
||||
E.ToOption[error, T],
|
||||
))),
|
||||
)
|
||||
}
|
||||
|
||||
// toArrayType converts an any to a []T
|
||||
func toArrayType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, []T] {
|
||||
return F.Flow2(
|
||||
toType[[]any](),
|
||||
E.Chain(E.TraverseArray(item)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package di
|
||||
import (
|
||||
"testing"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
@@ -24,24 +25,32 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
toInt = toType[int]()
|
||||
toString = toType[string]()
|
||||
)
|
||||
|
||||
func TestToType(t *testing.T) {
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](10), toType[int]()(any(10)))
|
||||
assert.Equal(t, E.Of[error]("Carsten"), toType[string]()(any("Carsten")))
|
||||
assert.Equal(t, E.Of[error](10), toInt(any(10)))
|
||||
assert.Equal(t, E.Of[error]("Carsten"), toString(any("Carsten")))
|
||||
assert.Equal(t, E.Of[error](O.Of("Carsten")), toType[O.Option[string]]()(any(O.Of("Carsten"))))
|
||||
assert.Equal(t, E.Of[error](O.Of(any("Carsten"))), toType[O.Option[any]]()(any(O.Of(any("Carsten")))))
|
||||
// failure
|
||||
assert.False(t, E.IsRight(toType[int]()(any("Carsten"))))
|
||||
assert.False(t, E.IsRight(toInt(any("Carsten"))))
|
||||
assert.False(t, E.IsRight(toType[O.Option[string]]()(O.Of(any("Carsten")))))
|
||||
}
|
||||
|
||||
func TestToOptionType(t *testing.T) {
|
||||
// shortcuts
|
||||
toOptInt := toOptionType(toInt)
|
||||
toOptString := toOptionType(toString)
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](O.Of(10)), toOptionType[int]()(any(O.Of(any(10)))))
|
||||
assert.Equal(t, E.Of[error](O.Of("Carsten")), toOptionType[string]()(any(O.Of(any("Carsten")))))
|
||||
assert.Equal(t, E.Of[error](O.Of(10)), toOptInt(any(O.Of(any(10)))))
|
||||
assert.Equal(t, E.Of[error](O.Of("Carsten")), toOptString(any(O.Of(any("Carsten")))))
|
||||
// bad cases
|
||||
assert.False(t, E.IsRight(toOptionType[int]()(any(10))))
|
||||
assert.False(t, E.IsRight(toOptionType[int]()(any(O.Of(10)))))
|
||||
assert.False(t, E.IsRight(toOptInt(any(10))))
|
||||
assert.False(t, E.IsRight(toOptInt(any(O.Of(10)))))
|
||||
}
|
||||
|
||||
func invokeIOEither[T any](e E.Either[error, IOE.IOEither[error, T]]) E.Either[error, T] {
|
||||
@@ -54,11 +63,21 @@ func invokeIOEither[T any](e E.Either[error, IOE.IOEither[error, T]]) E.Either[e
|
||||
}
|
||||
|
||||
func TestToIOEitherType(t *testing.T) {
|
||||
// shortcuts
|
||||
toIOEitherInt := toIOEitherType(toInt)
|
||||
toIOEitherString := toIOEitherType(toString)
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](10), invokeIOEither(toIOEitherType[int]()(any(IOE.Of[error](any(10))))))
|
||||
assert.Equal(t, E.Of[error]("Carsten"), invokeIOEither(toIOEitherType[string]()(any(IOE.Of[error](any("Carsten"))))))
|
||||
assert.Equal(t, E.Of[error](10), invokeIOEither(toIOEitherInt(any(IOE.Of[error](any(10))))))
|
||||
assert.Equal(t, E.Of[error]("Carsten"), invokeIOEither(toIOEitherString(any(IOE.Of[error](any("Carsten"))))))
|
||||
// bad cases
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherType[string]()(any(IOE.Of[error](any(10)))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherType[string]()(any(IOE.Of[error]("Carsten"))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherType[string]()(any("Carsten")))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error](any(10)))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error]("Carsten"))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any("Carsten")))))
|
||||
}
|
||||
|
||||
func TestToArrayType(t *testing.T) {
|
||||
// shortcuts
|
||||
toArrayString := toArrayType(toString)
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](A.From("a", "b")), toArrayString(any(A.From(any("a"), any("b")))))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user