diff --git a/README.md b/README.md index b1bd3a8..f84598b 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@ [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go?tab=readme-ov-file#dependency-injection) [![Maintainability](https://api.codeclimate.com/v1/badges/3bd6f2fa4390af7c8faa/maintainability)](https://codeclimate.com/github/firasdarwish/ore/maintainability) [![codecov](https://codecov.io/gh/firasdarwish/ore/graph/badge.svg?token=ISZVCCYGCR)](https://codecov.io/gh/firasdarwish/ore) - +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ffirasdarwish%2Fore.svg?type=shield&issueType=license)](https://app.fossa.com/projects/git%2Bgithub.com%2Ffirasdarwish%2Fore?ref=badge_shield&issueType=license) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Ffirasdarwish%2Fore.svg?type=shield&issueType=security)](https://app.fossa.com/projects/git%2Bgithub.com%2Ffirasdarwish%2Fore?ref=badge_shield&issueType=security) ![ore](https://github.com/firasdarwish/ore/assets/1930361/c1426ba1-777a-43f5-8a9a-7520caa45516) @@ -326,7 +327,7 @@ Alias is also scoped by key. When you "Get" an alias with keys for eg: `ore.Get[ ### Registration Validation -Once you're done with registering all the services, it is recommended to call `ore.Build()` AND `ore.Validate()`. +Once you're done with registering all the services, it is recommended to call `ore.Seal()`, then `ore.Validate()`, then finally `ore.DisableValidation=true`. `ore.Validate()` invokes ALL your registered resolvers. The purpose is to panic early if your registrations were in bad shape: @@ -345,9 +346,9 @@ Once you're done with registering all the services, it is recommended to call `o Option 1 (run `ore.Validate` on test) is usually a better choice. -(2) It is recommended to build your container `ore.Build()` (which seals the container) on application start => Please don't call `ore.RegisterXX` all over the place. +(2) It is recommended to seal your container `ore.Seal()` (which seals the container) on application start => Please don't call `ore.RegisterXX` all over the place. -(3) A combination of `ore.Buile()` and then `ore.Validate()` ensures no more new resolvers will be registered AND all registered resolvers are validated, this will automatically +(3) A combination of `ore.Buile()` and then `ore.Validate()` and then `ore.DisabledValidation=true` ensures no more new resolvers will be registered AND all registered resolvers are validated, this will prevent any further validation each time a resolver is invoked (`ore.Get`) which greatly enhances performance. (4) Keep the object creation function (a.k.a resolvers) simple. Their only responsibility should be **object creation**. @@ -462,7 +463,7 @@ ore.RegisterLazyFuncToContainer(brokerContainer, ore.Singleton, func(ctx context brs, ctx = ore.GetFromContainer[*BrokerageSystem](brokerContainer, ctx) return &Broker{brs}, ctx }) -// brokerContainer.Build() //prevent further registration +// brokerContainer.Seal() //prevent further registration // brokerContainer.Validate() //check the dependency graph // brokerContainer.DisableValidation = true //disable check when resolve new object broker, _ := ore.GetFromContainer[*Broker](brokerContainer, context.Background()) @@ -483,7 +484,7 @@ module" to have access to the `traderContainer` of the "Trader module". ### Injecting value at Runtime -A common scenario is that your "Service" depends on something which you couldn't provide on registration time. You can provide this dependency only when certain requests or events arrive later. Ore allows you to build an "incomplete" dependency graph using the "place holder". +A common scenario is that your "Service" depends on something which you couldn't provide on registration time. You can provide this dependency only when certain requests or events arrive later. Ore allows you to build an "incomplete" dependency graph using the "placeholder". ```go //register SomeService which depends on "someConfig" @@ -527,9 +528,9 @@ fmt.Println(service.someConfig) //"Admin config" - 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 place holder (which matches the type and keys) is registered. + - `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 place holder 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 `RegisterLazyFunc`, `RegisterLazyCreator`...), then the later would take precedent.
diff --git a/benchmarks_test.go b/benchmarks_test.go index 22711ec..359b4c1 100644 --- a/benchmarks_test.go +++ b/benchmarks_test.go @@ -45,7 +45,7 @@ func BenchmarkInitialGet(b *testing.B) { RegisterLazyCreator[someCounter](Scoped, &simpleCounter{}) - Build() + Seal() Validate() ctx := context.Background() @@ -66,7 +66,7 @@ func BenchmarkGet(b *testing.B) { RegisterEagerSingleton[someCounter](&simpleCounter{}) RegisterLazyCreator[someCounter](Scoped, &simpleCounter{}) - Build() + Seal() Validate() ctx := context.Background() @@ -86,7 +86,7 @@ func BenchmarkInitialGetList(b *testing.B) { RegisterEagerSingleton[someCounter](&simpleCounter{}) RegisterLazyCreator[someCounter](Scoped, &simpleCounter{}) - Build() + Seal() Validate() ctx := context.Background() @@ -107,7 +107,7 @@ func BenchmarkGetList(b *testing.B) { RegisterEagerSingleton[someCounter](&simpleCounter{}) RegisterLazyCreator[someCounter](Scoped, &simpleCounter{}) - Build() + Seal() Validate() ctx := context.Background() diff --git a/container.go b/container.go index 9336a0e..61763be 100644 --- a/container.go +++ b/container.go @@ -16,25 +16,23 @@ type Container struct { // - no lifetime misalignment (a longer lifetime service depends on a shorter one). // // You don't need Ore to validate over and over again each time it creates a new concrete. - // It's a waste of resource especially when you will need Ore to create milion of transient concretes + // It's a waste of resource especially when you will need Ore to create a million of transient concretes // and any "pico" seconds or memory allocation matter for you. // // In this case, you can set DisableValidation = true. // - // This config would impact also the the [GetResolvedSingletons] and the [GetResolvedScopedInstances] functions, + // This config would impact also the [GetResolvedSingletons] and the [GetResolvedScopedInstances] functions, // the returning order would be no longer guaranteed. DisableValidation bool containerID int32 + isSealed bool lock *sync.RWMutex - isBuilt bool resolvers map[typeID][]serviceResolver - //isSealed will be set to `true` when `Validate()` is called AFTER `Build()` is called - //it prevents any further validations thus enhancing performance - isSealed bool - //map interface type to the implementations type aliases map[pointerTypeName][]pointerTypeName + + name string } var lastContainerID atomic.Int32 @@ -43,17 +41,42 @@ func NewContainer() *Container { return &Container{ containerID: lastContainerID.Add(1), lock: &sync.RWMutex{}, - isBuilt: false, + isSealed: false, resolvers: map[typeID][]serviceResolver{}, aliases: map[pointerTypeName][]pointerTypeName{}, } } +func (c *Container) ContainerID() int32 { + return c.containerID +} + +func (c *Container) Name() string { + return c.name +} + +func (this *Container) SetName(name string) *Container { + if name == "" { + panic("container name can not be empty") + } + + if this.name == name { + return this + } + + if this.name != "" { + panic("container name already set") + } + + this.name = name + return this +} + // Validate invokes all registered resolvers. It panics if any of them fails. // It is recommended to call this function on application start, or in the CI/CD test pipeline -// The objectif is to panic early when the container is bad configured. For eg: +// The objective is to panic early when the container is bad configured. For eg: // -// - (1) Missing depedency (forget to register certain resolvers) +// - (1) Missing dependency (forget to register certain resolvers) // - (2) cyclic dependency // - (3) lifetime misalignment (a longer lifetime service depends on a shorter one). func (this *Container) Validate() { @@ -77,24 +100,20 @@ func (this *Container) Validate() { _, ctx = resolver.resolveService(this, ctx) } } - - this.lock.Lock() - defer this.lock.Unlock() - if this.isBuilt && this.isSealed == false { - this.isSealed = true - } } -func (this *Container) Build() { +// Seal puts the container into read-only mode, preventing any further registrations. +func (this *Container) Seal() { this.lock.Lock() defer this.lock.Unlock() - if this.isBuilt { + if this.isSealed { panic(alreadyBuilt) } - this.isBuilt = true + this.isSealed = true } -func (this *Container) IsBuilt() bool { - return this.isBuilt +// IsSealed checks whether the container is sealed (in readonly mode) +func (this *Container) IsSealed() bool { + return this.isSealed } diff --git a/errors.go b/errors.go index ec8c4fe..9bd22ea 100644 --- a/errors.go +++ b/errors.go @@ -10,6 +10,10 @@ func noValidImplementation[T any]() error { return fmt.Errorf("implementation not found for type: %s", reflect.TypeFor[T]()) } +func invalidKeyType(t reflect.Type) error { + return fmt.Errorf("cannot use type: `%s` as a key", t) +} + func nilVal[T any]() error { return fmt.Errorf("nil implementation for type: %s", reflect.TypeFor[T]()) } @@ -23,11 +27,11 @@ func cyclicDependency(resolver resolverMetadata) error { } func placeHolderValueNotProvided(resolver resolverMetadata) error { - return fmt.Errorf("No value has been provided for this place holder: %s", resolver) + return fmt.Errorf("No value has been provided for this placeholder: %s", resolver) } func typeAlreadyRegistered(typeID typeID) error { - return fmt.Errorf("The type '%s' has already been registered (as a Resolver or as a Place Holder). Cannot override it with other Place Holder", typeID) + return fmt.Errorf("The type '%s' has already been registered (as a Resolver or as a Placeholder). Cannot override it with other Placeholder", typeID) } var alreadyBuilt = errors.New("services container is already built") diff --git a/examples/placeholderdemo/main.go b/examples/placeholderdemo/main.go index fc00b2e..df90e9a 100644 --- a/examples/placeholderdemo/main.go +++ b/examples/placeholderdemo/main.go @@ -25,7 +25,7 @@ func main() { ore.RegisterPlaceHolder[string]("someConfig") //Seal registration, no further registration is allowed - ore.Build() + ore.Seal() ore.Validate() //a request arrive diff --git a/get_test.go b/get_test.go index 1449ae4..69b7697 100644 --- a/get_test.go +++ b/get_test.go @@ -75,14 +75,6 @@ func TestGetKeyed(t *testing.T) { } } -func TestGetKeyedUnhashable(t *testing.T) { - RegisterLazyCreator(Singleton, &simpleCounter{}, "a") - _, _ = Get[someCounter](context.Background(), "a") - - RegisterLazyCreator(Singleton, &simpleCounter{}, []string{"a", "b"}) - _, _ = Get[someCounter](context.Background(), []string{"a", "b"}) -} - func TestGetResolvedSingletons(t *testing.T) { t.Run("When multiple lifetimes and keys are registered", func(t *testing.T) { //Arrange diff --git a/key.go b/key.go index 27dfa07..b460f3d 100644 --- a/key.go +++ b/key.go @@ -1,17 +1,13 @@ package ore import ( - "fmt" + "reflect" "strconv" "strings" ) type KeyStringer any -type stringer interface { - String() string -} - func oreKey(key ...KeyStringer) string { if key == nil { return "" @@ -20,59 +16,53 @@ func oreKey(key ...KeyStringer) string { l := len(key) if l == 1 { - return stringifyOreKey(key[0]) + keyT, kV := stringifyOreKey(key[0]) + return keyT + kV } var sb strings.Builder for _, s := range key { - sb.WriteString(stringifyOreKey(s)) + keyT, keyV := stringifyOreKey(s) + sb.WriteString(keyT) + sb.WriteString(keyV) } return sb.String() } -func stringifyOreKey(key KeyStringer) string { +func stringifyOreKey(key KeyStringer) (string, string) { switch key.(type) { case nil: - return "" + return "n", "" case string: - return key.(string) - case bool: - return strconv.FormatBool(key.(bool)) + return "s", key.(string) case int: - return strconv.Itoa(key.(int)) + return "i", strconv.Itoa(key.(int)) case int8: - return strconv.FormatInt(int64(key.(int8)), 36) + return "i8", strconv.FormatInt(int64(key.(int8)), 36) case int16: - return strconv.FormatInt(int64(key.(int16)), 36) + return "i16", strconv.FormatInt(int64(key.(int16)), 36) case int32: - return strconv.FormatInt(int64(key.(int32)), 36) + return "i32", strconv.FormatInt(int64(key.(int32)), 36) case int64: - return strconv.FormatInt(key.(int64), 36) + return "i64", strconv.FormatInt(key.(int64), 36) case uint: - return strconv.FormatUint(uint64(key.(uint)), 36) + return "ui", strconv.FormatUint(uint64(key.(uint)), 36) case uint8: - return strconv.FormatUint(uint64(key.(uint8)), 36) + return "ui8", strconv.FormatUint(uint64(key.(uint8)), 36) case uint16: - return strconv.FormatUint(uint64(key.(uint16)), 36) + return "ui16", strconv.FormatUint(uint64(key.(uint16)), 36) case uint32: - return strconv.FormatUint(uint64(key.(uint32)), 36) + return "ui32", strconv.FormatUint(uint64(key.(uint32)), 36) case uint64: - return strconv.FormatUint(key.(uint64), 36) + return "ui64", strconv.FormatUint(key.(uint64), 36) case float32: - return strconv.FormatFloat(float64(key.(float32)), 'x', -1, 32) + return "f32", strconv.FormatFloat(float64(key.(float32)), 'x', -1, 32) case float64: - return strconv.FormatFloat(key.(float64), 'x', -1, 64) - case stringer: - return key.(stringer).String() - + return "f64", strconv.FormatFloat(key.(float64), 'x', -1, 64) default: - return stringifyOreKeyUnknown(key) + panic(invalidKeyType(reflect.TypeOf(key))) } } - -func stringifyOreKeyUnknown(key KeyStringer) string { - return fmt.Sprintf("%v", key) -} diff --git a/key_test.go b/key_test.go index 0237977..b7cb9e7 100644 --- a/key_test.go +++ b/key_test.go @@ -1,14 +1,15 @@ package ore import ( + "github.com/stretchr/testify/assert" "testing" ) func TestOreKeyNil(t *testing.T) { k := oreKey(nil) - if got := k; got != "" { - t.Errorf("got `%v`, expected `%v`", got, "") + if got := k; got != "n" { + t.Errorf("got `%v`, expected `%v`", got, "n") } } @@ -16,13 +17,13 @@ func TestOreKeyEmpty(t *testing.T) { k := oreKey() if got := k; got != "" { - t.Errorf("got `%v`, expected `%v`", got, "") + t.Errorf("got `%v`, expected `%v`", got, "s") } } func TestOreKey1String(t *testing.T) { k := oreKey("ore") - expect := "ore" + expect := "sore" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -31,7 +32,7 @@ func TestOreKey1String(t *testing.T) { func TestOreKey2String(t *testing.T) { k := oreKey("ore", "package") - expect := "orepackage" + expect := "sorespackage" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -40,7 +41,7 @@ func TestOreKey2String(t *testing.T) { func TestOreKey1Int(t *testing.T) { k := oreKey(10) - expect := "10" + expect := "i10" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -49,7 +50,7 @@ func TestOreKey1Int(t *testing.T) { func TestOreKey2Int(t *testing.T) { k := oreKey(10, 30) - expect := "1030" + expect := "i10i30" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -58,7 +59,7 @@ func TestOreKey2Int(t *testing.T) { func TestOreKeyStringInt(t *testing.T) { k := oreKey("ore", 97) - expect := "ore97" + expect := "sorei97" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -67,7 +68,7 @@ func TestOreKeyStringInt(t *testing.T) { func TestOreKey2StringInt(t *testing.T) { k := oreKey("ore", 97, "di", 5) - expect := "ore97di5" + expect := "sorei97sdii5" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -78,7 +79,7 @@ func TestOreKeyUint(t *testing.T) { var n uint n = 5 k := oreKey(n) - expect := "5" + expect := "ui5" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -89,7 +90,7 @@ func TestOreKeyFloat32(t *testing.T) { var n float32 n = 5.751 k := oreKey(n) - expect := "0x1.701062p+02" + expect := "f320x1.701062p+02" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -99,7 +100,7 @@ func TestOreKeyFloat32(t *testing.T) { func TestOreKeyFloat64(t *testing.T) { var n float64 = 5.7519 k := oreKey(n) - expect := "0x1.701f212d77319p+02" + expect := "f640x1.701f212d77319p+02" if got := k; got != expect { t.Errorf("got `%v`, expected `%v`", got, expect) @@ -111,12 +112,9 @@ func TestOreKeyStringer(t *testing.T) { Counter: 16, } - k := oreKey(n) - expect := "Counter is: 16" - - if got := k; got != expect { - t.Errorf("got `%v`, expected `%v`", got, expect) - } + assert.Panics(t, func() { + oreKey(n) + }) } func TestOreKeyStruct(t *testing.T) { @@ -124,21 +122,14 @@ func TestOreKeyStruct(t *testing.T) { counter: 17, } - k := oreKey(n) - expect := "&{17}" - - if got := k; got != expect { - t.Errorf("got `%v`, expected `%v`", got, expect) - } + assert.Panics(t, func() { + oreKey(n) + }) } func TestOreKeyVarious(t *testing.T) { - k := oreKey("firas", 16, "ore", 3.14, 1/6, -9, -1494546.452, &simpleCounter{ - counter: 17, - }, &c{ - Counter: 18, - }) - expect := "firas16ore0x1.91eb851eb851fp+010-9-0x1.6ce1273b645a2p+20&{17}Counter is: 18" + 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/ore.go b/ore.go index 46b82eb..06d218a 100644 --- a/ore.go +++ b/ore.go @@ -8,9 +8,9 @@ var ( DefaultContainer = NewContainer() //contextKeysRepositoryID is a special context key. The value of this key is the collection of other context keys stored in the context. - contextKeysRepositoryID specialContextKey = "The context keys repository" + contextKeysRepositoryID specialContextKey = "__ORE_CTX_KEYS_REPO" //contextKeyResolversStack is a special context key. The value of this key is the [ResolversStack]. - contextKeyResolversStack specialContextKey = "Dependencies stack" + contextKeyResolversStack specialContextKey = "__ORE_DEP_STACK" //placeHolderResolverID is a special resolverID of every "placeHolder". "placeHolder" is a special resolver //describing a "promise" for a concrete value, which will be provided in runtime. @@ -23,6 +23,10 @@ type Creator[T any] interface { New(ctx context.Context) (T, context.Context) } +func init() { + DefaultContainer.SetName("DEFAULT") +} + // 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 { @@ -40,7 +44,7 @@ func typeIdentifier[T any](key ...KeyStringer) typeID { // Appends a service resolver to the container with type and key func addResolver[T any](this *Container, resolver serviceResolverImpl[T], key ...KeyStringer) { - if this.isBuilt { + if this.isSealed { panic(alreadyBuiltCannotAdd) } @@ -87,12 +91,14 @@ func addAliases[TInterface, TImpl any](this *Container) { this.aliases[aliasType] = append(this.aliases[aliasType], originalType) } -func Build() { - DefaultContainer.Build() +// Seal puts the DEFAULT container into read-only mode, preventing any further registrations. +func Seal() { + DefaultContainer.Seal() } -func IsBuilt() bool { - return DefaultContainer.IsBuilt() +// IsSealed checks whether the DEFAULT container is sealed (in readonly mode) +func IsSealed() bool { + return DefaultContainer.IsSealed() } // Validate invokes all registered resolvers. It panics if any of them fails. @@ -105,3 +111,11 @@ func IsBuilt() bool { func Validate() { DefaultContainer.Validate() } + +func ContainerID() int32 { + return DefaultContainer.containerID +} + +func Name() string { + return DefaultContainer.name +} diff --git a/ore_test.go b/ore_test.go index b1b7715..2321ddd 100644 --- a/ore_test.go +++ b/ore_test.go @@ -6,15 +6,70 @@ import ( "github.com/stretchr/testify/assert" ) -func TestBuild(t *testing.T) { +func TestSeal(t *testing.T) { clearAll() RegisterLazyCreator[someCounter](Scoped, &simpleCounter{}) - Build() + Seal() assert.Panics(t, func() { RegisterLazyCreator[someCounter](Scoped, &simpleCounter{}) }) } +func TestIsSeal(t *testing.T) { + clearAll() + RegisterLazyCreator[someCounter](Scoped, &simpleCounter{}) + if got := IsSealed(); got != false { + t.Errorf("IsSealed() = %v; want %v", got, false) + } + + Seal() + + if got := IsSealed(); got != true { + t.Errorf("IsSealed() = %v; want %v", got, true) + } +} + +func TestContainerName(t *testing.T) { + if got := Name(); got != "DEFAULT" { + t.Errorf("Name() = %v; want %v", got, "DEFAULT") + } +} + +func TestContainerSetName(t *testing.T) { + con := NewContainer().SetName("ORE") + if got := con.Name(); got != "ORE" { + t.Errorf("Name() = %v; want %v", got, "ORE") + } +} + +func TestNewContainerName(t *testing.T) { + con := NewContainer() + if got := con.Name(); got != "" { + t.Errorf("Name() = `%v`; want `%v`", got, "") + } +} + +func TestContainerReSetName(t *testing.T) { + con := NewContainer().SetName("ORE") + assert.Panics(t, func() { + con.SetName("ORE1") + }) +} + +func TestContainerID(t *testing.T) { + if got := ContainerID(); got != 1 { + t.Errorf("ContainerID() = %v; want 1", got) + } +} + +func TestNewContainerIDSerial(t *testing.T) { + clearAll() + con := NewContainer() + if got := con.ContainerID(); got != 2 { + t.Errorf("ContainerID() = %v; want 2", got) + } +} + type A1 struct{} type A2 struct{} diff --git a/registrars.go b/registrars.go index 8537f17..26cddca 100644 --- a/registrars.go +++ b/registrars.go @@ -105,7 +105,7 @@ func RegisterPlaceHolderToContainer[T comparable](con *Container, key ...KeyStri addResolver[T](con, e, key...) } -// RegisterPlaceHolderToContainer registers a future value with Scoped lifetime. +// 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. @@ -115,7 +115,7 @@ func RegisterPlaceHolder[T comparable](key ...KeyStringer) { // 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) Place holder registered. Checkout the [RegisterPlaceHolderToContainer] function for more info. +// 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, @@ -133,7 +133,7 @@ func ProvideScopedValueToContainer[T comparable](con *Container, ctx context.Con // 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) Place holder registered. Checkout the [RegisterPlaceHolder] function for more info. +// 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 8e643b3..11d53a1 100644 --- a/registrars_placeholder_test.go +++ b/registrars_placeholder_test.go @@ -16,7 +16,7 @@ func TestPlaceHolder_HappyPath(t *testing.T) { RegisterPlaceHolder[*m.Trader]() //get the placeHolder value would failed - assert2.PanicsWithError(t, assert2.ErrorStartsWith("No value has been provided for this place holder"), func() { + assert2.PanicsWithError(t, assert2.ErrorStartsWith("No value has been provided for this placeholder"), func() { _, _ = Get[*m.Trader](context.Background()) }) @@ -31,7 +31,7 @@ func TestPlaceHolder_HappyPath(t *testing.T) { trader, _ := Get[*m.Trader](ctx) assert.Equal(t, "Peter", trader.Name) - //get list will include the place holder value + //get list will include the placeholder value traders, ctx = GetList[*m.Trader](ctx) assert.Equal(t, 1, len(traders)) @@ -42,7 +42,7 @@ func TestPlaceHolder_HappyPath(t *testing.T) { person, ctx := Get[m.IPerson](ctx) assert.Equal(t, "Peter", person.(*m.Trader).Name) - //get list will include the place holder value + //get list will include the placeholder value persons, _ := GetList[m.IPerson](ctx) assert.Equal(t, 1, len(persons)) } @@ -134,7 +134,7 @@ func TestPlaceHolder_OverridePlaceHolder(t *testing.T) { assert.True(t, tradersListContainsName(traders, "Mary")) } -// place holder value of a module is not accessible from other module +// placeholder value of a module is not accessible from other module func TestPlaceHolder_PerModule(t *testing.T) { con1 := NewContainer() RegisterPlaceHolderToContainer[*m.Trader](con1) @@ -146,7 +146,7 @@ func TestPlaceHolder_PerModule(t *testing.T) { trader, ctx := GetFromContainer[*m.Trader](con1, ctx) assert.Equal(t, "John", trader.Name) - assert2.PanicsWithError(t, assert2.ErrorStartsWith("No value has been provided for this place holder"), func() { + assert2.PanicsWithError(t, assert2.ErrorStartsWith("No value has been provided for this placeholder"), func() { trader, ctx = GetFromContainer[*m.Trader](con2, ctx) }) } diff --git a/serviceResolver.go b/serviceResolver.go index a2c9005..aeb185e 100644 --- a/serviceResolver.go +++ b/serviceResolver.go @@ -26,7 +26,7 @@ type serviceResolver interface { providePlaceHolderDefaultValue(ctn *Container, ctx context.Context) context.Context // isScopedValueResolved returns true if this resolver is a scoped resolver and the scoped value has been already resolved. - // in case this resolver is a placeHolder, then it returns true if the place holder value has been provided. + // in case this resolver is a placeHolder, then it returns true if the placeholder value has been provided. isScopedValueResolved(ctx context.Context) bool } @@ -77,7 +77,7 @@ func (this serviceResolverImpl[T]) resolveService(ctn *Container, ctx context.Co // get the currentStack from the context var currentStack resolversStack - if !ctn.isSealed && !ctn.DisableValidation { + if !ctn.DisableValidation { untypedCurrentStack := ctx.Value(contextKeyResolversStack) if untypedCurrentStack != nil { currentStack = untypedCurrentStack.(resolversStack) @@ -100,7 +100,7 @@ func (this serviceResolverImpl[T]) resolveService(ctn *Container, ctx context.Co // this resolver is about to create a new concrete value, we have to put it to the resolversStack until the creation done var marker *list.Element - if !ctn.isSealed && !ctn.DisableValidation { + if !ctn.DisableValidation { if currentStack == nil { currentStack = list.New() ctx = context.WithValue(ctx, contextKeyResolversStack, currentStack) @@ -120,7 +120,7 @@ func (this serviceResolverImpl[T]) resolveService(ctn *Container, ctx context.Co } invocationLevel := 0 - if !ctn.isSealed && !ctn.DisableValidation { + if !ctn.DisableValidation { invocationLevel = currentStack.Len() //the concreteValue is created, we must pop the current resolvers from the stack @@ -217,7 +217,7 @@ func (this serviceResolverImpl[T]) providePlaceHolderDefaultValue(ctn *Container } // isScopedValueResolved returns true if the scoped value has been already resolved. -// we need this to know if the place holder value has been provided? +// we need this to know if the placeholder value has been provided? func (this serviceResolverImpl[T]) isScopedValueResolved(ctx context.Context) bool { return ctx.Value(this.id) != nil } diff --git a/utils.go b/utils.go index ca8651c..a13434e 100644 --- a/utils.go +++ b/utils.go @@ -3,6 +3,7 @@ package ore import ( "fmt" "strings" + "sync/atomic" ) type specialContextKey string @@ -26,7 +27,10 @@ func isNil[T comparable](impl T) bool { func (this *Container) clearAll() { this.resolvers = make(map[typeID][]serviceResolver) this.aliases = make(map[pointerTypeName][]pointerTypeName) - this.isBuilt = false + this.isSealed = false + this.DisableValidation = false + lastContainerID = atomic.Int32{} + lastContainerID.Add(1) } func clearAll() {