diff --git a/README.md b/README.md index 6d83eac..ba4f8c8 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,20 @@ the management of object lifetimes and the inversion of control in your applicat - **Concurrency-Safe**: Utilizes a mutex to ensure safe concurrent access to the container. + +- **Placeholder Service Registration**: Designed to simplify scenarios where certain dependencies cannot be resolved at the time of service registration but can be resolved later during runtime. + + +- **Isolated Containers**: Enables the creation of multiple isolated, modular containers to support more scalable and testable architectures, especially for Modular Monoliths. + + +- **Aliases**: A powerful way to register type mappings, allowing one type to be treated as another, typically an interface or a more abstract type. + + +- **Runtime Validation**: Allow for better error handling and validation during the startup or testing phases of an application (circular dependencies, lifetime misalignment, and missing dependencies). + + +- **Graceful Termination**: To ensure graceful application (or context) termination and proper cleanup of resources, including shutting down all resolved objects created during the application (or context) lifetime.
## Installation @@ -99,7 +113,7 @@ var c Counter c = &models.SimpleCounter{} // register -ore.RegisterEagerSingleton[Counter](c) +ore.RegisterSingleton[Counter](c) ctx := context.Background() @@ -115,11 +129,11 @@ c.AddOne() ```go // register -ore.RegisterLazyCreator[Counter](ore.Scoped, &models.SimpleCounter{}) +ore.RegisterCreator[Counter](ore.Scoped, &models.SimpleCounter{}) // OR -//ore.RegisterLazyCreator[Counter](ore.Transient, &models.SimpleCounter{}) -//ore.RegisterLazyCreator[Counter](ore.Singleton, &models.SimpleCounter{}) +//ore.RegisterCreator[Counter](ore.Transient, &models.SimpleCounter{}) +//ore.RegisterCreator[Counter](ore.Singleton, &models.SimpleCounter{}) ctx := context.Background() @@ -142,29 +156,30 @@ fmt.Println("TOTAL: ", c.GetCount()) ```go // register -ore.RegisterLazyFunc[Counter](ore.Scoped, func(ctx context.Context) (Counter, context.Context) { +ore.RegisterFunc[Counter](ore.Scoped, func(ctx context.Context) (Counter, context.Context) { return &models.SimpleCounter{}, ctx }) // OR -//ore.RegisterLazyFunc[Counter](ore.Transient, func(ctx context.Context) (Counter, context.Context) { +//ore.RegisterFunc[Counter](ore.Transient, func(ctx context.Context) (Counter, context.Context) { // return &models.SimpleCounter{}, ctx //}) // Keyed service registration -//ore.RegisterLazyFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) { +//ore.RegisterKeyedFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) { // return &models.SimpleCounter{}, ctx -//}, "name here", 1234) +//}, "key-here") ctx := context.Background() // retrieve c, ctx := ore.Get[Counter](ctx) + c.AddOne() c.AddOne() // Keyed service retrieval -//c, ctx := ore.Get[Counter](ctx, "name here", 1234) +//c, ctx := ore.GetKeyed[Counter](ctx, "key-here") // retrieve again c, ctx = ore.Get[Counter](ctx) @@ -180,15 +195,15 @@ fmt.Println("TOTAL: ", c.GetCount()) ```go // register -ore.RegisterLazyCreator[Counter](ore.Scoped, &models.SimpleCounter{}) +ore.RegisterCreator[Counter](ore.Scoped, &models.SimpleCounter{}) -ore.RegisterLazyCreator[Counter](ore.Scoped, &yetAnotherCounter{}) +ore.RegisterCreator[Counter](ore.Scoped, &yetAnotherCounter{}) -ore.RegisterLazyFunc[Counter](ore.Transient, func(ctx context.Context) (Counter, context.Context) { +ore.RegisterFunc[Counter](ore.Transient, func(ctx context.Context) (Counter, context.Context) { return &models.SimpleCounter{}, ctx }) -ore.RegisterLazyCreator[Counter](ore.Singleton, &yetAnotherCounter{}) +ore.RegisterCreator[Counter](ore.Singleton, &yetAnotherCounter{}) ctx := context.Background() @@ -217,18 +232,18 @@ The last registered implementation takes precedence, so you can register a mock ```go // register -ore.RegisterLazyFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) { +ore.RegisterKeyedFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) { return &models.SimpleCounter{}, ctx -}, "name here", 1234) +}, "key-here") -//ore.RegisterLazyCreator[Counter](ore.Scoped, &models.SimpleCounter{}, "name here", 1234) +//ore.RegisterKeyedCreator[Counter](ore.Scoped, &models.SimpleCounter{}, "key-here") -//ore.RegisterEagerSingleton[Counter](&models.SimpleCounter{}, "name here", 1234) +//ore.RegisterKeyedSingleton[Counter](&models.SimpleCounter{}, "key-here") ctx := context.Background() // Keyed service retrieval -c, ctx := ore.Get[Counter](ctx, "name here", 1234) +c, ctx := ore.GetKeyed[Counter](ctx, "key-here") c.AddOne() // prints out: `TOTAL: 1` @@ -249,13 +264,13 @@ type Trader struct { } //implements IPerson func TestGetInterfaceAlias(t *testing.T) { - ore.RegisterLazyFunc(ore.Scoped, func(ctx context.Context) (*Broker, context.Context) { + ore.RegisterFunc(ore.Scoped, func(ctx context.Context) (*Broker, context.Context) { return &Broker{Name: "Peter"}, ctx }) - ore.RegisterLazyFunc(ore.Scoped, func(ctx context.Context) (*Broker, context.Context) { + ore.RegisterFunc(ore.Scoped, func(ctx context.Context) (*Broker, context.Context) { return &Broker{Name: "John"}, ctx }) - ore.RegisterLazyFunc(ore.Scoped, func(ctx context.Context) (*Trader, context.Context) { + ore.RegisterFunc(ore.Scoped, func(ctx context.Context) (*Trader, context.Context) { return &Trader{Name: "Mary"}, ctx }) @@ -321,9 +336,9 @@ Here how Ore can help you: type Shutdowner interface { Shutdown() } -ore.RegisterEagerSingleton(&Logger{}) //*Logger implements Shutdowner -ore.RegisterEagerSingleton(&SomeRepository{}) //*SomeRepository implements Shutdowner -ore.RegisterEagerSingleton(&SomeService{}, "some_module") //*SomeService implements Shutdowner +ore.RegisterSingleton(&Logger{}) //*Logger implements Shutdowner +ore.RegisterSingleton(&SomeRepository{}) //*SomeRepository implements Shutdowner +ore.RegisterKeyedSingleton(&SomeService{}, "some_module") //*SomeService implements Shutdowner //On application termination, Ore can help to retrieve all the singletons implementation //of the `Shutdowner` interface. @@ -360,7 +375,7 @@ Here how Ore can help you: type Disposer interface { Dispose() } -ore.RegisterLazyCreator(ore.Scoped, &SomeDisposableService{}) //*SomeDisposableService implements Disposer +ore.RegisterCreator(ore.Scoped, &SomeDisposableService{}) //*SomeDisposableService implements Disposer //a new request arrive ctx, cancel := context.WithCancel(context.Background()) @@ -392,24 +407,24 @@ The `ore.GetResolvedScopedInstances[TInterface](context)` function returns a lis ### Multiple Containers (a.k.a Modules) -| DefaultContainer | Custom container | -|------------------|------------------| -| Get | GetFromContainer | -| GetList | GetListFromContainer | +| DefaultContainer | Custom container | +|-----------------------|------------------------------------| +| Get | GetFromContainer | +| GetList | GetListFromContainer | | GetResolvedSingletons | GetResolvedSingletonsFromContainer | -| RegisterAlias | RegisterAliasToContainer | -| RegisterEagerSingleton | RegisterEagerSingletonToContainer | -| RegisterLazyCreator | RegisterLazyCreatorToContainer | -| RegisterLazyFunc | RegisterLazyFuncToContainer | -| RegisterPlaceHolder | RegisterPlaceHolderToContainer | -| ProvideScopedValue | ProvideScopedValueToContainer | +| RegisterAlias | RegisterAliasToContainer | +| RegisterSingleton | RegisterSingletonToContainer | +| RegisterCreator | RegisterCreatorToContainer | +| RegisterFunc | RegisterFuncToContainer | +| RegisterPlaceholder | RegisterPlaceholderToContainer | +| ProvideScopedValue | ProvideScopedValueToContainer | Most of time you only need the Default Container. In rare use case such as the Modular Monolith Architecture, you might want to use multiple containers, one per module. Ore provides minimum support for "module" in this case: ```go //broker module brokerContainer := ore.NewContainer() -ore.RegisterLazyFuncToContainer(brokerContainer, ore.Singleton, func(ctx context.Context) (*Broker, context.Context) { +ore.RegisterFuncToContainer(brokerContainer, ore.Singleton, func(ctx context.Context) (*Broker, context.Context) { brs, ctx = ore.GetFromContainer[*BrokerageSystem](brokerContainer, ctx) return &Broker{brs}, ctx }) @@ -420,7 +435,7 @@ broker, _ := ore.GetFromContainer[*Broker](brokerContainer, context.Background() //trader module traderContainer := ore.NewContainer() -ore.RegisterLazyFuncToContainer(traderContainer, ore.Singleton, func(ctx context.Context) (*Trader, context.Context) { +ore.RegisterFuncToContainer(traderContainer, ore.Singleton, func(ctx context.Context) (*Trader, context.Context) { mkp, ctx = ore.GetFromContainer[*MarketPlace](traderContainer, ctx) return &Trader{mkp}, ctx }) @@ -438,14 +453,14 @@ A common scenario is that your "Service" depends on something which you couldn't ```go //register SomeService which depends on "someConfig" -ore.RegisterLazyFunc[*SomeService](ore.Scoped, func(ctx context.Context) (*SomeService, context.Context) { - someConfig, ctx := ore.Get[string](ctx, "someConfig") +ore.RegisterFunc[*SomeService](ore.Scoped, func(ctx context.Context) (*SomeService, context.Context) { + someConfig, ctx := ore.GetKeyed[string](ctx, "someConfig") return &SomeService{someConfig}, ctx }) //someConfig is unknow at registration time because //this value depends on the future user's request -ore.RegisterPlaceHolder[string]("someConfig") +ore.RegisterKeyedPlaceholder[string]("someConfig") //a new request arrive ctx := context.Background() @@ -455,14 +470,14 @@ ctx = context.WithValue(ctx, "role", "admin") //inject a different somConfig value depending on the request's content userRole := ctx.Value("role").(string) if userRole == "admin" { - ctx = ore.ProvideScopedValue(ctx, "Admin config", "someConfig") + ctx = ore.ProvideKeyedScopedValue(ctx, "Admin config", "someConfig") } else if userRole == "supervisor" { - ctx = ore.ProvideScopedValue(ctx, "Supervisor config", "someConfig") + ctx = ore.ProvideKeyedScopedValue(ctx, "Supervisor config", "someConfig") } else if userRole == "user" { if (isAuthenticatedUser) { - ctx = ore.ProvideScopedValue(ctx, "Public user config", "someConfig") + ctx = ore.ProvideKeyedScopedValue(ctx, "Private user config", "someConfig") } else { - ctx = ore.ProvideScopedValue(ctx, "Private user config", "someConfig") + ctx = ore.ProvideKeyedScopedValue(ctx, "Public user config", "someConfig") } } @@ -473,14 +488,14 @@ fmt.Println(service.someConfig) //"Admin config" ([See full codes here](./examples/placeholderdemo/main.go)) -- `ore.RegisterPlaceHolder[T](key...)` registers a future value with Scoped lifetime. +- `ore.RegisterPlaceholder[T](key...)` registers a future value with Scoped lifetime. - This value will be injected in runtime using the `ProvideScopedValue` function. - Resolving objects which depend on this value will panic if the value has not been provided. - `ore.ProvideScopedValue[T](context, value T, key...)` injects a concrete value into the given context - `ore` can access (`Get()` or `GetList()`) to this value only if the corresponding placeholder (which matches the type and keys) is registered. -- A value provided to a placeholder would never replace value returned by other resolvers. It's the opposite, if a type (and key) could be resolved by a real resolver (such as `RegisterLazyFunc`, `RegisterLazyCreator`...), then the later would take precedent. +- A value provided to a placeholder would never replace value returned by other resolvers. It's the opposite, if a type (and key) could be resolved by a real resolver (such as `RegisterFunc`, `RegisterCreator`...), then the later would take precedent.
@@ -513,7 +528,7 @@ func (gc *genericCounter[T]) GetCount(ctx context.Context) T { ```go // register -ore.RegisterLazyFunc[GenericCounter[int]](ore.Scoped, func(ctx context.Context) (GenericCounter[int], context.Context) { +ore.RegisterFunc[GenericCounter[int]](ore.Scoped, func(ctx context.Context) (GenericCounter[int], context.Context) { return &genericCounter[int]{}, ctx }) @@ -530,15 +545,15 @@ goos: windows goarch: amd64 pkg: github.com/firasdarwish/ore cpu: 13th Gen Intel(R) Core(TM) i9-13900H -BenchmarkRegisterLazyFunc-20 5706694 196.9 ns/op -BenchmarkRegisterLazyCreator-20 6283534 184.5 ns/op -BenchmarkRegisterEagerSingleton-20 5146953 211.5 ns/op -BenchmarkInitialGet-20 3440072 352.1 ns/op -BenchmarkGet-20 9806043 121.8 ns/op -BenchmarkInitialGetList-20 1601787 747.9 ns/op -BenchmarkGetList-20 4237449 282.1 ns/op +BenchmarkRegisterFunc-20 5612482 214.6 ns/op +BenchmarkRegisterCreator-20 6498038 174.1 ns/op +BenchmarkRegisterSingleton-20 5474991 259.1 ns/op +BenchmarkInitialGet-20 2297595 514.3 ns/op +BenchmarkGet-20 9389530 122.1 ns/op +BenchmarkInitialGetList-20 1000000 1072 ns/op +BenchmarkGetList-20 3970850 301.7 ns/op PASS -ok github.com/firasdarwish/ore 11.427s +ok github.com/firasdarwish/ore 10.883s ``` Checkout also [examples/benchperf/README.md](examples/benchperf/README.md) diff --git a/alias_test.go b/alias_test.go index d285b25..8280ba3 100644 --- a/alias_test.go +++ b/alias_test.go @@ -11,10 +11,10 @@ import ( func TestAliasResolverConflict(t *testing.T) { clearAll() - RegisterLazyFunc(Singleton, func(ctx context.Context) (m.IPerson, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (m.IPerson, context.Context) { return &m.Trader{Name: "Peter Singleton"}, ctx }) - RegisterLazyFunc(Transient, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterFunc(Transient, func(ctx context.Context) (*m.Broker, context.Context) { return &m.Broker{Name: "Mary Transient"}, ctx }) @@ -36,10 +36,10 @@ func TestAliasResolverConflict(t *testing.T) { func TestAliasOfAliasIsNotAllow(t *testing.T) { clearAll() - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.Trader, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.Trader, context.Context) { return &m.Trader{Name: "Peter Singleton"}, ctx }) - RegisterLazyFunc(Transient, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterFunc(Transient, func(ctx context.Context) (*m.Broker, context.Context) { return &m.Broker{Name: "Mary Transient"}, ctx }) @@ -58,46 +58,46 @@ func TestAliasOfAliasIsNotAllow(t *testing.T) { func TestAliasWithDifferentScope(t *testing.T) { clearAll() module := "TestGetInterfaceAliasWithDifferentScope" - RegisterLazyFunc(Transient, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterKeyedFunc(Transient, func(ctx context.Context) (*m.Broker, context.Context) { return &m.Broker{Name: "Transient"}, ctx }, module) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterKeyedFunc(Singleton, func(ctx context.Context) (*m.Broker, context.Context) { return &m.Broker{Name: "Singleton"}, ctx }, module) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterKeyedFunc(Scoped, func(ctx context.Context) (*m.Broker, context.Context) { return &m.Broker{Name: "Scoped"}, ctx }, module) RegisterAlias[m.IPerson, *m.Broker]() //link m.IPerson to *m.Broker ctx := context.Background() - person, ctx := Get[m.IPerson](ctx, module) + person, ctx := GetKeyed[m.IPerson](ctx, module) assert.Equal(t, person.(*m.Broker).Name, "Scoped") - personList, _ := GetList[m.IPerson](ctx, module) + personList, _ := GetKeyedList[m.IPerson](ctx, module) assert.Equal(t, len(personList), 3) } func TestAliasIsScopedByKeys(t *testing.T) { clearAll() - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterKeyedFunc(Scoped, func(ctx context.Context) (*m.Broker, context.Context) { return &m.Broker{Name: "Peter1"}, ctx }, "module1") - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterKeyedFunc(Scoped, func(ctx context.Context) (*m.Broker, context.Context) { return &m.Broker{Name: "John1"}, ctx }, "module1") - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.Trader, context.Context) { + RegisterKeyedFunc(Scoped, func(ctx context.Context) (*m.Trader, context.Context) { return &m.Trader{Name: "Mary1"}, ctx }, "module1") - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterKeyedFunc(Scoped, func(ctx context.Context) (*m.Broker, context.Context) { return &m.Broker{Name: "John2"}, ctx }, "module2") - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.Trader, context.Context) { + RegisterKeyedFunc(Scoped, func(ctx context.Context) (*m.Trader, context.Context) { return &m.Trader{Name: "Mary2"}, ctx }, "module2") - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.Trader, context.Context) { + RegisterKeyedFunc(Scoped, func(ctx context.Context) (*m.Trader, context.Context) { return &m.Trader{Name: "Mary3"}, ctx }, "module3") @@ -106,22 +106,22 @@ func TestAliasIsScopedByKeys(t *testing.T) { ctx := context.Background() - person1, ctx := Get[m.IPerson](ctx, "module1") // will return the m.Broker John + person1, ctx := GetKeyed[m.IPerson](ctx, "module1") // will return the m.Broker John assert.Equal(t, person1.(*m.Broker).Name, "John1") - personList1, ctx := GetList[m.IPerson](ctx, "module1") // will return all registered m.Broker and m.Trader + personList1, ctx := GetKeyedList[m.IPerson](ctx, "module1") // will return all registered m.Broker and m.Trader assert.Equal(t, len(personList1), 3) - person2, ctx := Get[m.IPerson](ctx, "module2") // will return the m.Broker John + person2, ctx := GetKeyed[m.IPerson](ctx, "module2") // will return the m.Broker John assert.Equal(t, person2.(*m.Broker).Name, "John2") - personList2, ctx := GetList[m.IPerson](ctx, "module2") // will return all registered m.Broker and m.Trader + personList2, ctx := GetKeyedList[m.IPerson](ctx, "module2") // will return all registered m.Broker and m.Trader assert.Equal(t, len(personList2), 2) - person3, ctx := Get[m.IPerson](ctx, "module3") // will return the m.Trader Mary + person3, ctx := GetKeyed[m.IPerson](ctx, "module3") // will return the m.Trader Mary assert.Equal(t, person3.(*m.Trader).Name, "Mary3") - personList3, ctx := GetList[m.IPerson](ctx, "module3") // will return all registered m.Broker and m.Trader + personList3, ctx := GetKeyedList[m.IPerson](ctx, "module3") // will return all registered m.Broker and m.Trader assert.Equal(t, len(personList3), 1) personListNoModule, _ := GetList[m.IPerson](ctx) // will return all registered m.Broker and m.Trader without keys @@ -138,7 +138,7 @@ func TestGetGenericAlias(t *testing.T) { for _, registrationType := range types { container := NewContainer() - RegisterLazyFuncToContainer(container, registrationType, func(ctx context.Context) (*m.SimpleCounterUint, context.Context) { + RegisterFuncToContainer(container, registrationType, func(ctx context.Context) (*m.SimpleCounterUint, context.Context) { return &m.SimpleCounterUint{}, ctx }) RegisterAliasToContainer[interfaces.SomeCounterGeneric[uint], *m.SimpleCounterUint](container) @@ -157,7 +157,7 @@ func TestGetListGenericAlias(t *testing.T) { container := NewContainer() for i := 0; i < 3; i++ { - RegisterLazyFuncToContainer(container, registrationType, func(ctx context.Context) (*m.SimpleCounterUint, context.Context) { + RegisterFuncToContainer(container, registrationType, func(ctx context.Context) (*m.SimpleCounterUint, context.Context) { return &m.SimpleCounterUint{}, ctx }) } diff --git a/benchmarks_test.go b/benchmarks_test.go index f7a9136..e28c401 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -7,45 +7,45 @@ import ( "testing" ) -func BenchmarkRegisterLazyFunc(b *testing.B) { +func BenchmarkRegisterFunc(b *testing.B) { clearAll() b.ResetTimer() for i := 0; i < b.N; i++ { - RegisterLazyFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) } } -func BenchmarkRegisterLazyCreator(b *testing.B) { +func BenchmarkRegisterCreator(b *testing.B) { clearAll() b.ResetTimer() for i := 0; i < b.N; i++ { - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) } } -func BenchmarkRegisterEagerSingleton(b *testing.B) { +func BenchmarkRegisterSingleton(b *testing.B) { clearAll() b.ResetTimer() for i := 0; i < b.N; i++ { - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) } } func BenchmarkInitialGet(b *testing.B) { clearAll() - RegisterLazyFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) Seal() Validate() @@ -61,15 +61,17 @@ func BenchmarkInitialGet(b *testing.B) { func BenchmarkGet(b *testing.B) { clearAll() - RegisterLazyFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) Seal() Validate() + DefaultContainer.DisableValidation = true + ctx := context.Background() b.ResetTimer() @@ -81,13 +83,13 @@ func BenchmarkGet(b *testing.B) { func BenchmarkInitialGetList(b *testing.B) { clearAll() - RegisterLazyFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) Seal() Validate() @@ -102,15 +104,16 @@ func BenchmarkInitialGetList(b *testing.B) { func BenchmarkGetList(b *testing.B) { clearAll() - RegisterLazyFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) Seal() Validate() + DefaultContainer.DisableValidation = true ctx := context.Background() b.ResetTimer() diff --git a/container_keyed_getters.go b/container_keyed_getters.go new file mode 100644 index 0000000..cac9036 --- /dev/null +++ b/container_keyed_getters.go @@ -0,0 +1,21 @@ +package ore + +import ( + "context" +) + +// GetKeyedFromContainer Retrieves an instance from the given container based on type and key (panics if no valid implementations) +func GetKeyedFromContainer[T any](con *Container, ctx context.Context, key KeyStringer) (T, context.Context) { + if key == nil { + panic(nilKey) + } + return getFromContainer[T](con, ctx, key) +} + +// GetKeyedListFromContainer Retrieves a list of instances from the given container based on type and key +func GetKeyedListFromContainer[T any](con *Container, ctx context.Context, key KeyStringer) ([]T, context.Context) { + if key == nil { + panic(nilKey) + } + return getListFromContainer[T](con, ctx, key) +} diff --git a/container_keyed_registerers.go b/container_keyed_registerers.go new file mode 100644 index 0000000..015315f --- /dev/null +++ b/container_keyed_registerers.go @@ -0,0 +1,51 @@ +package ore + +import ( + "context" +) + +// RegisterKeyedCreatorToContainer Registers a lazily initialized value to the given container using a `Creator[T]` interface +func RegisterKeyedCreatorToContainer[T any](con *Container, lifetime Lifetime, creator Creator[T], key KeyStringer) { + if key == nil { + panic(nilKey) + } + registerCreatorToContainer(con, lifetime, creator, key) +} + +// RegisterKeyedSingletonToContainer Registers an eagerly instantiated singleton value to the given container. +// To register an eagerly instantiated scoped value use [ProvideScopedValueToContainer] +func RegisterKeyedSingletonToContainer[T comparable](con *Container, impl T, key KeyStringer) { + if key == nil { + panic(nilKey) + } + registerSingletonToContainer(con, impl, key) +} + +// RegisterKeyedFuncToContainer Registers a lazily initialized value to the given container using an `Initializer[T]` function signature +func RegisterKeyedFuncToContainer[T any](con *Container, lifetime Lifetime, initializer Initializer[T], key KeyStringer) { + if key == nil { + panic(nilKey) + } + registerFuncToContainer(con, lifetime, initializer, key) +} + +// RegisterKeyedPlaceholderToContainer registers a future value with Scoped lifetime to the given container. +// This value will be injected in runtime using the [ProvideScopedValue] function. +// Resolving objects which depend on this value will panic if the value has not been provided. +// Placeholder with the same type and key can be registered only once. +func RegisterKeyedPlaceholderToContainer[T comparable](con *Container, key KeyStringer) { + if key == nil { + panic(nilKey) + } + registerPlaceholderToContainer[T](con, key) +} + +// ProvideKeyedScopedValueToContainer injects a concrete value into the given context. +// This value will be available only to the given container. And the container can only resolve this value if +// it has the matching (type and key's) Placeholder registered. Checkout the [RegisterPlaceholderToContainer] function for more info. +func ProvideKeyedScopedValueToContainer[T comparable](con *Container, ctx context.Context, value T, key KeyStringer) context.Context { + if key == nil { + panic(nilKey) + } + return provideScopedValueToContainer(con, ctx, value, key) +} diff --git a/container_unkeyed_getters.go b/container_unkeyed_getters.go new file mode 100644 index 0000000..de48931 --- /dev/null +++ b/container_unkeyed_getters.go @@ -0,0 +1,15 @@ +package ore + +import ( + "context" +) + +// GetFromContainer Retrieves an instance from the given container based on type and key (panics if no valid implementations) +func GetFromContainer[T any](con *Container, ctx context.Context) (T, context.Context) { + return getFromContainer[T](con, ctx, nil) +} + +// GetListFromContainer Retrieves a list of instances from the given container based on type and key +func GetListFromContainer[T any](con *Container, ctx context.Context) ([]T, context.Context) { + return getListFromContainer[T](con, ctx, nil) +} diff --git a/container_unkeyed_registerers.go b/container_unkeyed_registerers.go new file mode 100644 index 0000000..add8a83 --- /dev/null +++ b/container_unkeyed_registerers.go @@ -0,0 +1,36 @@ +package ore + +import ( + "context" +) + +// RegisterCreatorToContainer Registers a lazily initialized value to the given container using a `Creator[T]` interface +func RegisterCreatorToContainer[T any](con *Container, lifetime Lifetime, creator Creator[T]) { + registerCreatorToContainer(con, lifetime, creator, nil) +} + +// RegisterSingletonToContainer Registers an eagerly instantiated singleton value to the given container. +// To register an eagerly instantiated scoped value use [ProvideScopedValueToContainer] +func RegisterSingletonToContainer[T comparable](con *Container, impl T) { + registerSingletonToContainer(con, impl, nil) +} + +// RegisterFuncToContainer Registers a lazily initialized value to the given container using an `Initializer[T]` function signature +func RegisterFuncToContainer[T any](con *Container, lifetime Lifetime, initializer Initializer[T]) { + registerFuncToContainer(con, lifetime, initializer, nil) +} + +// RegisterPlaceholderToContainer registers a future value with Scoped lifetime to the given container. +// This value will be injected in runtime using the [ProvideScopedValue] function. +// Resolving objects which depend on this value will panic if the value has not been provided. +// Placeholder with the same type and key can be registered only once. +func RegisterPlaceholderToContainer[T comparable](con *Container) { + registerPlaceholderToContainer[T](con, nil) +} + +// ProvideScopedValueToContainer injects a concrete value into the given context. +// This value will be available only to the given container. And the container can only resolve this value if +// it has the matching (type and key's) Placeholder registered. Checkout the [RegisterPlaceholderToContainer] function for more info. +func ProvideScopedValueToContainer[T comparable](con *Container, ctx context.Context, value T) context.Context { + return provideScopedValueToContainer(con, ctx, value, nil) +} diff --git a/creator_test.go b/creator_test.go index 231103c..831a895 100644 --- a/creator_test.go +++ b/creator_test.go @@ -9,11 +9,11 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRegisterLazyCreator(t *testing.T) { +func TestRegisterCreator(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) c, _ := Get[interfaces.SomeCounter](context.Background()) @@ -26,36 +26,36 @@ func TestRegisterLazyCreator(t *testing.T) { } } -func TestRegisterLazyCreatorNilFuncTransient(t *testing.T) { +func TestRegisterCreatorNilFuncTransient(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterLazyCreator[interfaces.SomeCounter](Transient, nil) + RegisterCreator[interfaces.SomeCounter](Transient, nil) }) } -func TestRegisterLazyCreatorNilFuncScoped(t *testing.T) { +func TestRegisterCreatorNilFuncScoped(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterLazyCreator[interfaces.SomeCounter](Scoped, nil) + RegisterCreator[interfaces.SomeCounter](Scoped, nil) }) } -func TestRegisterLazyCreatorNilFuncSingleton(t *testing.T) { +func TestRegisterCreatorNilFuncSingleton(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterLazyCreator[interfaces.SomeCounter](Singleton, nil) + RegisterCreator[interfaces.SomeCounter](Singleton, nil) }) } -func TestRegisterLazyCreatorMultipleImplementations(t *testing.T) { +func TestRegisterCreatorMultipleImplementations(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) counters, _ := GetList[interfaces.SomeCounter](context.Background()) @@ -65,17 +65,17 @@ func TestRegisterLazyCreatorMultipleImplementations(t *testing.T) { } } -func TestRegisterLazyCreatorMultipleImplementationsKeyed(t *testing.T) { +func TestRegisterCreatorMultipleImplementationsKeyed(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, "firas") + RegisterKeyedCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, "firas") - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, "firas") + RegisterKeyedCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, "firas") - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) - counters, _ := GetList[interfaces.SomeCounter](context.Background(), "firas") + counters, _ := GetKeyedList[interfaces.SomeCounter](context.Background(), "firas") if got := len(counters); got != 2 { t.Errorf("got %v, expected %v", got, 2) @@ -83,12 +83,12 @@ func TestRegisterLazyCreatorMultipleImplementationsKeyed(t *testing.T) { } } -func TestRegisterLazyCreatorSingletonState(t *testing.T) { +func TestRegisterCreatorSingletonState(t *testing.T) { var registrationType Lifetime = Singleton clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) c, _ := Get[interfaces.SomeCounter](context.Background()) c.AddOne() @@ -107,12 +107,12 @@ func TestRegisterLazyCreatorSingletonState(t *testing.T) { } } -func TestRegisterLazyCreatorScopedState(t *testing.T) { +func TestRegisterCreatorScopedState(t *testing.T) { var registrationType Lifetime = Scoped clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) ctx := context.Background() @@ -133,12 +133,12 @@ func TestRegisterLazyCreatorScopedState(t *testing.T) { } } -func TestRegisterLazyCreatorTransientState(t *testing.T) { +func TestRegisterCreatorTransientState(t *testing.T) { var registrationType Lifetime = Transient clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) ctx := context.Background() @@ -159,26 +159,26 @@ func TestRegisterLazyCreatorTransientState(t *testing.T) { } } -func TestRegisterLazyCreatorNilKeyOnRegistering(t *testing.T) { +func TestRegisterCreatorNilKeyOnRegistering(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}, "", nil) + RegisterKeyedCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}, nil) }) } -func TestRegisterLazyCreatorNilKeyOnGetting(t *testing.T) { +func TestRegisterCreatorNilKeyOnGetting(t *testing.T) { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}, "firas") + RegisterKeyedCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}, "firas") assert.Panics(t, func() { - Get[interfaces.SomeCounter](context.Background(), nil) + GetKeyed[interfaces.SomeCounter](context.Background(), nil) }) } -func TestRegisterLazyCreatorGeneric(t *testing.T) { +func TestRegisterCreatorGeneric(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyCreator[interfaces.SomeCounterGeneric[uint]](registrationType, &models.CounterGeneric[uint]{}) + RegisterCreator[interfaces.SomeCounterGeneric[uint]](registrationType, &models.CounterGeneric[uint]{}) c, _ := Get[interfaces.SomeCounterGeneric[uint]](context.Background()) @@ -191,15 +191,15 @@ func TestRegisterLazyCreatorGeneric(t *testing.T) { } } -func TestRegisterLazyCreatorMultipleGenericImplementations(t *testing.T) { +func TestRegisterCreatorMultipleGenericImplementations(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyCreator[interfaces.SomeCounterGeneric[uint]](registrationType, &models.CounterGeneric[uint]{}) + RegisterCreator[interfaces.SomeCounterGeneric[uint]](registrationType, &models.CounterGeneric[uint]{}) - RegisterLazyCreator[interfaces.SomeCounterGeneric[uint]](registrationType, &models.CounterGeneric[uint]{}) + RegisterCreator[interfaces.SomeCounterGeneric[uint]](registrationType, &models.CounterGeneric[uint]{}) - RegisterLazyCreator[interfaces.SomeCounterGeneric[uint]](registrationType, &models.CounterGeneric[uint]{}) + RegisterCreator[interfaces.SomeCounterGeneric[uint]](registrationType, &models.CounterGeneric[uint]{}) counters, _ := GetList[interfaces.SomeCounterGeneric[uint]](context.Background()) diff --git a/default_container_keyed_getters.go b/default_container_keyed_getters.go new file mode 100644 index 0000000..24a8cf5 --- /dev/null +++ b/default_container_keyed_getters.go @@ -0,0 +1,21 @@ +package ore + +import ( + "context" +) + +// GetKeyed Retrieves an instance based on type and key (panics if no valid implementations) +func GetKeyed[T any](ctx context.Context, key KeyStringer) (T, context.Context) { + if key == nil { + panic(nilKey) + } + return getFromContainer[T](DefaultContainer, ctx, key) +} + +// GetKeyedList Retrieves a list of instances based on type and key +func GetKeyedList[T any](ctx context.Context, key KeyStringer) ([]T, context.Context) { + if key == nil { + panic(nilKey) + } + return getListFromContainer[T](DefaultContainer, ctx, key) +} diff --git a/default_container_keyed_registerers.go b/default_container_keyed_registerers.go new file mode 100644 index 0000000..60a99d5 --- /dev/null +++ b/default_container_keyed_registerers.go @@ -0,0 +1,49 @@ +package ore + +import "context" + +// RegisterKeyedCreator Registers a lazily initialized value using a `Creator[T]` interface +func RegisterKeyedCreator[T any](lifetime Lifetime, creator Creator[T], key KeyStringer) { + if key == nil { + panic(nilKey) + } + registerCreatorToContainer[T](DefaultContainer, lifetime, creator, key) +} + +// RegisterKeyedSingleton Registers an eagerly instantiated singleton value +// To register an eagerly instantiated scoped value use [ProvideScopedValue] +func RegisterKeyedSingleton[T comparable](impl T, key KeyStringer) { + if key == nil { + panic(nilKey) + } + registerSingletonToContainer[T](DefaultContainer, impl, key) +} + +// RegisterKeyedFunc Registers a lazily initialized value using an `Initializer[T]` function signature +func RegisterKeyedFunc[T any](lifetime Lifetime, initializer Initializer[T], key KeyStringer) { + if key == nil { + panic(nilKey) + } + registerFuncToContainer(DefaultContainer, lifetime, initializer, key) +} + +// RegisterKeyedPlaceholder registers a future value with Scoped lifetime. +// This value will be injected in runtime using the [ProvideScopedValue] function. +// Resolving objects which depend on this value will panic if the value has not been provided. +// Placeholder with the same type and key can be registered only once. +func RegisterKeyedPlaceholder[T comparable](key KeyStringer) { + if key == nil { + panic(nilKey) + } + registerPlaceholderToContainer[T](DefaultContainer, key) +} + +// ProvideKeyedScopedValue injects a concrete value into the given context. +// This value will be available only to the default container. And the container can only resolve this value if +// it has the matching (type and key's) Placeholder registered. Checkout the [RegisterPlaceholder] function for more info. +func ProvideKeyedScopedValue[T comparable](ctx context.Context, value T, key KeyStringer) context.Context { + if key == nil { + panic(nilKey) + } + return provideScopedValueToContainer(DefaultContainer, ctx, value, key) +} diff --git a/default_container_unkeyed_getters.go b/default_container_unkeyed_getters.go new file mode 100644 index 0000000..3b9e7d0 --- /dev/null +++ b/default_container_unkeyed_getters.go @@ -0,0 +1,15 @@ +package ore + +import ( + "context" +) + +// Get Retrieves an instance based on type and key (panics if no valid implementations) +func Get[T any](ctx context.Context) (T, context.Context) { + return getFromContainer[T](DefaultContainer, ctx, nil) +} + +// GetList Retrieves a list of instances based on type and key +func GetList[T any](ctx context.Context) ([]T, context.Context) { + return getListFromContainer[T](DefaultContainer, ctx, nil) +} diff --git a/default_container_unkeyed_registerers.go b/default_container_unkeyed_registerers.go new file mode 100644 index 0000000..1b5a1bc --- /dev/null +++ b/default_container_unkeyed_registerers.go @@ -0,0 +1,34 @@ +package ore + +import "context" + +// RegisterCreator Registers a lazily initialized value using a `Creator[T]` interface +func RegisterCreator[T any](lifetime Lifetime, creator Creator[T]) { + registerCreatorToContainer[T](DefaultContainer, lifetime, creator, nil) +} + +// RegisterSingleton Registers an eagerly instantiated singleton value +// To register an eagerly instantiated scoped value use [ProvideScopedValue] +func RegisterSingleton[T comparable](impl T) { + registerSingletonToContainer[T](DefaultContainer, impl, nil) +} + +// RegisterFunc Registers a lazily initialized value using an `Initializer[T]` function signature +func RegisterFunc[T any](lifetime Lifetime, initializer Initializer[T]) { + registerFuncToContainer(DefaultContainer, lifetime, initializer, nil) +} + +// RegisterPlaceholder registers a future value with Scoped lifetime. +// This value will be injected in runtime using the [ProvideScopedValue] function. +// Resolving objects which depend on this value will panic if the value has not been provided. +// Placeholder with the same type and key can be registered only once. +func RegisterPlaceholder[T comparable]() { + registerPlaceholderToContainer[T](DefaultContainer, nil) +} + +// ProvideScopedValue injects a concrete value into the given context. +// This value will be available only to the default container. And the container can only resolve this value if +// it has the matching (type and key's) Placeholder registered. Checkout the [RegisterPlaceholder] function for more info. +func ProvideScopedValue[T comparable](ctx context.Context, value T) context.Context { + return provideScopedValueToContainer(DefaultContainer, ctx, value, nil) +} diff --git a/eager_singleton_test.go b/eager_singleton_test.go index 10d6548..76216b2 100644 --- a/eager_singleton_test.go +++ b/eager_singleton_test.go @@ -9,10 +9,10 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRegisterEagerSingleton(t *testing.T) { +func TestRegisterSingleton(t *testing.T) { clearAll() - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) c, _ := Get[interfaces.SomeCounter](context.Background()) @@ -24,19 +24,19 @@ func TestRegisterEagerSingleton(t *testing.T) { } } -func TestRegisterEagerSingletonNilImplementation(t *testing.T) { +func TestRegisterSingletonNilImplementation(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterEagerSingleton[interfaces.SomeCounter](nil) + RegisterSingleton[interfaces.SomeCounter](nil) }) } -func TestRegisterEagerSingletonMultipleImplementations(t *testing.T) { +func TestRegisterSingletonMultipleImplementations(t *testing.T) { clearAll() - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) counters, _ := GetList[interfaces.SomeCounter](context.Background()) @@ -45,25 +45,25 @@ func TestRegisterEagerSingletonMultipleImplementations(t *testing.T) { } } -func TestRegisterEagerSingletonMultipleImplementationsKeyed(t *testing.T) { +func TestRegisterSingletonMultipleImplementationsKeyed(t *testing.T) { clearAll() - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, "firas") - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, "firas") + RegisterKeyedSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, "firas") + RegisterKeyedSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, "firas") - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) - counters, _ := GetList[interfaces.SomeCounter](context.Background(), "firas") + counters, _ := GetKeyedList[interfaces.SomeCounter](context.Background(), "firas") if got := len(counters); got != 2 { t.Errorf("got %v, expected %v", got, 2) } } -func TestRegisterEagerSingletonSingletonState(t *testing.T) { +func TestRegisterSingletonSingletonState(t *testing.T) { clearAll() - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) + RegisterSingleton[interfaces.SomeCounter](&models.SimpleCounter{}) c, _ := Get[interfaces.SomeCounter](context.Background()) c.AddOne() @@ -82,25 +82,25 @@ func TestRegisterEagerSingletonSingletonState(t *testing.T) { } } -func TestRegisterEagerSingletonNilKeyOnRegistering(t *testing.T) { +func TestRegisterSingletonNilKeyOnRegistering(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, nil, "") + RegisterKeyedSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, nil) }) } -func TestRegisterEagerSingletonNilKeyOnGetting(t *testing.T) { +func TestRegisterSingletonNilKeyOnGetting(t *testing.T) { clearAll() - RegisterEagerSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, "firas") + RegisterKeyedSingleton[interfaces.SomeCounter](&models.SimpleCounter{}, "firas") assert.Panics(t, func() { - Get[interfaces.SomeCounter](context.Background(), nil, "") + GetKeyed[interfaces.SomeCounter](context.Background(), nil) }) } -func TestRegisterEagerSingletonGeneric(t *testing.T) { +func TestRegisterSingletonGeneric(t *testing.T) { clearAll() - RegisterEagerSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) + RegisterSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) c, _ := Get[interfaces.SomeCounterGeneric[uint]](context.Background()) @@ -112,12 +112,12 @@ func TestRegisterEagerSingletonGeneric(t *testing.T) { } } -func TestRegisterEagerSingletonMultipleGenericImplementations(t *testing.T) { +func TestRegisterSingletonMultipleGenericImplementations(t *testing.T) { clearAll() - RegisterEagerSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) - RegisterEagerSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) - RegisterEagerSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) + RegisterSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) + RegisterSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) + RegisterSingleton[interfaces.SomeCounterGeneric[uint]](&models.CounterGeneric[uint]{}) counters, _ := GetList[interfaces.SomeCounterGeneric[uint]](context.Background()) diff --git a/examples/benchperf/README.md b/examples/benchperf/README.md index 0783bfe..c139466 100644 --- a/examples/benchperf/README.md +++ b/examples/benchperf/README.md @@ -57,15 +57,15 @@ DGa -.implement..-> G On my machine, Ore always perform faster and use less memory than Samber/Do: ```text -goos: linux +goos: windows goarch: amd64 pkg: examples/benchperf -cpu: 13th Gen Intel(R) Core(TM) i7-1365U -Benchmark_Ore-12 409480 2509 ns/op 2089 B/op 57 allocs/op -Benchmark_OreNoValidation-12 671000 1699 ns/op 1080 B/op 30 allocs/op -Benchmark_SamberDo-12 218361 4825 ns/op 2184 B/op 70 allocs/op +cpu: 13th Gen Intel(R) Core(TM) i9-13900H +Benchmark_Ore-20 448519 2427 ns/op 2233 B/op 57 allocs/op +Benchmark_OreNoValidation-20 814785 1477 ns/op 1080 B/op 30 allocs/op +Benchmark_SamberDo-20 246958 4891 ns/op 2184 B/op 70 allocs/op PASS -ok examples/benchperf 4.222s +ok examples/benchperf 4.016s ``` As any benchmarks, please take these number "relatively" as a general idea: diff --git a/examples/benchperf/internal/DiOre.go b/examples/benchperf/internal/DiOre.go index e248b56..93f7804 100644 --- a/examples/benchperf/internal/DiOre.go +++ b/examples/benchperf/internal/DiOre.go @@ -14,45 +14,45 @@ func BuildContainerOre(disableValidation bool) *ore.Container { } func RegisterToOreContainer(container *ore.Container) { - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (*A, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (*A, context.Context) { b, ctx := ore.GetFromContainer[*B](container, ctx) c, ctx := ore.GetFromContainer[*C](container, ctx) return NewA(b, c), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (*B, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (*B, context.Context) { d, ctx := ore.GetFromContainer[*D](container, ctx) e, ctx := ore.GetFromContainer[*E](container, ctx) return NewB(d, e), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (*C, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (*C, context.Context) { return NewC(), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (*D, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (*D, context.Context) { f, ctx := ore.GetFromContainer[*F](container, ctx) h, ctx := ore.GetFromContainer[H](container, ctx) return NewD(f, h), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (*E, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (*E, context.Context) { gs, ctx := ore.GetListFromContainer[G](container, ctx) return NewE(gs), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Singleton, func(ctx context.Context) (*F, context.Context) { + ore.RegisterFuncToContainer(container, ore.Singleton, func(ctx context.Context) (*F, context.Context) { return NewF(), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (*Ga, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (*Ga, context.Context) { return NewGa(), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Singleton, func(ctx context.Context) (G, context.Context) { + ore.RegisterFuncToContainer(container, ore.Singleton, func(ctx context.Context) (G, context.Context) { return NewGb(), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (G, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (G, context.Context) { return NewGc(), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (G, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (G, context.Context) { ga, ctx := ore.GetFromContainer[*Ga](container, ctx) return NewDGa(ga), ctx }) - ore.RegisterLazyFuncToContainer(container, ore.Transient, func(ctx context.Context) (H, context.Context) { + ore.RegisterFuncToContainer(container, ore.Transient, func(ctx context.Context) (H, context.Context) { return NewHr(), ctx }) } diff --git a/examples/placeholderdemo/main.go b/examples/placeholderdemo/main.go index df90e9a..a536555 100644 --- a/examples/placeholderdemo/main.go +++ b/examples/placeholderdemo/main.go @@ -7,22 +7,20 @@ import ( "github.com/firasdarwish/ore" ) -type UserRole struct { -} type SomeService struct { someConfig string } func main() { //register SomeService which depends on "someConfig" - ore.RegisterLazyFunc[*SomeService](ore.Scoped, func(ctx context.Context) (*SomeService, context.Context) { - someConfig, ctx := ore.Get[string](ctx, "someConfig") + ore.RegisterFunc[*SomeService](ore.Scoped, func(ctx context.Context) (*SomeService, context.Context) { + someConfig, ctx := ore.GetKeyed[string](ctx, "someConfig") return &SomeService{someConfig}, ctx }) - //someConfig is unknow at registration time + //someConfig is unknown at registration time //the value of "someConfig" depends on the future user's request - ore.RegisterPlaceHolder[string]("someConfig") + ore.RegisterKeyedPlaceholder[string]("someConfig") //Seal registration, no further registration is allowed ore.Seal() @@ -36,11 +34,11 @@ func main() { //inject a different config depends on the request, userRole := ctx.Value("role").(string) if userRole == "admin" { - ctx = ore.ProvideScopedValue(ctx, "Admin config", "someConfig") + ctx = ore.ProvideKeyedScopedValue(ctx, "Admin config", "someConfig") } else if userRole == "supervisor" { - ctx = ore.ProvideScopedValue(ctx, "Supervisor config", "someConfig") + ctx = ore.ProvideKeyedScopedValue(ctx, "Supervisor config", "someConfig") } else if userRole == "user" { - ctx = ore.ProvideScopedValue(ctx, "Public user config", "someConfig") + ctx = ore.ProvideKeyedScopedValue(ctx, "Public user config", "someConfig") } service, _ := ore.Get[*SomeService](ctx) diff --git a/examples/shutdownerdemo/main.go b/examples/shutdownerdemo/main.go index d300197..b65bae6 100644 --- a/examples/shutdownerdemo/main.go +++ b/examples/shutdownerdemo/main.go @@ -41,8 +41,8 @@ func (*myScopedRepo) New(ctx context.Context) (*myScopedRepo, context.Context) { } func main() { - ore.RegisterEagerSingleton[*myGlobalRepo](&myGlobalRepo{}) - ore.RegisterLazyCreator(ore.Scoped, &myScopedRepo{}) + ore.RegisterSingleton[*myGlobalRepo](&myGlobalRepo{}) + ore.RegisterCreator(ore.Scoped, &myScopedRepo{}) ore.Validate() diff --git a/examples/simple/main.go b/examples/simple/main.go index 05c9c33..d554e75 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -8,36 +8,36 @@ import ( ) func main() { - ore.RegisterLazyFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) { + ore.RegisterKeyedFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) { fmt.Println("NEWLY INITIALIZED FROM FUNC") return &mycounter{}, ctx }, "firas") - ore.RegisterLazyFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) { + ore.RegisterKeyedFunc[Counter](ore.Singleton, func(ctx context.Context) (Counter, context.Context) { fmt.Println("NEWLY INITIALIZED FROM FUNC") return &mycounter{}, ctx }, "darwish") - ore.RegisterLazyCreator[Counter](ore.Singleton, &mycounter{}) + ore.RegisterCreator[Counter](ore.Singleton, &mycounter{}) cc := &mycounter{} - ore.RegisterEagerSingleton[Counter](cc) + ore.RegisterSingleton[Counter](cc) ctx := context.Background() fmt.Println("STARTED ...") - c, ctx := ore.Get[Counter](ctx, "firas") + c, ctx := ore.GetKeyed[Counter](ctx, "firas") c.AddOne() c.AddOne() - c, ctx = ore.Get[Counter](ctx, "darwish") + c, ctx = ore.GetKeyed[Counter](ctx, "darwish") c.AddOne() c.AddOne() fmt.Printf("Total Count: %v", c.Total()) - ore.RegisterLazyCreator[GenericCounter[uint]](ore.Scoped, &genCounter[uint]{}) + ore.RegisterCreator[GenericCounter[uint]](ore.Scoped, &genCounter[uint]{}) gc, ctx := ore.Get[GenericCounter[uint]](ctx) gc.Add(1) diff --git a/get_list_test.go b/get_list_test.go index 2803345..cd38630 100644 --- a/get_list_test.go +++ b/get_list_test.go @@ -11,7 +11,7 @@ func TestGetList(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}) counters, _ := GetList[interfaces.SomeCounter](context.Background()) @@ -35,12 +35,12 @@ func TestGetListKeyed(t *testing.T) { key := "somekeyhere" - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, key) - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, key) - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, key) - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, "Firas") + RegisterKeyedCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, key) + RegisterKeyedCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, key) + RegisterKeyedCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, key) + RegisterKeyedCreator[interfaces.SomeCounter](registrationType, &models.SimpleCounter{}, "Firas") - counters, _ := GetList[interfaces.SomeCounter](context.Background(), key) + counters, _ := GetKeyedList[interfaces.SomeCounter](context.Background(), key) if got := len(counters); got != 3 { t.Errorf("got %v, expected %v", got, 3) } diff --git a/get_test.go b/get_test.go index 40366ae..5c9be4e 100644 --- a/get_test.go +++ b/get_test.go @@ -15,7 +15,7 @@ func TestGet(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &m.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &m.SimpleCounter{}) c, _ := Get[interfaces.SomeCounter](context.Background()) @@ -32,12 +32,12 @@ func TestGetLatestByDefault(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &m.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &m.SimpleCounter{}) c, _ := Get[interfaces.SomeCounter](context.Background()) c.AddOne() c.AddOne() - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &m.SimpleCounter2{}) + RegisterCreator[interfaces.SomeCounter](registrationType, &m.SimpleCounter2{}) c, _ = Get[interfaces.SomeCounter](context.Background()) c.AddOne() c.AddOne() @@ -63,9 +63,9 @@ func TestGetKeyed(t *testing.T) { key := fmt.Sprintf("keynum: %v", i) - RegisterLazyCreator[interfaces.SomeCounter](registrationType, &m.SimpleCounter{}, key) + RegisterKeyedCreator[interfaces.SomeCounter](registrationType, &m.SimpleCounter{}, key) - c, _ := Get[interfaces.SomeCounter](context.Background(), key) + c, _ := GetKeyed[interfaces.SomeCounter](context.Background(), key) c.AddOne() c.AddOne() @@ -80,24 +80,24 @@ func TestGetResolvedSingletons(t *testing.T) { t.Run("When multiple lifetimes and keys are registered", func(t *testing.T) { //Arrange clearAll() - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { return &m.DisposableService1{Name: "A1"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { return &m.DisposableService1{Name: "A2"}, ctx }) - RegisterEagerSingleton(&m.DisposableService2{Name: "E1"}) - RegisterEagerSingleton(&m.DisposableService2{Name: "E2"}) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterSingleton(&m.DisposableService2{Name: "E1"}) + RegisterSingleton(&m.DisposableService2{Name: "E2"}) + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService3, context.Context) { return &m.DisposableService3{Name: "S1"}, ctx }) - RegisterLazyFunc(Transient, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(Transient, func(ctx context.Context) (*m.DisposableService3, context.Context) { return &m.DisposableService3{Name: "S2"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService4, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService4, context.Context) { return &m.DisposableService4{Name: "X1"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService4, context.Context) { + RegisterKeyedFunc(Singleton, func(ctx context.Context) (*m.DisposableService4, context.Context) { return &m.DisposableService4{Name: "X2"}, ctx }, "somekey") @@ -124,7 +124,7 @@ func TestGetResolvedSingletons(t *testing.T) { assert.Equal(t, 5, len(disposables)) //invoke X2 in "somekey" scope - _, _ = GetList[fmt.Stringer](ctx, "somekey") + _, _ = GetKeyedList[fmt.Stringer](ctx, "somekey") //Act //all invoked singleton would be returned whatever keys they are registered with @@ -135,13 +135,13 @@ func TestGetResolvedSingletons(t *testing.T) { t.Run("respect invocation chronological time order", func(t *testing.T) { //Arrange clearAll() - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { return &m.DisposableService1{Name: "A"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService2, context.Context) { return &m.DisposableService2{Name: "B"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService3, context.Context) { return &m.DisposableService3{Name: "C"}, ctx }) @@ -166,15 +166,15 @@ func TestGetResolvedSingletons(t *testing.T) { t.Run("deeper invocation level is returned first", func(t *testing.T) { //Arrange clearAll() - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //1 calls 2 return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = Get[*m.DisposableService3](ctx) //2 calls 3 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService3, context.Context) { return &m.DisposableService3{Name: "3"}, ctx }) @@ -195,11 +195,11 @@ func TestGetResolvedSingletons(t *testing.T) { func TestGetResolvedScopedInstances(t *testing.T) { t.Run("When multiple lifetimes and keys are registered", func(t *testing.T) { clearAll() - RegisterEagerSingleton(&m.DisposableService1{Name: "S1"}) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterSingleton(&m.DisposableService1{Name: "S1"}) + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService1, context.Context) { return &m.DisposableService1{Name: "S2"}, ctx }) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterKeyedFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { return &m.DisposableService2{Name: "T1"}, ctx }, "module1") @@ -218,7 +218,7 @@ func TestGetResolvedScopedInstances(t *testing.T) { assert.Equal(t, "S2", disposables[0].String()) //invoke the keyed service T1 - _, ctx = GetList[*m.DisposableService2](ctx, "module1") + _, ctx = GetKeyedList[*m.DisposableService2](ctx, "module1") //Act disposables = GetResolvedScopedInstances[m.Disposer](ctx) //S2, T1 @@ -228,13 +228,13 @@ func TestGetResolvedScopedInstances(t *testing.T) { t.Run("respect invocation chronological time order", func(t *testing.T) { //Arrange clearAll() - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService1, context.Context) { return &m.DisposableService1{Name: "A"}, ctx }) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { return &m.DisposableService2{Name: "B"}, ctx }) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService3, context.Context) { return &m.DisposableService3{Name: "C"}, ctx }) @@ -260,18 +260,18 @@ func TestGetResolvedScopedInstances(t *testing.T) { t.Run("respect invocation deep level", func(t *testing.T) { //Arrange clearAll() - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService1, context.Context) { //1 calls 3 _, ctx = Get[*m.DisposableService3](ctx) return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService3, context.Context) { return &m.DisposableService3{Name: "3"}, ctx }) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService4, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService4, context.Context) { //4 calls 1, 2 _, ctx = Get[*m.DisposableService1](ctx) _, ctx = Get[*m.DisposableService2](ctx) diff --git a/getters.go b/getters.go deleted file mode 100644 index f683385..0000000 --- a/getters.go +++ /dev/null @@ -1,204 +0,0 @@ -package ore - -import ( - "context" - "sort" -) - -func (this *Container) getLastRegisteredResolver(typeID typeID) serviceResolver { - // try to get service resolver from container - this.lock.RLock() - resolvers, resolverExists := this.resolvers[typeID] - this.lock.RUnlock() - - if !resolverExists { - return nil - } - - count := len(resolvers) - - if count == 0 { - return nil - } - - // index of the last implementation - lastIndex := count - 1 - return resolvers[lastIndex] -} - -// GetFromContainer Retrieves an instance from the given container based on type and key (panics if no valid implementations) -func GetFromContainer[T any](con *Container, ctx context.Context, key ...KeyStringer) (T, context.Context) { - pointerTypeName := getPointerTypeName[T]() - typeID := getTypeID(pointerTypeName, key...) - lastRegisteredResolver := con.getLastRegisteredResolver(typeID) - if lastRegisteredResolver == nil { //not found, T is an alias - - con.lock.RLock() - implementations, implExists := con.aliases[pointerTypeName] - con.lock.RUnlock() - - if !implExists { - panic(noValidImplementation[T]()) - } - count := len(implementations) - if count == 0 { - panic(noValidImplementation[T]()) - } - for i := count - 1; i >= 0; i-- { - impl := implementations[i] - typeID = getTypeID(impl, key...) - lastRegisteredResolver = con.getLastRegisteredResolver(typeID) - if lastRegisteredResolver != nil { - break - } - } - } - if lastRegisteredResolver == nil { - panic(noValidImplementation[T]()) - } - concrete, ctx := lastRegisteredResolver.resolveService(con, ctx) - return concrete.value.(T), ctx -} - -// Get Retrieves an instance based on type and key (panics if no valid implementations) -func Get[T any](ctx context.Context, key ...KeyStringer) (T, context.Context) { - return GetFromContainer[T](DefaultContainer, ctx, key...) -} - -// GetListFromContainer Retrieves a list of instances from the given container based on type and key -func GetListFromContainer[T any](con *Container, ctx context.Context, key ...KeyStringer) ([]T, context.Context) { - inputPointerTypeName := getPointerTypeName[T]() - - con.lock.RLock() - pointerTypeNames, implExists := con.aliases[inputPointerTypeName] - con.lock.RUnlock() - - if implExists { - pointerTypeNames = append(pointerTypeNames, inputPointerTypeName) - } else { - pointerTypeNames = []pointerTypeName{inputPointerTypeName} - } - - servicesArray := []T{} - - for i := 0; i < len(pointerTypeNames); i++ { - pointerTypeName := pointerTypeNames[i] - // generate type identifier - typeID := getTypeID(pointerTypeName, key...) - - // try to get service resolver from container - con.lock.RLock() - resolvers, resolverExists := con.resolvers[typeID] - con.lock.RUnlock() - - if !resolverExists { - continue - } - - for index := 0; index < len(resolvers); index++ { - resolver := resolvers[index] - if resolver.isPlaceHolder() && !resolver.isScopedValueResolved(ctx) { - //the resolver is a placeHolder and the placeHolder's value has not been provided - //don't panic, just skip (don't add anything to the list) - continue - } - con, newCtx := resolver.resolveService(con, ctx) - servicesArray = append(servicesArray, con.value.(T)) - ctx = newCtx - } - } - - return servicesArray, ctx -} - -// GetList Retrieves a list of instances based on type and key -func GetList[T any](ctx context.Context, key ...KeyStringer) ([]T, context.Context) { - return GetListFromContainer[T](DefaultContainer, ctx, key...) -} - -// GetResolvedSingletonsFromContainer retrieves a list of Singleton instances that implement the [TInterface] from the given container. -// See [GetResolvedSingletons] for more information. -func GetResolvedSingletonsFromContainer[TInterface any](con *Container) []TInterface { - con.lock.RLock() - defer con.lock.RUnlock() - - list := []*concrete{} - - //filtering - for _, resolvers := range con.resolvers { - for _, resolver := range resolvers { - con, isInvokedSingleton := resolver.getInvokedSingleton() - if isInvokedSingleton { - if _, ok := con.value.(TInterface); ok { - list = append(list, con) - } - } - } - } - - return sortAndSelect[TInterface](list) -} - -// GetResolvedSingletons retrieves a list of Singleton instances that implement the [TInterface]. -// The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the "most recently" created one. -// If an instance "A" depends on certain instances "B" and "C" then this function guarantee to return "B" and "C" before "A" in the list. -// It would return only the instances which had been resolved. Other lazy implementations which have never been invoked will not be returned. -// This function is useful for cleaning operations. -// -// Example: -// -// disposableSingletons := ore.GetResolvedSingletons[Disposer]() -// for _, disposable := range disposableSingletons { -// disposable.Dispose() -// } -func GetResolvedSingletons[TInterface any]() []TInterface { - return GetResolvedSingletonsFromContainer[TInterface](DefaultContainer) -} - -// GetResolvedScopedInstances retrieves a list of Scoped instances that implement the [TInterface]. -// The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the most recently created one. -// If an instance "A" depends on certain instances "B" and "C" then this function guarantee to return "B" and "C" before "A" in the list. -// It would return only the instances which had been resolved. Other lazy implementations which have never been invoked will not be returned. -// This function is useful for cleaning operations. -// -// Example: -// -// disposableInstances := ore.GetResolvedScopedInstances[Disposer](ctx) -// for _, disposable := range disposableInstances { -// disposable.Dispose() -// } -func GetResolvedScopedInstances[TInterface any](ctx context.Context) []TInterface { - contextKeyRepository, ok := ctx.Value(contextKeysRepositoryID).(contextKeysRepository) - if !ok { - return []TInterface{} - } - - list := []*concrete{} - - //filtering - for _, contextKey := range contextKeyRepository { - con := ctx.Value(contextKey).(*concrete) - if _, ok := con.value.(TInterface); ok { - list = append(list, con) - } - } - - return sortAndSelect[TInterface](list) -} - -// sortAndSelect sorts concretes by invocation order and return its value. -func sortAndSelect[TInterface any](list []*concrete) []TInterface { - //sorting - sort.Slice(list, func(i, j int) bool { - return list[i].invocationTime.After(list[j].invocationTime) || - (list[i].invocationTime == list[j].invocationTime && - list[i].invocationLevel > list[j].invocationLevel) - }) - - //selecting - result := make([]TInterface, len(list)) - for i := 0; i < len(list); i++ { - result[i] = list[i].value.(TInterface) - } - return result -} diff --git a/initializer_test.go b/initializer_test.go index 10c2d75..5ae5d7d 100644 --- a/initializer_test.go +++ b/initializer_test.go @@ -9,11 +9,11 @@ import ( "github.com/stretchr/testify/assert" ) -func TestRegisterLazyFunc(t *testing.T) { +func TestRegisterFunc(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) @@ -28,40 +28,40 @@ func TestRegisterLazyFunc(t *testing.T) { } } -func TestRegisterLazyFuncNilFuncTransient(t *testing.T) { +func TestRegisterFuncNilFuncTransient(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterLazyFunc[interfaces.SomeCounter](Transient, nil) + RegisterFunc[interfaces.SomeCounter](Transient, nil) }) } -func TestRegisterLazyFuncNilFuncScoped(t *testing.T) { +func TestRegisterFuncNilFuncScoped(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterLazyFunc[interfaces.SomeCounter](Scoped, nil) + RegisterFunc[interfaces.SomeCounter](Scoped, nil) }) } -func TestRegisterLazyFuncNilFuncSingleton(t *testing.T) { +func TestRegisterFuncNilFuncSingleton(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterLazyFunc[interfaces.SomeCounter](Singleton, nil) + RegisterFunc[interfaces.SomeCounter](Singleton, nil) }) } -func TestRegisterLazyFuncMultipleImplementations(t *testing.T) { +func TestRegisterFuncMultipleImplementations(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) @@ -73,23 +73,23 @@ func TestRegisterLazyFuncMultipleImplementations(t *testing.T) { } } -func TestRegisterLazyFuncMultipleImplementationsKeyed(t *testing.T) { +func TestRegisterFuncMultipleImplementationsKeyed(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterKeyedFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }, "firas") - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterKeyedFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }, "firas") - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) - counters, _ := GetList[interfaces.SomeCounter](context.Background(), "firas") + counters, _ := GetKeyedList[interfaces.SomeCounter](context.Background(), "firas") if got := len(counters); got != 2 { t.Errorf("got %v, expected %v", got, 2) @@ -97,12 +97,12 @@ func TestRegisterLazyFuncMultipleImplementationsKeyed(t *testing.T) { } } -func TestRegisterLazyFuncSingletonState(t *testing.T) { +func TestRegisterFuncSingletonState(t *testing.T) { var registrationType Lifetime = Singleton clearAll() - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) @@ -123,12 +123,12 @@ func TestRegisterLazyFuncSingletonState(t *testing.T) { } } -func TestRegisterLazyFuncScopedState(t *testing.T) { +func TestRegisterFuncScopedState(t *testing.T) { var registrationType Lifetime = Scoped clearAll() - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) @@ -151,12 +151,12 @@ func TestRegisterLazyFuncScopedState(t *testing.T) { } } -func TestRegisterLazyFuncTransientState(t *testing.T) { +func TestRegisterFuncTransientState(t *testing.T) { var registrationType Lifetime = Transient clearAll() - RegisterLazyFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterFunc[interfaces.SomeCounter](registrationType, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }) @@ -179,31 +179,31 @@ func TestRegisterLazyFuncTransientState(t *testing.T) { } } -func TestRegisterLazyFuncNilKeyOnRegistering(t *testing.T) { +func TestRegisterFuncNilKeyOnRegistering(t *testing.T) { clearAll() assert.Panics(t, func() { - RegisterLazyFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterKeyedFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx - }, "", nil) + }, nil) }) } -func TestRegisterLazyFuncNilKeyOnGetting(t *testing.T) { +func TestRegisterFuncNilKeyOnGetting(t *testing.T) { clearAll() - RegisterLazyFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { + RegisterKeyedFunc[interfaces.SomeCounter](Scoped, func(ctx context.Context) (interfaces.SomeCounter, context.Context) { return &models.SimpleCounter{}, ctx }, "firas") assert.Panics(t, func() { - Get[interfaces.SomeCounter](context.Background(), "", nil) + GetKeyed[interfaces.SomeCounter](context.Background(), "") }) } -func TestRegisterLazyFuncGeneric(t *testing.T) { +func TestRegisterFuncGeneric(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyFunc[interfaces.SomeCounterGeneric[uint]](registrationType, func(ctx context.Context) (interfaces.SomeCounterGeneric[uint], context.Context) { + RegisterFunc[interfaces.SomeCounterGeneric[uint]](registrationType, func(ctx context.Context) (interfaces.SomeCounterGeneric[uint], context.Context) { return &models.CounterGeneric[uint]{}, ctx }) @@ -218,19 +218,19 @@ func TestRegisterLazyFuncGeneric(t *testing.T) { } } -func TestRegisterLazyFuncMultipleGenericImplementations(t *testing.T) { +func TestRegisterFuncMultipleGenericImplementations(t *testing.T) { for _, registrationType := range types { clearAll() - RegisterLazyFunc[interfaces.SomeCounterGeneric[uint]](registrationType, func(ctx context.Context) (interfaces.SomeCounterGeneric[uint], context.Context) { + RegisterFunc[interfaces.SomeCounterGeneric[uint]](registrationType, func(ctx context.Context) (interfaces.SomeCounterGeneric[uint], context.Context) { return &models.CounterGeneric[uint]{}, ctx }) - RegisterLazyFunc[interfaces.SomeCounterGeneric[uint]](registrationType, func(ctx context.Context) (interfaces.SomeCounterGeneric[uint], context.Context) { + RegisterFunc[interfaces.SomeCounterGeneric[uint]](registrationType, func(ctx context.Context) (interfaces.SomeCounterGeneric[uint], context.Context) { return &models.CounterGeneric[uint]{}, ctx }) - RegisterLazyFunc[interfaces.SomeCounterGeneric[uint]](registrationType, func(ctx context.Context) (interfaces.SomeCounterGeneric[uint], context.Context) { + RegisterFunc[interfaces.SomeCounterGeneric[uint]](registrationType, func(ctx context.Context) (interfaces.SomeCounterGeneric[uint], context.Context) { return &models.CounterGeneric[uint]{}, ctx }) @@ -242,17 +242,17 @@ func TestRegisterLazyFuncMultipleGenericImplementations(t *testing.T) { } } -func TestRegisterLazyFuncScopedNested(t *testing.T) { +func TestRegisterFuncScopedNested(t *testing.T) { clearAll() - RegisterLazyFunc[*models.A](Transient, func(ctx context.Context) (*models.A, context.Context) { + RegisterFunc[*models.A](Transient, func(ctx context.Context) (*models.A, context.Context) { cc, ctx := Get[*models.C](ctx) return &models.A{ C: cc, }, ctx }) - RegisterLazyFunc[*models.C](Scoped, func(ctx context.Context) (*models.C, context.Context) { + RegisterFunc[*models.C](Scoped, func(ctx context.Context) (*models.C, context.Context) { return &models.C{}, ctx }) diff --git a/internal_getters.go b/internal_getters.go new file mode 100644 index 0000000..36faf6c --- /dev/null +++ b/internal_getters.go @@ -0,0 +1,143 @@ +package ore + +import ( + "context" + "sort" +) + +func (this *Container) getLastRegisteredResolver(typeID typeID) serviceResolver { + // try to get service resolver from container + this.lock.RLock() + resolvers, resolverExists := this.resolvers[typeID] + this.lock.RUnlock() + + if !resolverExists { + return nil + } + + count := len(resolvers) + + if count == 0 { + return nil + } + + // index of the last implementation + lastIndex := count - 1 + return resolvers[lastIndex] +} + +// sortAndSelect sorts concretes by invocation order and return its value. +func sortAndSelect[TInterface any](list []*concrete) []TInterface { + //sorting + sort.Slice(list, func(i, j int) bool { + return list[i].invocationTime.After(list[j].invocationTime) || + (list[i].invocationTime == list[j].invocationTime && + list[i].invocationLevel > list[j].invocationLevel) + }) + + //selecting + result := make([]TInterface, len(list)) + for i := 0; i < len(list); i++ { + result[i] = list[i].value.(TInterface) + } + return result +} + +func getFromContainer[T any](con *Container, ctx context.Context, key KeyStringer) (T, context.Context) { + pointerTypeName := getPointerTypeName[T]() + typeID := getTypeID(pointerTypeName, key) + lastRegisteredResolver := con.getLastRegisteredResolver(typeID) + if lastRegisteredResolver == nil { //not found, T is an alias + + con.lock.RLock() + implementations, implExists := con.aliases[pointerTypeName] + con.lock.RUnlock() + + if !implExists { + panic(noValidImplementation[T]()) + } + count := len(implementations) + if count == 0 { + panic(noValidImplementation[T]()) + } + for i := count - 1; i >= 0; i-- { + impl := implementations[i] + typeID = getTypeID(impl, key) + lastRegisteredResolver = con.getLastRegisteredResolver(typeID) + if lastRegisteredResolver != nil { + break + } + } + } + if lastRegisteredResolver == nil { + panic(noValidImplementation[T]()) + } + concrete, ctx := lastRegisteredResolver.resolveService(con, ctx) + return concrete.value.(T), ctx +} + +func getListFromContainer[T any](con *Container, ctx context.Context, key KeyStringer) ([]T, context.Context) { + inputPointerTypeName := getPointerTypeName[T]() + + con.lock.RLock() + pointerTypeNames, implExists := con.aliases[inputPointerTypeName] + con.lock.RUnlock() + + if implExists { + pointerTypeNames = append(pointerTypeNames, inputPointerTypeName) + } else { + pointerTypeNames = []pointerTypeName{inputPointerTypeName} + } + + servicesArray := []T{} + + for i := 0; i < len(pointerTypeNames); i++ { + pointerTypeName := pointerTypeNames[i] + // generate type identifier + typeID := getTypeID(pointerTypeName, key) + + // try to get service resolver from container + con.lock.RLock() + resolvers, resolverExists := con.resolvers[typeID] + con.lock.RUnlock() + + if !resolverExists { + continue + } + + for index := 0; index < len(resolvers); index++ { + resolver := resolvers[index] + if resolver.isPlaceHolder() && !resolver.isScopedValueResolved(ctx) { + //the resolver is a placeHolder and the placeHolder's value has not been provided + //don't panic, just skip (don't add anything to the list) + continue + } + con, newCtx := resolver.resolveService(con, ctx) + servicesArray = append(servicesArray, con.value.(T)) + ctx = newCtx + } + } + + return servicesArray, ctx +} + +func getResolvedSingletonsFromContainer[TInterface any](con *Container) []TInterface { + con.lock.RLock() + defer con.lock.RUnlock() + + list := []*concrete{} + + //filtering + for _, resolvers := range con.resolvers { + for _, resolver := range resolvers { + con, isInvokedSingleton := resolver.getInvokedSingleton() + if isInvokedSingleton { + if _, ok := con.value.(TInterface); ok { + list = append(list, con) + } + } + } + } + + return sortAndSelect[TInterface](list) +} diff --git a/internal_registerers.go b/internal_registerers.go new file mode 100644 index 0000000..dc74a76 --- /dev/null +++ b/internal_registerers.go @@ -0,0 +1,89 @@ +package ore + +import ( + "context" + "fmt" + "reflect" + "time" +) + +func registerCreatorToContainer[T any](con *Container, lifetime Lifetime, creator Creator[T], key KeyStringer) { + if creator == nil { + panic(nilVal[T]()) + } + + e := serviceResolverImpl[T]{ + resolverMetadata: resolverMetadata{ + lifetime: lifetime, + }, + creatorInstance: creator, + } + addResolver[T](con, e, key) +} + +func registerSingletonToContainer[T comparable](con *Container, impl T, key KeyStringer) { + if isNil[T](impl) { + panic(nilVal[T]()) + } + + e := serviceResolverImpl[T]{ + resolverMetadata: resolverMetadata{ + lifetime: Singleton, + }, + singletonConcrete: &concrete{ + value: impl, + lifetime: Singleton, + invocationTime: time.Now(), + }, + } + addResolver[T](con, e, key) +} + +func registerFuncToContainer[T any](con *Container, lifetime Lifetime, initializer Initializer[T], key KeyStringer) { + if initializer == nil { + panic(nilVal[T]()) + } + + e := serviceResolverImpl[T]{ + resolverMetadata: resolverMetadata{ + lifetime: lifetime, + }, + anonymousInitializer: &initializer, + } + addResolver[T](con, e, key) +} + +func registerAliasToContainer[TInterface, TImpl any](con *Container) { + interfaceType := reflect.TypeFor[TInterface]() + implType := reflect.TypeFor[TImpl]() + + if !implType.Implements(interfaceType) { + panic(fmt.Errorf("%s does not implements %s", implType, interfaceType)) + } + + addAliases[TInterface, TImpl](con) +} + +func registerPlaceholderToContainer[T comparable](con *Container, key KeyStringer) { + e := serviceResolverImpl[T]{ + resolverMetadata: resolverMetadata{ + lifetime: Scoped, + }, + } + addResolver[T](con, e, key) +} + +func provideScopedValueToContainer[T comparable](con *Container, ctx context.Context, value T, key KeyStringer) context.Context { + concreteValue := &concrete{ + value: value, + lifetime: Scoped, + invocationTime: time.Now(), + invocationLevel: 0, + } + id := contextKey{ + containerID: con.containerID, + typeID: typeIdentifier[T](key), + resolverID: placeHolderResolverID, + } + return addScopedConcreteToContext(ctx, id, concreteValue) +} diff --git a/key.go b/key.go index b460f3d..64a7c2f 100644 --- a/key.go +++ b/key.go @@ -3,32 +3,17 @@ package ore import ( "reflect" "strconv" - "strings" ) type KeyStringer any -func oreKey(key ...KeyStringer) string { +func oreKey(key KeyStringer) string { if key == nil { - return "" + return "n" } - l := len(key) - - if l == 1 { - keyT, kV := stringifyOreKey(key[0]) - return keyT + kV - } - - var sb strings.Builder - - for _, s := range key { - keyT, keyV := stringifyOreKey(s) - sb.WriteString(keyT) - sb.WriteString(keyV) - } - - return sb.String() + keyT, kV := stringifyOreKey(key) + return keyT + kV } func stringifyOreKey(key KeyStringer) (string, string) { diff --git a/key_test.go b/key_test.go index 23de674..41cd0e1 100644 --- a/key_test.go +++ b/key_test.go @@ -14,14 +14,6 @@ func TestOreKeyNil(t *testing.T) { } } -func TestOreKeyEmpty(t *testing.T) { - k := oreKey() - - if got := k; got != "" { - t.Errorf("got `%v`, expected `%v`", got, "s") - } -} - func TestOreKey1String(t *testing.T) { k := oreKey("ore") expect := "sore" @@ -31,15 +23,6 @@ func TestOreKey1String(t *testing.T) { } } -func TestOreKey2String(t *testing.T) { - k := oreKey("ore", "package") - expect := "sorespackage" - - if got := k; got != expect { - t.Errorf("got `%v`, expected `%v`", got, expect) - } -} - func TestOreKey1Int(t *testing.T) { k := oreKey(10) expect := "i10" @@ -49,33 +32,6 @@ func TestOreKey1Int(t *testing.T) { } } -func TestOreKey2Int(t *testing.T) { - k := oreKey(10, 30) - expect := "i10i30" - - if got := k; got != expect { - t.Errorf("got `%v`, expected `%v`", got, expect) - } -} - -func TestOreKeyStringInt(t *testing.T) { - k := oreKey("ore", 97) - expect := "sorei97" - - if got := k; got != expect { - t.Errorf("got `%v`, expected `%v`", got, expect) - } -} - -func TestOreKey2StringInt(t *testing.T) { - k := oreKey("ore", 97, "di", 5) - expect := "sorei97sdii5" - - if got := k; got != expect { - t.Errorf("got `%v`, expected `%v`", got, expect) - } -} - func TestOreKeyUint(t *testing.T) { var n uint n = 5 @@ -117,12 +73,3 @@ func TestOreKeyStruct(t *testing.T) { oreKey(n) }) } - -func TestOreKeyVarious(t *testing.T) { - k := oreKey("firas", 16, "ore", 3.14, 1/6, -9, -1494546.452) - expect := "sfirasi16soref640x1.91eb851eb851fp+01i0i-9f64-0x1.6ce1273b645a2p+20" - - if got := k; got != expect { - t.Errorf("got `%v`, expected `%v`", got, expect) - } -} diff --git a/module_test.go b/module_test.go index 8d22208..056f9e5 100644 --- a/module_test.go +++ b/module_test.go @@ -13,12 +13,12 @@ func TestModuleIsolation(t *testing.T) { for _, lifetime := range types { t.Run(fmt.Sprintf("Module isolation %s", lifetime), func(t *testing.T) { con1 := NewContainer() - RegisterLazyFuncToContainer(con1, lifetime, func(ctx context.Context) (*m.Trader, context.Context) { + RegisterFuncToContainer(con1, lifetime, func(ctx context.Context) (*m.Trader, context.Context) { return &m.Trader{Name: "John"}, ctx }) con2 := NewContainer() - RegisterLazyFuncToContainer(con2, lifetime, func(ctx context.Context) (*m.Trader, context.Context) { + RegisterFuncToContainer(con2, lifetime, func(ctx context.Context) (*m.Trader, context.Context) { return &m.Trader{Name: "Mary"}, ctx }) diff --git a/ore.go b/ore.go index 3fd1227..be51ce6 100644 --- a/ore.go +++ b/ore.go @@ -30,27 +30,22 @@ func init() { } // Generates a unique identifier for a service resolver based on type and key(s) -func getTypeID(pointerTypeName pointerTypeName, key ...KeyStringer) typeID { - for _, stringer := range key { - if stringer == nil { - panic(nilKey) - } - } - return typeID{pointerTypeName, oreKey(key...)} +func getTypeID(pointerTypeName pointerTypeName, key KeyStringer) typeID { + return typeID{pointerTypeName, oreKey(key)} } // Generates a unique identifier for a service resolver based on type and key(s) -func typeIdentifier[T any](key ...KeyStringer) typeID { - return getTypeID(getPointerTypeName[T](), key...) +func typeIdentifier[T any](key KeyStringer) typeID { + return getTypeID(getPointerTypeName[T](), key) } // Appends a service resolver to the container with type and key -func addResolver[T any](this *Container, resolver serviceResolverImpl[T], key ...KeyStringer) { +func addResolver[T any](this *Container, resolver serviceResolverImpl[T], key KeyStringer) { if this.isSealed { panic(alreadyBuiltCannotAdd) } - typeID := typeIdentifier[T](key...) + typeID := typeIdentifier[T](key) this.lock.Lock() defer this.lock.Unlock() diff --git a/ore_test.go b/ore_test.go index 95dca54..41acac9 100644 --- a/ore_test.go +++ b/ore_test.go @@ -10,16 +10,16 @@ import ( func TestSeal(t *testing.T) { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) Seal() assert.Panics(t, func() { - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) }) } func TestIsSeal(t *testing.T) { clearAll() - RegisterLazyCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) + RegisterCreator[interfaces.SomeCounter](Scoped, &models.SimpleCounter{}) if got := IsSealed(); got != false { t.Errorf("IsSealed() = %v; want %v", got, false) } @@ -76,13 +76,13 @@ type A1 struct{} type A2 struct{} func TestTypeIdentifier(t *testing.T) { - id1 := typeIdentifier[*A1]() - id11 := typeIdentifier[*A1]() - id2 := typeIdentifier[*A2]() + id1 := typeIdentifier[*A1](nil) + id11 := typeIdentifier[*A1](nil) + id2 := typeIdentifier[*A2](nil) assert.NotEqual(t, id1, id2) assert.Equal(t, id1, id11) - id3 := typeIdentifier[*A1]("a", "b") - id4 := typeIdentifier[*A1]("a", "b") + id3 := typeIdentifier[*A1]("a") + id4 := typeIdentifier[*A1]("a") assert.Equal(t, id3, id4) } diff --git a/registrars.go b/registrars.go deleted file mode 100644 index 26cddca..0000000 --- a/registrars.go +++ /dev/null @@ -1,139 +0,0 @@ -package ore - -import ( - "context" - "fmt" - "reflect" - "time" -) - -// RegisterLazyCreatorToContainer Registers a lazily initialized value to the given container using a `Creator[T]` interface -func RegisterLazyCreatorToContainer[T any](con *Container, lifetime Lifetime, creator Creator[T], key ...KeyStringer) { - if creator == nil { - panic(nilVal[T]()) - } - - e := serviceResolverImpl[T]{ - resolverMetadata: resolverMetadata{ - lifetime: lifetime, - }, - creatorInstance: creator, - } - addResolver[T](con, e, key...) -} - -// RegisterLazyCreator Registers a lazily initialized value using a `Creator[T]` interface -func RegisterLazyCreator[T any](lifetime Lifetime, creator Creator[T], key ...KeyStringer) { - RegisterLazyCreatorToContainer[T](DefaultContainer, lifetime, creator, key...) -} - -// RegisterEagerSingletonToContainer Registers an eagerly instantiated singleton value to the given container. -// To register an eagerly instantiated scoped value use [ProvideScopedValueToContainer] -func RegisterEagerSingletonToContainer[T comparable](con *Container, impl T, key ...KeyStringer) { - if isNil[T](impl) { - panic(nilVal[T]()) - } - - e := serviceResolverImpl[T]{ - resolverMetadata: resolverMetadata{ - lifetime: Singleton, - }, - singletonConcrete: &concrete{ - value: impl, - lifetime: Singleton, - invocationTime: time.Now(), - }, - } - addResolver[T](con, e, key...) -} - -// RegisterEagerSingleton Registers an eagerly instantiated singleton value -// To register an eagerly instantiated scoped value use [ProvideScopedValue] -func RegisterEagerSingleton[T comparable](impl T, key ...KeyStringer) { - RegisterEagerSingletonToContainer[T](DefaultContainer, impl, key...) -} - -// RegisterLazyFuncToContainer Registers a lazily initialized value to the given container using an `Initializer[T]` function signature -func RegisterLazyFuncToContainer[T any](con *Container, lifetime Lifetime, initializer Initializer[T], key ...KeyStringer) { - if initializer == nil { - panic(nilVal[T]()) - } - - e := serviceResolverImpl[T]{ - resolverMetadata: resolverMetadata{ - lifetime: lifetime, - }, - anonymousInitializer: &initializer, - } - addResolver[T](con, e, key...) -} - -// RegisterLazyFunc Registers a lazily initialized value using an `Initializer[T]` function signature -func RegisterLazyFunc[T any](lifetime Lifetime, initializer Initializer[T], key ...KeyStringer) { - RegisterLazyFuncToContainer(DefaultContainer, lifetime, initializer, key...) -} - -// RegisterAliasToContainer Registers an interface type to a concrete implementation in the given container. -// Allowing you to register the concrete implementation to the container and later get the interface from it. -func RegisterAliasToContainer[TInterface, TImpl any](con *Container) { - interfaceType := reflect.TypeFor[TInterface]() - implType := reflect.TypeFor[TImpl]() - - if !implType.Implements(interfaceType) { - panic(fmt.Errorf("%s does not implements %s", implType, interfaceType)) - } - - addAliases[TInterface, TImpl](con) -} - -// RegisterAlias Registers an interface type to a concrete implementation. -// Allowing you to register the concrete implementation to the default container and later get the interface from it. -func RegisterAlias[TInterface, TImpl any]() { - RegisterAliasToContainer[TInterface, TImpl](DefaultContainer) -} - -// RegisterPlaceHolderToContainer registers a future value with Scoped lifetime to the given container. -// This value will be injected in runtime using the [ProvideScopedValue] function. -// Resolving objects which depend on this value will panic if the value has not been provided. -// Placeholder with the same type and key can be registered only once. -func RegisterPlaceHolderToContainer[T comparable](con *Container, key ...KeyStringer) { - e := serviceResolverImpl[T]{ - resolverMetadata: resolverMetadata{ - lifetime: Scoped, - }, - } - addResolver[T](con, e, key...) -} - -// RegisterPlaceHolder registers a future value with Scoped lifetime. -// This value will be injected in runtime using the [ProvideScopedValue] function. -// Resolving objects which depend on this value will panic if the value has not been provided. -// Placeholder with the same type and key can be registered only once. -func RegisterPlaceHolder[T comparable](key ...KeyStringer) { - RegisterPlaceHolderToContainer[T](DefaultContainer, key...) -} - -// ProvideScopedValueToContainer injects a concrete value into the given context. -// This value will be available only to the given container. And the container can only resolve this value if -// it has the matching (type and key's) Placeholder registered. Checkout the [RegisterPlaceHolderToContainer] function for more info. -func ProvideScopedValueToContainer[T comparable](con *Container, ctx context.Context, value T, key ...KeyStringer) context.Context { - concreteValue := &concrete{ - value: value, - lifetime: Scoped, - invocationTime: time.Now(), - invocationLevel: 0, - } - id := contextKey{ - containerID: con.containerID, - typeID: typeIdentifier[T](key...), - resolverID: placeHolderResolverID, - } - return addScopedConcreteToContext(ctx, id, concreteValue) -} - -// ProvideScopedValue injects a concrete value into the given context. -// This value will be available only to the default container. And the container can only resolve this value if -// it has the matching (type and key's) Placeholder registered. Checkout the [RegisterPlaceHolder] function for more info. -func ProvideScopedValue[T comparable](ctx context.Context, value T, key ...KeyStringer) context.Context { - return ProvideScopedValueToContainer[T](DefaultContainer, ctx, value, key...) -} diff --git a/registrars_placeholder_test.go b/registrars_placeholder_test.go index 11d53a1..f237fa8 100644 --- a/registrars_placeholder_test.go +++ b/registrars_placeholder_test.go @@ -13,7 +13,7 @@ func TestPlaceHolder_HappyPath(t *testing.T) { clearAll() //register a placeHolder - RegisterPlaceHolder[*m.Trader]() + RegisterPlaceholder[*m.Trader]() //get the placeHolder value would failed assert2.PanicsWithError(t, assert2.ErrorStartsWith("No value has been provided for this placeholder"), func() { @@ -59,7 +59,7 @@ func TestPlaceHolder_ProvideValueBeforeRegistering(t *testing.T) { }) //register a matching placeHolder - RegisterPlaceHolder[*m.Trader]() + RegisterPlaceholder[*m.Trader]() //get the placeHolder value would success trader, _ := Get[*m.Trader](ctx) @@ -71,64 +71,64 @@ func TestPlaceHolder_OverrideRealResolver(t *testing.T) { clearAll() //register a real resolver - RegisterEagerSingleton(&m.Trader{Name: "Mary"}, "module1") + RegisterKeyedSingleton(&m.Trader{Name: "Mary"}, "module1") //register a placeHolder to override the real resolver should failed assert2.PanicsWithError(t, assert2.ErrorContains("has already been registered"), func() { - RegisterPlaceHolder[*m.Trader]("module1") + RegisterKeyedPlaceholder[*m.Trader]("module1") }) //register 2 time a placeHolder should failed - RegisterPlaceHolder[*m.Trader]("module2") + RegisterKeyedPlaceholder[*m.Trader]("module2") assert2.PanicsWithError(t, assert2.ErrorContains("has already been registered"), func() { - RegisterPlaceHolder[*m.Trader]("module2") + RegisterKeyedPlaceholder[*m.Trader]("module2") }) } func TestPlaceHolder_OverridePlaceHolder(t *testing.T) { clearAll() //register a placeHolder - RegisterPlaceHolder[*m.Trader]("module2") + RegisterKeyedPlaceholder[*m.Trader]("module2") //Provide the value to the placeHolder - ctx := ProvideScopedValue[*m.Trader](context.Background(), &m.Trader{Name: "John"}, "module2") + ctx := ProvideKeyedScopedValue[*m.Trader](context.Background(), &m.Trader{Name: "John"}, "module2") //get the placeHolder value would success - trader, ctx := Get[*m.Trader](ctx, "module2") + trader, ctx := GetKeyed[*m.Trader](ctx, "module2") assert.Equal(t, "John", trader.Name) //replace the placeHolder value "John" with a new value "David" - ctx = ProvideScopedValue[*m.Trader](ctx, &m.Trader{Name: "David"}, "module2") - trader, ctx = Get[*m.Trader](ctx, "module2") + ctx = ProvideKeyedScopedValue[*m.Trader](ctx, &m.Trader{Name: "David"}, "module2") + trader, ctx = GetKeyed[*m.Trader](ctx, "module2") assert.Equal(t, "David", trader.Name) - traders, ctx := GetList[*m.Trader](ctx, "module2") + traders, ctx := GetKeyedList[*m.Trader](ctx, "module2") assert.Equal(t, 1, len(traders)) assert.Equal(t, "David", traders[0].Name) //Register a real resolver should override the placeHolder resolver - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.Trader, context.Context) { + RegisterKeyedFunc(Singleton, func(ctx context.Context) (*m.Trader, context.Context) { return &m.Trader{Name: "Mary"}, ctx }, "module2") - trader, ctx = Get[*m.Trader](ctx, "module2") + trader, ctx = GetKeyed[*m.Trader](ctx, "module2") assert.Equal(t, "Mary", trader.Name) //Get both the placeHolder value ("David") and the real resolver value ("Mary") - traders, ctx = GetList[*m.Trader](ctx, "module2") + traders, ctx = GetKeyedList[*m.Trader](ctx, "module2") assert.Equal(t, 2, len(traders)) //David and Mary assert.True(t, tradersListContainsName(traders, "David")) assert.True(t, tradersListContainsName(traders, "Mary")) //replace the placeHolder value ("David") with a new value ("Nathan") - ctx = ProvideScopedValue[*m.Trader](ctx, &m.Trader{Name: "Nathan"}, "module2") + ctx = ProvideKeyedScopedValue[*m.Trader](ctx, &m.Trader{Name: "Nathan"}, "module2") //the placeHolder value cannot override the real resolver value - trader, ctx = Get[*m.Trader](ctx, "module2") + trader, ctx = GetKeyed[*m.Trader](ctx, "module2") assert.Equal(t, "Mary", trader.Name) //but it replaces the old placeHolder value ("Nathan" will replace "David") - traders, _ = GetList[*m.Trader](ctx, "module2") + traders, _ = GetKeyedList[*m.Trader](ctx, "module2") assert.Equal(t, 2, len(traders)) //Nathan and Mary assert.True(t, tradersListContainsName(traders, "Nathan")) assert.True(t, tradersListContainsName(traders, "Mary")) @@ -137,10 +137,10 @@ func TestPlaceHolder_OverridePlaceHolder(t *testing.T) { // placeholder value of a module is not accessible from other module func TestPlaceHolder_PerModule(t *testing.T) { con1 := NewContainer() - RegisterPlaceHolderToContainer[*m.Trader](con1) + RegisterPlaceholderToContainer[*m.Trader](con1) con2 := NewContainer() - RegisterPlaceHolderToContainer[*m.Trader](con2) + RegisterPlaceholderToContainer[*m.Trader](con2) ctx := ProvideScopedValueToContainer(con1, context.Background(), &m.Trader{Name: "John"}) trader, ctx := GetFromContainer[*m.Trader](con1, ctx) diff --git a/shared_getters.go b/shared_getters.go new file mode 100644 index 0000000..e3ef1f0 --- /dev/null +++ b/shared_getters.go @@ -0,0 +1,56 @@ +package ore + +import "context" + +// GetResolvedSingletonsFromContainer retrieves a list of Singleton instances that implement the [TInterface] from the given container. +// See [GetResolvedSingletons] for more information. +func GetResolvedSingletonsFromContainer[TInterface any](con *Container) []TInterface { + return getResolvedSingletonsFromContainer[TInterface](con) +} + +// GetResolvedSingletons retrieves a list of Singleton instances that implement the [TInterface]. +// The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the "most recently" created one. +// If an instance "A" depends on certain instances "B" and "C" then this function guarantee to return "B" and "C" before "A" in the list. +// It would return only the instances which had been resolved. Other lazy implementations which have never been invoked will not be returned. +// This function is useful for cleaning operations. +// +// Example: +// +// disposableSingletons := ore.GetResolvedSingletons[Disposer]() +// for _, disposable := range disposableSingletons { +// disposable.Dispose() +// } +func GetResolvedSingletons[TInterface any]() []TInterface { + return getResolvedSingletonsFromContainer[TInterface](DefaultContainer) +} + +// GetResolvedScopedInstances retrieves a list of Scoped instances that implement the [TInterface]. +// The returned instances are sorted by creation time (a.k.a the invocation order), the first one being the most recently created one. +// If an instance "A" depends on certain instances "B" and "C" then this function guarantee to return "B" and "C" before "A" in the list. +// It would return only the instances which had been resolved. Other lazy implementations which have never been invoked will not be returned. +// This function is useful for cleaning operations. +// +// Example: +// +// disposableInstances := ore.GetResolvedScopedInstances[Disposer](ctx) +// for _, disposable := range disposableInstances { +// disposable.Dispose() +// } +func GetResolvedScopedInstances[TInterface any](ctx context.Context) []TInterface { + contextKeyRepository, ok := ctx.Value(contextKeysRepositoryID).(contextKeysRepository) + if !ok { + return []TInterface{} + } + + var list []*concrete + + //filtering + for _, contextKey := range contextKeyRepository { + con := ctx.Value(contextKey).(*concrete) + if _, ok := con.value.(TInterface); ok { + list = append(list, con) + } + } + + return sortAndSelect[TInterface](list) +} diff --git a/shared_registerers.go b/shared_registerers.go new file mode 100644 index 0000000..913385e --- /dev/null +++ b/shared_registerers.go @@ -0,0 +1,13 @@ +package ore + +// RegisterAlias Registers an interface type to a concrete implementation. +// Allowing you to register the concrete implementation to the default container and later get the interface from it. +func RegisterAlias[TInterface, TImpl any]() { + registerAliasToContainer[TInterface, TImpl](DefaultContainer) +} + +// RegisterAliasToContainer Registers an interface type to a concrete implementation in the given container. +// Allowing you to register the concrete implementation to the container and later get the interface from it. +func RegisterAliasToContainer[TInterface, TImpl any](con *Container) { + registerAliasToContainer[TInterface, TImpl](con) +} diff --git a/validate_test.go b/validate_test.go index 20dfefe..6a22366 100644 --- a/validate_test.go +++ b/validate_test.go @@ -13,7 +13,7 @@ func TestValidate_CircularDepsUniformLifetype(t *testing.T) { for _, lt := range types { t.Run("Direct circular "+lt.String()+" (1 calls 1)", func(t *testing.T) { clearAll() - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService1](ctx) //1 calls 1 return &m.DisposableService1{Name: "1"}, ctx }) @@ -21,15 +21,15 @@ func TestValidate_CircularDepsUniformLifetype(t *testing.T) { }) t.Run("Indirect circular "+lt.String()+" (1 calls 2 calls 3 calls 1)", func(t *testing.T) { clearAll() - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //1 calls 2 return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = Get[*m.DisposableService3](ctx) //2 calls 3 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { _, ctx = Get[*m.DisposableService1](ctx) //3 calls 1 return &m.DisposableService3{Name: "3"}, ctx }) @@ -37,19 +37,19 @@ func TestValidate_CircularDepsUniformLifetype(t *testing.T) { }) t.Run("Middle circular "+lt.String()+" (1 calls 2 calls 3 calls 4 calls 2)", func(t *testing.T) { clearAll() - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //1 calls 2 return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = Get[*m.DisposableService3](ctx) //2 calls 3 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { _, ctx = Get[*m.DisposableService4](ctx) //3 calls 4 return &m.DisposableService3{Name: "3"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService4, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService4, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //4 calls 2 return &m.DisposableService4{Name: "4"}, ctx }) @@ -57,25 +57,25 @@ func TestValidate_CircularDepsUniformLifetype(t *testing.T) { }) t.Run("circular on complex tree "+lt.String()+"", func(t *testing.T) { clearAll() - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //1 calls 2 _, ctx = Get[*m.DisposableService3](ctx) //1 calls 3 return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = Get[*m.DisposableService4](ctx) //2 calls 4 _, ctx = Get[*m.DisposableService5](ctx) //2 calls 5 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { _, ctx = Get[*m.DisposableService4](ctx) //3 calls 4 return &m.DisposableService3{Name: "3"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService4, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService4, context.Context) { _, ctx = Get[*m.DisposableService5](ctx) //4 calls 5 return &m.DisposableService4{Name: "4"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService5, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService5, context.Context) { _, ctx = Get[*m.DisposableService3](ctx) //5 calls 3 => circular here: 5->3->4->5 return &m.DisposableService5{Name: "5"}, ctx }) @@ -83,38 +83,38 @@ func TestValidate_CircularDepsUniformLifetype(t *testing.T) { }) t.Run("fake circular top down "+lt.String()+": (1 calls 2 (x2) calls 3 calls 4, 2 calls 4)", func(t *testing.T) { clearAll() - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //1 calls 2 _, ctx = Get[*m.DisposableService2](ctx) //1 calls 2 again return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = Get[*m.DisposableService3](ctx) //2 calls 3 _, ctx = Get[*m.DisposableService4](ctx) //2 calls 4 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { _, ctx = Get[*m.DisposableService4](ctx) //3 calls 4 _, ctx = Get[*m.DisposableService4](ctx) //3 calls 4 return &m.DisposableService3{Name: "3"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService4, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService4, context.Context) { return &m.DisposableService4{Name: "4"}, ctx }) assert.NotPanics(t, Validate) }) t.Run("fake circular sibling "+lt.String()+": 1 calls 2 & 3; 2 calls 3)", func(t *testing.T) { clearAll() - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //1 calls 2 _, ctx = Get[*m.DisposableService3](ctx) //1 calls 3 return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = Get[*m.DisposableService3](ctx) //2 calls 3 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(lt, func(ctx context.Context) (*m.DisposableService3, context.Context) { return &m.DisposableService3{Name: "3"}, ctx }) assert.NotPanics(t, Validate) @@ -125,24 +125,24 @@ func TestValidate_CircularDepsUniformLifetype(t *testing.T) { func TestValidate_CircularMixedLifetype(t *testing.T) { clearAll() - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = Get[*m.DisposableService4](ctx) //2 calls 4 _, ctx = Get[*m.DisposableService5](ctx) //2 calls 5 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService3, context.Context) { _, ctx = Get[*m.DisposableService4](ctx) //3 calls 4 return &m.DisposableService3{Name: "3"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService4, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService4, context.Context) { _, ctx = Get[*m.DisposableService5](ctx) //4 calls 5 return &m.DisposableService4{Name: "4"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService5, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService5, context.Context) { _, ctx = Get[*m.DisposableService3](ctx) //5 calls 3 => circular here: 5->3->4->5 return &m.DisposableService5{Name: "5"}, ctx }) - RegisterLazyFunc(Transient, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(Transient, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //1 calls 2 _, ctx = Get[*m.DisposableService3](ctx) //1 calls 3 return &m.DisposableService1{Name: "1"}, ctx @@ -155,10 +155,10 @@ func TestValidate_CircularMixedLifetype(t *testing.T) { func TestValidate_LifetimeAlignment_SingletonCallsScoped(t *testing.T) { con := NewContainer() - RegisterLazyFuncToContainer(con, Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFuncToContainer(con, Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFuncToContainer(con, Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFuncToContainer(con, Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = GetFromContainer[*m.DisposableService2](con, ctx) //1 depends on 2 return &m.DisposableService1{Name: "1"}, ctx }) @@ -166,26 +166,26 @@ func TestValidate_LifetimeAlignment_SingletonCallsScoped(t *testing.T) { } func TestValidate_LifetimeAlignment_ScopedCallsTransient(t *testing.T) { con := NewContainer() - RegisterLazyFuncToContainer(con, Scoped, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFuncToContainer(con, Scoped, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = GetFromContainer[*m.DisposableService2](con, ctx) //1 depends on 2 return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFuncToContainer(con, Transient, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFuncToContainer(con, Transient, func(ctx context.Context) (*m.DisposableService2, context.Context) { return &m.DisposableService2{Name: "2"}, ctx }) assert2.PanicsWithError(t, assert2.ErrorStartsWith("detect lifetime misalignment"), con.Validate) } func TestValidate_LifetimeAlignment_SingletonCallsTransient(t *testing.T) { con := NewContainer() - RegisterLazyFuncToContainer(con, Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFuncToContainer(con, Singleton, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = GetFromContainer[*m.DisposableService2](con, ctx) //1 depends on 2 return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFuncToContainer(con, Singleton, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFuncToContainer(con, Singleton, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = GetFromContainer[*m.DisposableService3](con, ctx) //2 depends on 3 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFuncToContainer(con, Transient, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFuncToContainer(con, Transient, func(ctx context.Context) (*m.DisposableService3, context.Context) { return &m.DisposableService3{Name: "3"}, ctx }) assert2.PanicsWithError(t, assert2.ErrorStartsWith("detect lifetime misalignment"), con.Validate) @@ -193,15 +193,15 @@ func TestValidate_LifetimeAlignment_SingletonCallsTransient(t *testing.T) { func TestValidate_MissingDependency(t *testing.T) { clearAll() - RegisterLazyFunc(Transient, func(ctx context.Context) (*m.DisposableService1, context.Context) { + RegisterFunc(Transient, func(ctx context.Context) (*m.DisposableService1, context.Context) { _, ctx = Get[*m.DisposableService2](ctx) //1 depends on 2 return &m.DisposableService1{Name: "1"}, ctx }) - RegisterLazyFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { + RegisterFunc(Scoped, func(ctx context.Context) (*m.DisposableService2, context.Context) { _, ctx = Get[*m.DisposableService3](ctx) //2 depends on 3 return &m.DisposableService2{Name: "2"}, ctx }) - RegisterLazyFunc(Singleton, func(ctx context.Context) (*m.DisposableService3, context.Context) { + RegisterFunc(Singleton, func(ctx context.Context) (*m.DisposableService3, context.Context) { _, ctx = Get[*m.DisposableService4](ctx) //3 depends on 4 return &m.DisposableService3{Name: "3"}, ctx }) @@ -211,20 +211,20 @@ func TestValidate_MissingDependency(t *testing.T) { func TestValidate_WithPlaceHolder(t *testing.T) { con := NewContainer() - RegisterPlaceHolderToContainer[*m.Trader](con) + RegisterPlaceholderToContainer[*m.Trader](con) assert.NotPanics(t, con.Validate) } func TestValidate_WithPlaceHolderInterface(t *testing.T) { con := NewContainer() - RegisterPlaceHolderToContainer[m.IPerson](con) + RegisterPlaceholderToContainer[m.IPerson](con) assert.NotPanics(t, con.Validate) } func TestValidate_DisableValidation(t *testing.T) { con := NewContainer() - RegisterPlaceHolderToContainer[*m.Trader](con) - RegisterLazyFuncToContainer(con, Singleton, func(ctx context.Context) (*m.Broker, context.Context) { + RegisterPlaceholderToContainer[*m.Trader](con) + RegisterFuncToContainer(con, Singleton, func(ctx context.Context) (*m.Broker, context.Context) { _, ctx = GetFromContainer[*m.Trader](con, ctx) return &m.Broker{Name: "John"}, ctx })