mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-10 22:31:32 +02:00
Dependency injection (#81)
* fix: checkin Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com> * fix: add initial DI implementation Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com> * fix: add multi provider Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com> * fix: simplify DI implementation Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com> * fix: simplify provider Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com> * fix: add Switch to function package Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com> * fix: add DI Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com> --------- Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com> Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
168
di/erasure/injector.go
Normal file
168
di/erasure/injector.go
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package erasure
|
||||
|
||||
import (
|
||||
A "github.com/IBM/fp-go/array"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
I "github.com/IBM/fp-go/identity"
|
||||
IG "github.com/IBM/fp-go/identity/generic"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
L "github.com/IBM/fp-go/lazy"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
R "github.com/IBM/fp-go/record"
|
||||
T "github.com/IBM/fp-go/tuple"
|
||||
|
||||
"sync"
|
||||
)
|
||||
|
||||
func providerToEntry(p Provider) T.Tuple2[string, ProviderFactory] {
|
||||
return T.MakeTuple2(p.Provides().Id(), p.Factory())
|
||||
}
|
||||
|
||||
func itemProviderToMap(p Provider) map[string][]ProviderFactory {
|
||||
return R.Singleton(p.Provides().Id(), A.Of(p.Factory()))
|
||||
}
|
||||
|
||||
var (
|
||||
// missingProviderError returns a [ProviderFactory] that fails due to a missing dependency
|
||||
missingProviderError = F.Flow4(
|
||||
Dependency.String,
|
||||
errors.OnSome[string]("no provider for dependency [%s]"),
|
||||
IOE.Left[any, error],
|
||||
F.Constant1[InjectableFactory, IOE.IOEither[error, any]],
|
||||
)
|
||||
|
||||
// missingProviderErrorOrDefault returns the default [ProviderFactory] or an error
|
||||
missingProviderErrorOrDefault = F.Flow3(
|
||||
T.Replicate2[Dependency],
|
||||
T.Map2(Dependency.ProviderFactory, F.Flow2(missingProviderError, F.Constant[ProviderFactory])),
|
||||
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
|
||||
)
|
||||
|
||||
emptyMulti any = A.Empty[any]()
|
||||
|
||||
// emptyMultiDependency returns a [ProviderFactory] for an empty, multi dependency
|
||||
emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOE.Of[error](emptyMulti)))
|
||||
|
||||
// handleMissingProvider covers the case of a missing provider. It either
|
||||
// returns an error or an empty multi value provider
|
||||
handleMissingProvider = F.Flow2(
|
||||
F.Ternary(isMultiDependency, emptyMultiDependency, missingProviderErrorOrDefault),
|
||||
F.Constant[ProviderFactory],
|
||||
)
|
||||
|
||||
// mergeItemProviders is a monoid for item provider factories
|
||||
mergeItemProviders = R.UnionMonoid[string](A.Semigroup[ProviderFactory]())
|
||||
|
||||
// mergeProviders is a monoid for provider factories
|
||||
mergeProviders = R.UnionLastMonoid[string, ProviderFactory]()
|
||||
|
||||
// collectItemProviders create a provider map for item providers
|
||||
collectItemProviders = F.Flow2(
|
||||
A.FoldMap[Provider](mergeItemProviders)(itemProviderToMap),
|
||||
R.Map[string](itemProviderFactory),
|
||||
)
|
||||
|
||||
// collectProviders collects non-item providers
|
||||
collectProviders = F.Flow2(
|
||||
A.Map(providerToEntry),
|
||||
R.FromEntries[string, ProviderFactory],
|
||||
)
|
||||
|
||||
// assembleProviders constructs the provider map for item and non-item providers
|
||||
assembleProviders = F.Flow3(
|
||||
A.Partition(isItemProvider),
|
||||
T.Map2(collectProviders, collectItemProviders),
|
||||
T.Tupled2(mergeProviders.Concat),
|
||||
)
|
||||
)
|
||||
|
||||
// isMultiDependency tests if a dependency is a container dependency
|
||||
func isMultiDependency(dep Dependency) bool {
|
||||
return dep.Flag()&Multi == Multi
|
||||
}
|
||||
|
||||
// isItemProvider tests if a provivder provides a single item
|
||||
func isItemProvider(provider Provider) bool {
|
||||
return provider.Provides().Flag()&Item == 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]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MakeInjector creates an [InjectableFactory] based on a set of [Provider]s
|
||||
func MakeInjector(providers []Provider) InjectableFactory {
|
||||
|
||||
type Result = IOE.IOEither[error, any]
|
||||
type LazyResult = L.Lazy[Result]
|
||||
|
||||
// resolved stores the values resolved so far, key is the string ID
|
||||
// of the token, value is a lazy result
|
||||
var resolved sync.Map
|
||||
|
||||
// provide a mapping for all providers
|
||||
factoryById := assembleProviders(providers)
|
||||
|
||||
// the actual factory, we need lazy initialization
|
||||
var injFct InjectableFactory
|
||||
|
||||
// lazy initialization, so we can cross reference it
|
||||
injFct = func(token Dependency) Result {
|
||||
|
||||
key := token.Id()
|
||||
|
||||
// according to https://github.com/golang/go/issues/44159 this
|
||||
// is the best way to use the sync map
|
||||
actual, loaded := resolved.Load(key)
|
||||
if !loaded {
|
||||
|
||||
computeResult := L.MakeLazy(func() Result {
|
||||
return F.Pipe5(
|
||||
token,
|
||||
T.Replicate2[Dependency],
|
||||
T.Map2(F.Flow3(
|
||||
Dependency.Id,
|
||||
R.Lookup[ProviderFactory, string],
|
||||
I.Ap[O.Option[ProviderFactory]](factoryById),
|
||||
), handleMissingProvider),
|
||||
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
|
||||
IG.Ap[ProviderFactory](injFct),
|
||||
IOE.Memoize[error, any],
|
||||
)
|
||||
})
|
||||
|
||||
actual, _ = resolved.LoadOrStore(key, F.Pipe1(
|
||||
computeResult,
|
||||
L.Memoize[Result],
|
||||
))
|
||||
}
|
||||
|
||||
return actual.(LazyResult)()
|
||||
}
|
||||
|
||||
return injFct
|
||||
}
|
174
di/erasure/provider.go
Normal file
174
di/erasure/provider.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package erasure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
I "github.com/IBM/fp-go/identity"
|
||||
IO "github.com/IBM/fp-go/io"
|
||||
IOG "github.com/IBM/fp-go/io/generic"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
IOO "github.com/IBM/fp-go/iooption"
|
||||
Int "github.com/IBM/fp-go/number/integer"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
R "github.com/IBM/fp-go/record"
|
||||
)
|
||||
|
||||
type InjectableFactory = func(Dependency) IOE.IOEither[error, any]
|
||||
type ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any]
|
||||
|
||||
type paramIndex = map[int]int
|
||||
type paramValue = map[int]any
|
||||
type handler = func(paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue]
|
||||
|
||||
type Provider interface {
|
||||
fmt.Stringer
|
||||
// Provides returns the [Dependency] implemented by this provider
|
||||
Provides() Dependency
|
||||
// Factory returns s function that can create an instance of the dependency based on an [InjectableFactory]
|
||||
Factory() ProviderFactory
|
||||
}
|
||||
|
||||
type provider struct {
|
||||
provides Dependency
|
||||
factory ProviderFactory
|
||||
}
|
||||
|
||||
func (p *provider) Provides() Dependency {
|
||||
return p.provides
|
||||
}
|
||||
|
||||
func (p *provider) Factory() ProviderFactory {
|
||||
return p.factory
|
||||
}
|
||||
|
||||
func (p *provider) String() string {
|
||||
return fmt.Sprintf("Provider for [%s]", p.provides)
|
||||
}
|
||||
|
||||
func MakeProvider(token Dependency, fct ProviderFactory) Provider {
|
||||
return &provider{token, fct}
|
||||
}
|
||||
|
||||
func mapFromToken(idx int, token Dependency) map[int]paramIndex {
|
||||
return R.Singleton(token.Flag()&BehaviourMask, R.Singleton(idx, idx))
|
||||
}
|
||||
|
||||
var (
|
||||
mergeTokenMaps = R.UnionMonoid[int](R.UnionLastSemigroup[int, int]())
|
||||
foldDeps = A.FoldMapWithIndex[Dependency](mergeTokenMaps)(mapFromToken)
|
||||
mergeMaps = R.UnionLastMonoid[int, any]()
|
||||
collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any])
|
||||
|
||||
handlers = map[int]handler{
|
||||
Identity: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return F.Pipe1(
|
||||
mp,
|
||||
IOE.TraverseRecord[int](getAt(res)),
|
||||
)
|
||||
}
|
||||
},
|
||||
Option: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return F.Pipe3(
|
||||
mp,
|
||||
IOG.TraverseRecord[IO.IO[map[int]E.Either[error, any]], paramIndex](getAt(res)),
|
||||
IO.Map(R.Map[int](F.Flow2(
|
||||
E.ToOption[error, any],
|
||||
F.ToAny[O.Option[any]],
|
||||
))),
|
||||
IOE.FromIO[error, paramValue],
|
||||
)
|
||||
}
|
||||
},
|
||||
IOEither: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return F.Pipe2(
|
||||
mp,
|
||||
R.Map[int](F.Flow2(
|
||||
getAt(res),
|
||||
F.ToAny[IOE.IOEither[error, any]],
|
||||
)),
|
||||
IOE.Of[error, paramValue],
|
||||
)
|
||||
}
|
||||
},
|
||||
IOOption: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return F.Pipe2(
|
||||
mp,
|
||||
R.Map[int](F.Flow3(
|
||||
getAt(res),
|
||||
IOO.FromIOEither[error, any],
|
||||
F.ToAny[IOO.IOOption[any]],
|
||||
)),
|
||||
IOE.Of[error, paramValue],
|
||||
)
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type Mapping = map[int]paramIndex
|
||||
|
||||
func getAt[T any](ar []T) func(idx int) T {
|
||||
return func(idx int) T {
|
||||
return ar[idx]
|
||||
}
|
||||
}
|
||||
|
||||
func handleMapping(mp Mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
|
||||
preFct := F.Pipe1(
|
||||
mp,
|
||||
R.Collect(func(idx int, p paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] {
|
||||
return handlers[idx](p)
|
||||
}),
|
||||
)
|
||||
doFct := F.Flow2(
|
||||
I.Flap[IOE.IOEither[error, paramValue], []IOE.IOEither[error, any]],
|
||||
IOE.TraverseArray[error, func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue], paramValue],
|
||||
)
|
||||
postFct := IOE.Map[error](F.Flow2(
|
||||
A.Fold(mergeMaps),
|
||||
collectParams,
|
||||
))
|
||||
|
||||
return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] {
|
||||
return F.Pipe2(
|
||||
preFct,
|
||||
doFct(res),
|
||||
postFct,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MakeProviderFactory constructs a [ProviderFactory] based on a set of [Dependency]s and
|
||||
// a function that accepts the resolved dependencies to return a result
|
||||
func MakeProviderFactory(
|
||||
deps []Dependency,
|
||||
fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory {
|
||||
|
||||
return F.Flow3(
|
||||
F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, any]])(deps),
|
||||
handleMapping(foldDeps(deps)),
|
||||
IOE.Chain(F.Unvariadic0(fct)),
|
||||
)
|
||||
}
|
45
di/erasure/token.go
Normal file
45
di/erasure/token.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package erasure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
const (
|
||||
BehaviourMask = 0x0f
|
||||
Identity = 0 // required dependency
|
||||
Option = 1 // optional dependency
|
||||
IOEither = 2 // lazy and required
|
||||
IOOption = 3 // lazy and optional
|
||||
|
||||
TypeMask = 0xf0
|
||||
Multi = 1 << 4 // array of implementations
|
||||
Item = 2 << 4 // item of a multi token
|
||||
)
|
||||
|
||||
// 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
|
||||
Id() string
|
||||
// Flag returns a tag that identifies the behaviour of the dependency
|
||||
Flag() int
|
||||
// ProviderFactory optionally returns an attached [ProviderFactory] that represents the default for this dependency
|
||||
ProviderFactory() O.Option[ProviderFactory]
|
||||
}
|
33
di/injector.go
Normal file
33
di/injector.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package di
|
||||
|
||||
import (
|
||||
DIE "github.com/IBM/fp-go/di/erasure"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IG "github.com/IBM/fp-go/identity/generic"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
RIOE "github.com/IBM/fp-go/readerioeither"
|
||||
)
|
||||
|
||||
// Resolve performs a type safe resolution of a dependency
|
||||
func Resolve[T any](token InjectionToken[T]) RIOE.ReaderIOEither[DIE.InjectableFactory, error, T] {
|
||||
return F.Flow2(
|
||||
IG.Ap[DIE.InjectableFactory](asDependency(token)),
|
||||
IOE.ChainEitherK(token.Unerase),
|
||||
)
|
||||
}
|
166
di/provider.go
Normal file
166
di/provider.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package di
|
||||
|
||||
import (
|
||||
A "github.com/IBM/fp-go/array"
|
||||
DIE "github.com/IBM/fp-go/di/erasure"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
T "github.com/IBM/fp-go/tuple"
|
||||
)
|
||||
|
||||
func lookupAt[T any](idx int, token Dependency[T]) func(params []any) E.Either[error, T] {
|
||||
return F.Flow3(
|
||||
A.Lookup[any](idx),
|
||||
E.FromOption[any](errors.OnNone("No parameter at position %d", idx)),
|
||||
E.Chain(token.Unerase),
|
||||
)
|
||||
}
|
||||
|
||||
func eraseProviderFactory0[R any](f func() IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
|
||||
return func(params ...any) IOE.IOEither[error, any] {
|
||||
return F.Pipe1(
|
||||
f(),
|
||||
IOE.Map[error](F.ToAny[R]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func eraseProviderFactory1[T1 any, R any](
|
||||
d1 Dependency[T1],
|
||||
f func(T1) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
|
||||
ft := T.Tupled1(f)
|
||||
t1 := lookupAt[T1](0, d1)
|
||||
return func(params ...any) IOE.IOEither[error, any] {
|
||||
return F.Pipe3(
|
||||
E.SequenceT1(t1(params)),
|
||||
IOE.FromEither[error, T.Tuple1[T1]],
|
||||
IOE.Chain(ft),
|
||||
IOE.Map[error](F.ToAny[R]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func eraseProviderFactory2[T1, T2 any, R any](
|
||||
d1 Dependency[T1],
|
||||
d2 Dependency[T2],
|
||||
f func(T1, T2) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
|
||||
ft := T.Tupled2(f)
|
||||
t1 := lookupAt[T1](0, d1)
|
||||
t2 := lookupAt[T2](1, d2)
|
||||
return func(params ...any) IOE.IOEither[error, any] {
|
||||
return F.Pipe3(
|
||||
E.SequenceT2(t1(params), t2(params)),
|
||||
IOE.FromEither[error, T.Tuple2[T1, T2]],
|
||||
IOE.Chain(ft),
|
||||
IOE.Map[error](F.ToAny[R]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func MakeProviderFactory0[R any](
|
||||
fct func() IOE.IOEither[error, R],
|
||||
) DIE.ProviderFactory {
|
||||
return DIE.MakeProviderFactory(
|
||||
A.Empty[DIE.Dependency](),
|
||||
eraseProviderFactory0(fct),
|
||||
)
|
||||
}
|
||||
|
||||
// MakeTokenWithDefault0 create a unique `InjectionToken` for a specific type with an attached default provider
|
||||
func MakeTokenWithDefault0[R any](name string, fct func() IOE.IOEither[error, R]) InjectionToken[R] {
|
||||
return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct))
|
||||
}
|
||||
|
||||
func MakeProvider0[R any](
|
||||
token InjectionToken[R],
|
||||
fct func() IOE.IOEither[error, R],
|
||||
) DIE.Provider {
|
||||
return DIE.MakeProvider(
|
||||
token,
|
||||
MakeProviderFactory0(fct),
|
||||
)
|
||||
}
|
||||
|
||||
func MakeProviderFactory1[T1, R any](
|
||||
d1 Dependency[T1],
|
||||
fct func(T1) IOE.IOEither[error, R],
|
||||
) DIE.ProviderFactory {
|
||||
|
||||
return DIE.MakeProviderFactory(
|
||||
A.From[DIE.Dependency](d1),
|
||||
eraseProviderFactory1(d1, fct),
|
||||
)
|
||||
}
|
||||
|
||||
// MakeTokenWithDefault1 create a unique `InjectionToken` for a specific type with an attached default provider
|
||||
func MakeTokenWithDefault1[T1, R any](name string,
|
||||
d1 Dependency[T1],
|
||||
fct func(T1) IOE.IOEither[error, R]) InjectionToken[R] {
|
||||
return MakeTokenWithDefault[R](name, MakeProviderFactory1(d1, fct))
|
||||
}
|
||||
|
||||
func MakeProvider1[T1, R any](
|
||||
token InjectionToken[R],
|
||||
d1 Dependency[T1],
|
||||
fct func(T1) IOE.IOEither[error, R],
|
||||
) DIE.Provider {
|
||||
|
||||
return DIE.MakeProvider(
|
||||
token,
|
||||
MakeProviderFactory1(d1, fct),
|
||||
)
|
||||
}
|
||||
|
||||
func MakeProviderFactory2[T1, T2, R any](
|
||||
d1 Dependency[T1],
|
||||
d2 Dependency[T2],
|
||||
fct func(T1, T2) IOE.IOEither[error, R],
|
||||
) DIE.ProviderFactory {
|
||||
|
||||
return DIE.MakeProviderFactory(
|
||||
A.From[DIE.Dependency](d1, d2),
|
||||
eraseProviderFactory2(d1, d2, fct),
|
||||
)
|
||||
}
|
||||
|
||||
// MakeTokenWithDefault2 create a unique `InjectionToken` for a specific type with an attached default provider
|
||||
func MakeTokenWithDefault2[T1, T2, R any](name string,
|
||||
d1 Dependency[T1],
|
||||
d2 Dependency[T2],
|
||||
fct func(T1, T2) IOE.IOEither[error, R]) InjectionToken[R] {
|
||||
return MakeTokenWithDefault[R](name, MakeProviderFactory2(d1, d2, fct))
|
||||
}
|
||||
|
||||
func MakeProvider2[T1, T2, R any](
|
||||
token InjectionToken[R],
|
||||
d1 Dependency[T1],
|
||||
d2 Dependency[T2],
|
||||
fct func(T1, T2) IOE.IOEither[error, R],
|
||||
) DIE.Provider {
|
||||
|
||||
return DIE.MakeProvider(
|
||||
token,
|
||||
MakeProviderFactory2(d1, d2, fct),
|
||||
)
|
||||
}
|
||||
|
||||
// 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)))
|
||||
}
|
351
di/provider_test.go
Normal file
351
di/provider_test.go
Normal file
@@ -0,0 +1,351 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package di
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
DIE "github.com/IBM/fp-go/di/erasure"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
INJ_KEY2 = MakeToken[string]("INJ_KEY2")
|
||||
INJ_KEY1 = MakeToken[string]("INJ_KEY1")
|
||||
INJ_KEY3 = MakeToken[string]("INJ_KEY3")
|
||||
)
|
||||
|
||||
func TestSimpleProvider(t *testing.T) {
|
||||
|
||||
var staticCount int
|
||||
|
||||
staticValue := func(value string) func() IOE.IOEither[error, string] {
|
||||
return func() IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
staticCount++
|
||||
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dynamicCount int
|
||||
|
||||
dynamicValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicCount++
|
||||
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
|
||||
}
|
||||
}
|
||||
|
||||
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
|
||||
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
|
||||
|
||||
inj := DIE.MakeInjector(A.From(p1, p2))
|
||||
|
||||
i1 := Resolve(INJ_KEY1)
|
||||
i2 := Resolve(INJ_KEY2)
|
||||
|
||||
res := IOE.SequenceT4(
|
||||
i2(inj),
|
||||
i1(inj),
|
||||
i2(inj),
|
||||
i1(inj),
|
||||
)
|
||||
|
||||
r := res()
|
||||
|
||||
assert.True(t, E.IsRight(r))
|
||||
assert.Equal(t, 1, staticCount)
|
||||
assert.Equal(t, 1, dynamicCount)
|
||||
}
|
||||
|
||||
func TestOptionalProvider(t *testing.T) {
|
||||
|
||||
var staticCount int
|
||||
|
||||
staticValue := func(value string) func() IOE.IOEither[error, string] {
|
||||
return func() IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
staticCount++
|
||||
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dynamicCount int
|
||||
|
||||
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicCount++
|
||||
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
|
||||
}
|
||||
}
|
||||
|
||||
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
|
||||
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue)
|
||||
|
||||
inj := DIE.MakeInjector(A.From(p1, p2))
|
||||
|
||||
i1 := Resolve(INJ_KEY1)
|
||||
i2 := Resolve(INJ_KEY2)
|
||||
|
||||
res := IOE.SequenceT4(
|
||||
i2(inj),
|
||||
i1(inj),
|
||||
i2(inj),
|
||||
i1(inj),
|
||||
)
|
||||
|
||||
r := res()
|
||||
|
||||
assert.True(t, E.IsRight(r))
|
||||
assert.Equal(t, 1, staticCount)
|
||||
assert.Equal(t, 1, dynamicCount)
|
||||
}
|
||||
|
||||
func TestOptionalProviderMissingDependency(t *testing.T) {
|
||||
|
||||
var dynamicCount int
|
||||
|
||||
dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicCount++
|
||||
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
|
||||
}
|
||||
}
|
||||
|
||||
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue)
|
||||
|
||||
inj := DIE.MakeInjector(A.From(p2))
|
||||
|
||||
i2 := Resolve(INJ_KEY2)
|
||||
|
||||
res := IOE.SequenceT2(
|
||||
i2(inj),
|
||||
i2(inj),
|
||||
)
|
||||
|
||||
r := res()
|
||||
|
||||
assert.True(t, E.IsRight(r))
|
||||
assert.Equal(t, 1, dynamicCount)
|
||||
}
|
||||
|
||||
func TestProviderMissingDependency(t *testing.T) {
|
||||
|
||||
var dynamicCount int
|
||||
|
||||
dynamicValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicCount++
|
||||
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
|
||||
}
|
||||
}
|
||||
|
||||
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
|
||||
|
||||
inj := DIE.MakeInjector(A.From(p2))
|
||||
|
||||
i2 := Resolve(INJ_KEY2)
|
||||
|
||||
res := IOE.SequenceT2(
|
||||
i2(inj),
|
||||
i2(inj),
|
||||
)
|
||||
|
||||
r := res()
|
||||
|
||||
assert.True(t, E.IsLeft(r))
|
||||
assert.Equal(t, 0, dynamicCount)
|
||||
}
|
||||
|
||||
func TestEagerAndLazyProvider(t *testing.T) {
|
||||
|
||||
var staticCount int
|
||||
|
||||
staticValue := func(value string) func() IOE.IOEither[error, string] {
|
||||
return func() IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
staticCount++
|
||||
return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var dynamicCount int
|
||||
|
||||
dynamicValue := func(value string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
dynamicCount++
|
||||
return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now()))
|
||||
}
|
||||
}
|
||||
|
||||
var lazyEagerCount int
|
||||
|
||||
lazyEager := func(laz IOE.IOEither[error, string], eager string) IOE.IOEither[error, string] {
|
||||
return F.Pipe1(
|
||||
laz,
|
||||
IOE.Chain(func(lazValue string) IOE.IOEither[error, string] {
|
||||
return func() E.Either[error, string] {
|
||||
lazyEagerCount++
|
||||
return E.Of[error](fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now()))
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
|
||||
p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue)
|
||||
p3 := MakeProvider2(INJ_KEY3, INJ_KEY2.IOEither(), INJ_KEY1.Identity(), lazyEager)
|
||||
|
||||
inj := DIE.MakeInjector(A.From(p1, p2, p3))
|
||||
|
||||
i3 := Resolve(INJ_KEY3)
|
||||
|
||||
r := i3(inj)()
|
||||
|
||||
fmt.Println(r)
|
||||
|
||||
assert.True(t, E.IsRight(r))
|
||||
assert.Equal(t, 1, staticCount)
|
||||
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)
|
||||
}
|
||||
|
||||
func TestDependencyOnMultiProvider(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")
|
||||
|
||||
fromMulti := func(val string, multi []string) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("Val: %s, Multi: %s", val, multi))
|
||||
}
|
||||
p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti)
|
||||
|
||||
// populate the injector
|
||||
inj := DIE.MakeInjector(A.From(p1, p2, v1, v2, p3))
|
||||
|
||||
r3 := Resolve(INJ_KEY3)
|
||||
|
||||
v := r3(inj)()
|
||||
|
||||
assert.Equal(t, E.Of[error]("Val: Value3, Multi: [Value1 Value2]"), v)
|
||||
}
|
||||
|
||||
func TestTokenWithDefaultProvider(t *testing.T) {
|
||||
// token without a default
|
||||
injToken1 := MakeToken[string]("Token1")
|
||||
// token with a default
|
||||
injToken2 := MakeTokenWithDefault0("Token2", F.Constant(IOE.Of[error]("Carsten")))
|
||||
// dependency
|
||||
injToken3 := MakeToken[string]("Token3")
|
||||
|
||||
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
|
||||
})
|
||||
|
||||
// populate the injector
|
||||
inj := DIE.MakeInjector(A.From(p3))
|
||||
|
||||
// resolving injToken3 should work and use the default provider for injToken2
|
||||
r1 := Resolve(injToken1)
|
||||
r3 := Resolve(injToken3)
|
||||
|
||||
// inj1 should not be available
|
||||
assert.True(t, E.IsLeft(r1(inj)()))
|
||||
// r3 should work
|
||||
assert.Equal(t, E.Of[error]("Token: Carsten"), r3(inj)())
|
||||
}
|
||||
|
||||
func TestTokenWithDefaultProviderAndOverride(t *testing.T) {
|
||||
// token with a default
|
||||
injToken2 := MakeTokenWithDefault0("Token2", F.Constant(IOE.Of[error]("Carsten")))
|
||||
// dependency
|
||||
injToken3 := MakeToken[string]("Token3")
|
||||
|
||||
p2 := ConstProvider(injToken2, "Override")
|
||||
|
||||
p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("Token: %s", data))
|
||||
})
|
||||
|
||||
// populate the injector
|
||||
inj := DIE.MakeInjector(A.From(p2, p3))
|
||||
|
||||
// resolving injToken3 should work and use the default provider for injToken2
|
||||
r3 := Resolve(injToken3)
|
||||
|
||||
// r3 should work
|
||||
assert.Equal(t, E.Of[error]("Token: Override"), r3(inj)())
|
||||
}
|
196
di/token.go
Normal file
196
di/token.go
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package di
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
DIE "github.com/IBM/fp-go/di/erasure"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
IO "github.com/IBM/fp-go/io"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
IOO "github.com/IBM/fp-go/iooption"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// Dependency describes the relationship to a service, that has a type and
|
||||
// a behaviour such as required, option or lazy
|
||||
type Dependency[T any] interface {
|
||||
DIE.Dependency
|
||||
// Unerase converts a value with erased type signature into a strongly typed value
|
||||
Unerase(val any) E.Either[error, T]
|
||||
}
|
||||
|
||||
// InjectionToken uniquely identifies a dependency by giving it an Id, Type and name
|
||||
type InjectionToken[T any] interface {
|
||||
Dependency[T]
|
||||
// Identity idenifies this dependency as a mandatory, required dependency, it will be resolved eagerly and injected as `T`.
|
||||
// If the dependency cannot be resolved, the resolution process fails
|
||||
Identity() Dependency[T]
|
||||
// Option identifies this dependency as optional, it will be resolved eagerly and injected as `O.Option[T]`.
|
||||
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as `O.None[T]`
|
||||
Option() Dependency[O.Option[T]]
|
||||
// IOEither identifies this dependency as mandatory but it will be resolved lazily as a `IOE.IOEither[error, T]`. This
|
||||
// value is memoized to make sure the dependency is a singleton.
|
||||
// If the dependency cannot be resolved, the resolution process fails
|
||||
IOEither() Dependency[IOE.IOEither[error, T]]
|
||||
// IOOption identifies this dependency as optional but it will be resolved lazily as a `IOO.IOOption[T]`. This
|
||||
// value is memoized to make sure the dependency is a singleton.
|
||||
// If the dependency cannot be resolved, the resolution process continues and the dependency is represented as the none value.
|
||||
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
|
||||
return IO.MakeIO(func() string {
|
||||
return strconv.FormatInt(count.Add(1), 16)
|
||||
})
|
||||
}
|
||||
|
||||
// genId is the common generator of unique string IDs
|
||||
var genId = makeId()
|
||||
|
||||
type token[T any] struct {
|
||||
name string
|
||||
id string
|
||||
flag int
|
||||
toType func(val any) E.Either[error, T]
|
||||
providerFactory O.Option[DIE.ProviderFactory]
|
||||
}
|
||||
|
||||
func (t *token[T]) Id() string {
|
||||
return t.id
|
||||
}
|
||||
|
||||
func (t *token[T]) Flag() int {
|
||||
return t.flag
|
||||
}
|
||||
|
||||
func (t *token[T]) String() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
func (t *token[T]) Unerase(val any) E.Either[error, T] {
|
||||
return t.toType(val)
|
||||
}
|
||||
|
||||
func (t *token[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
|
||||
return t.providerFactory
|
||||
}
|
||||
|
||||
func makeToken[T any](name string, id string, typ int, unerase func(val any) E.Either[error, T], providerFactory O.Option[DIE.ProviderFactory]) Dependency[T] {
|
||||
return &token[T]{name, id, typ, unerase, providerFactory}
|
||||
}
|
||||
|
||||
type injectionToken[T any] struct {
|
||||
token[T]
|
||||
option Dependency[O.Option[T]]
|
||||
ioeither Dependency[IOE.IOEither[error, T]]
|
||||
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
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) Option() Dependency[O.Option[T]] {
|
||||
return i.option
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) IOEither() Dependency[IOE.IOEither[error, T]] {
|
||||
return i.ioeither
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] {
|
||||
return i.iooption
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
|
||||
return i.providerFactory
|
||||
}
|
||||
|
||||
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 makeInjectionToken[T any](name string, providerFactory O.Option[DIE.ProviderFactory]) InjectionToken[T] {
|
||||
id := genId()
|
||||
toIdentity := toType[T]()
|
||||
return &injectionToken[T]{
|
||||
token[T]{name, id, DIE.Identity, toIdentity, providerFactory},
|
||||
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity), providerFactory),
|
||||
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity), providerFactory),
|
||||
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType(toIdentity), providerFactory),
|
||||
}
|
||||
}
|
||||
|
||||
// MakeToken create a unique `InjectionToken` for a specific type
|
||||
func MakeToken[T any](name string) InjectionToken[T] {
|
||||
return makeInjectionToken[T](name, O.None[DIE.ProviderFactory]())
|
||||
}
|
||||
|
||||
// MakeToken create a unique `InjectionToken` for a specific type
|
||||
func MakeTokenWithDefault[T any](name string, providerFactory DIE.ProviderFactory) InjectionToken[T] {
|
||||
return makeInjectionToken[T](name, O.Of(providerFactory))
|
||||
}
|
||||
|
||||
// MakeMultiToken creates a [MultiInjectionToken]
|
||||
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)
|
||||
// empty factory
|
||||
providerFactory := O.None[DIE.ProviderFactory]()
|
||||
// container
|
||||
container := &injectionToken[[]T]{
|
||||
token[[]T]{containerName, id, DIE.Multi | DIE.Identity, toContainer, providerFactory},
|
||||
makeToken[O.Option[[]T]](fmt.Sprintf("Option[%s]", containerName), id, DIE.Multi|DIE.Option, toOptionType(toContainer), providerFactory),
|
||||
makeToken[IOE.IOEither[error, []T]](fmt.Sprintf("IOEither[%s]", containerName), id, DIE.Multi|DIE.IOEither, toIOEitherType(toContainer), providerFactory),
|
||||
makeToken[IOO.IOOption[[]T]](fmt.Sprintf("IOOption[%s]", containerName), id, DIE.Multi|DIE.IOOption, toIOOptionType(toContainer), providerFactory),
|
||||
}
|
||||
// item
|
||||
item := &injectionToken[T]{
|
||||
token[T]{itemName, id, DIE.Item | DIE.Identity, toItem, providerFactory},
|
||||
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", itemName), id, DIE.Item|DIE.Option, toOptionType(toItem), providerFactory),
|
||||
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Item|DIE.IOEither, toIOEitherType(toItem), providerFactory),
|
||||
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", itemName), id, DIE.Item|DIE.IOOption, toIOOptionType(toItem), providerFactory),
|
||||
}
|
||||
// returns the token
|
||||
return &multiInjectionToken[T]{container, item}
|
||||
}
|
76
di/utils.go
Normal file
76
di/utils.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package di
|
||||
|
||||
import (
|
||||
DIE "github.com/IBM/fp-go/di/erasure"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
IOO "github.com/IBM/fp-go/iooption"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// asDependency converts a generic type to a [DIE.Dependency]
|
||||
func asDependency[T DIE.Dependency](t T) DIE.Dependency {
|
||||
return 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](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(
|
||||
item,
|
||||
E.Map[error](O.Of[T]),
|
||||
),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
// toIOEitherType converts an any to an IOEither[error, any] and then to an 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(item)),
|
||||
)
|
||||
}
|
||||
|
||||
// toIOOptionType converts an any to an IOOption[any] and then to an 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(
|
||||
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)),
|
||||
)
|
||||
}
|
83
di/utils_test.go
Normal file
83
di/utils_test.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
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"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
"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), 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(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)), 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(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] {
|
||||
return F.Pipe1(
|
||||
e,
|
||||
E.Chain(func(ioe IOE.IOEither[error, T]) E.Either[error, T] {
|
||||
return ioe()
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestToIOEitherType(t *testing.T) {
|
||||
// shortcuts
|
||||
toIOEitherInt := toIOEitherType(toInt)
|
||||
toIOEitherString := toIOEitherType(toString)
|
||||
// good cases
|
||||
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(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