From 6acf33e5124c0c973e0eeada89cc6dbd8538d9df Mon Sep 17 00:00:00 2001 From: Marko Milojevic Date: Mon, 17 Oct 2022 00:23:48 +0200 Subject: [PATCH] finished documentation --- .travis.yml | 1 - README.md | 137 +++++++++++++++- binding.go | 153 ++++++++++++++++-- chain.go | 104 ++++++++++-- example_test.go | 310 ------------------------------------ examples/annotation_test.go | 4 +- injection.go | 51 ++++++ key.go | 48 +++++- key_test.go | 14 -- 9 files changed, 461 insertions(+), 361 deletions(-) delete mode 100644 example_test.go diff --git a/.travis.yml b/.travis.yml index cc39974..785c441 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ language: go go: - 1.18 - stable - - master script: - go test -v ./... diff --git a/README.md b/README.md index 7361c58..f277ddd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,135 @@ -# genjector -Reflection-free Dependency Injection framework for Go 1.18+ +# Package Genjector +Reflection-free Run-Time Dependency Injection framework for Go 1.18+ + +## The Goal +The purpose of The Genjector package is to provide a Dependency +Injection framework without relying on reflection and depending solely +on Go Generics (provided from Go version 1.18). + +It supports many different features: ++ Binding concrete implementations to particular interfaces. ++ Binding implementations as references or values. ++ Binding implementations with Provider methods. ++ Binding implementations with concrete instances. ++ Define Binding as singletons. ++ Define annotations for Binding. ++ Define slices and maps of implementations. ++ ... + +## Benchmark +While providing the most of known features of Dependency Injection +frameworks, The Genjector Package also delivers top performance +comparing to current widely used Run-Time DI frameworks, (so, solutions +based on code generators are excluded). + +```shell +goos: darwin +goarch: amd64 +pkg: github.com/ompluscator/genjector/_benchmark +cpu: Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz +Benchmark +Benchmark/github.com/golobby/container/v3 +Benchmark/github.com/golobby/container/v3-8 2834061 409.6 ns/op +Benchmark/github.com/goava/di +Benchmark/github.com/goava/di-8 4568984 261.9 ns/op +Benchmark/github.com/goioc/di +Benchmark/github.com/goioc/di-8 19844284 60.66 ns/op +Benchmark/go.uber.org/dig +Benchmark/go.uber.org/dig-8 755488 1497 ns/op +Benchmark/flamingo.me/dingo +Benchmark/flamingo.me/dingo-8 2373394 503.7 ns/op +Benchmark/github.com/samber/do +Benchmark/github.com/samber/do-8 3585386 336.0 ns/op +Benchmark/github.com/ompluscator/genjector +Benchmark/github.com/ompluscator/genjector-8 21460600 55.71 ns/op +Benchmark/github.com/vardius/gocontainer +Benchmark/github.com/vardius/gocontainer-8 60947049 20.25 ns/op +Benchmark/github.com/go-kata/kinit +Benchmark/github.com/go-kata/kinit-8 733842 1451 ns/op +Benchmark/github.com/Fs02/wire +Benchmark/github.com/Fs02/wire-8 25099182 47.43 ns/op +PASS +``` + +## Examples +Detailed examples can be found inside inner "examples" package. + +Some simple code blocks can be found bellow. + +### Simple Reference +```go +package example + +type ServiceInterface interface { + String() string +} + +type Service struct { + value string +} + +func (s *Service) Init() { + s.value = "value provided inside the Service" +} + +func (s *Service) String() string { + return s.value +} + +err := genjector.Bind(genjector.AsReference[ServiceInterface, *Service]()) +if err != nil { + return err +} + +instance, err := genjector.Initialize[ServiceInterface]() +if err != nil { + return err +} + +value := instance.String() +if value != "value provided inside the Service" { + return err +} +``` + +### Complex Reference +```go +package example + +type ServiceInterface interface { + String() string +} + +type Service struct { + value string +} + +func (s *Service) Init() { + s.value = "value provided inside the Service" +} + +func (s *Service) String() string { + return s.value +} + +err := genjector.Bind( + genjector.AsReference[ServiceInterface, *Service](), + genjector.AsSingleton(), + genjector.WithAnnotation("service") +) +if err != nil { + return err +} + +instance, err := genjector.Initialize[ServiceInterface]( + genjector.WithAnnotation("service"), +) +if err != nil { + return err +} + +value := instance.String() +if value != "value provided inside the Service" { + return err +} +``` \ No newline at end of file diff --git a/binding.go b/binding.go index 2631100..a3d0758 100644 --- a/binding.go +++ b/binding.go @@ -2,11 +2,16 @@ package genjector import "fmt" +// bindingSource is a concrete implementation for BindingSource interface. type bindingSource[T any] struct { binding Binding keySource baseKeySource[T] } +// Binding returns containing instance of Binding interface. Initially it makes +// the concrete instance, to check if instance matches desired type of Binding. +// +// It respects BindingSource interface. func (s *bindingSource[T]) Binding() (Binding, error) { instance, err := s.binding.Instance(false) if err != nil { @@ -20,12 +25,28 @@ func (s *bindingSource[T]) Binding() (Binding, error) { return s.binding, nil } +// Key executes the same method from inner KeyOption instance. +// +// It respects BindingSource interface. func (s *bindingSource[T]) Key() Key { return s.keySource.Key() } +// Initializable represents any struct that contains a method Init. +// When such struct as defined AsReference or AsValue, method Init will be +// called during initialization process. +type Initializable interface { + Init() +} + +// valueBinding is a concrete implementation for Binding interface. type valueBinding[S any] struct{} +// Instance delivers the value of the concrete instance of type S. +// If the reference to the struct respects Initializable interface, +// Init method will be called. +// +// It respects Binding interface. func (valueBinding[S]) Instance(initialize bool) (interface{}, error) { initial := *new(S) var instance interface{} = &initial @@ -33,13 +54,21 @@ func (valueBinding[S]) Instance(initialize bool) (interface{}, error) { return initial, nil } - if value, ok := instance.(initializable); ok { + if value, ok := instance.(Initializable); ok { value.Init() } return initial, nil } +// AsValue delivers a BindingSource for a type T, by binding a value of a struct +// to the concrete interface (or the struct itself). It must be only used with value and +// not reference. In case reference is used, code will return a nil value for the instance. +// +// Example: +// err := genjector.Bind(genjector.AsValue[ValueInterface, ValueStruct]()) +// +// BindingSource can be only used as the first argument to Bind method. func AsValue[T any, S any]() BindingSource[T] { return &bindingSource[T]{ binding: valueBinding[S]{}, @@ -47,25 +76,34 @@ func AsValue[T any, S any]() BindingSource[T] { } } -type initializable interface { - Init() -} - +// referenceBinding is a concrete implementation for Binding interface. type referenceBinding[R any] struct{} +// Instance delivers the reference of the concrete instance of type S. +// If the struct respects Initializable interface, Init method will be called. +// +// It respects Binding interface. func (referenceBinding[R]) Instance(initialize bool) (interface{}, error) { var instance interface{} = new(R) if !initialize { return instance, nil } - if value, ok := instance.(initializable); ok { + if value, ok := instance.(Initializable); ok { value.Init() } return instance, nil } +// AsReference delivers a BindingSource for a type T, by binding reference of a struct +// to the concrete interface (or the struct itself). It must be only used with references and +// not values. In case values is used, code will panic. +// +// Example: +// err := genjector.Bind(genjector.AsReference[ReferenceInterface, *ReferenceStruct]()) +// +// BindingSource can be only used as the first argument to Bind method. func AsReference[T any, S *R, R any]() BindingSource[T] { return &bindingSource[T]{ binding: referenceBinding[R]{}, @@ -73,12 +111,33 @@ func AsReference[T any, S *R, R any]() BindingSource[T] { } } +// ProviderMethod defines a type of a method that should delivers +// an instance od type S. This method acts as an constructor method +// and it is executed at the time of Initialize method. +// +// It respects Binding interface. type ProviderMethod[S any] func() (S, error) -func (b ProviderMethod[S]) Instance(bool) (interface{}, error) { - return b() +// Instance delivers the concrete instance of type S, by executing +// root ProviderMethod itself. +// +// It respects Binding interface. +func (s ProviderMethod[S]) Instance(bool) (interface{}, error) { + return s() } +// AsProvider delivers a BindingSource for a type T, by defining a ProviderMethod +// (or constructor method) for the new instance of some interface (or a struct). +// +// Example: +// +// err := genjector.Bind(genjector.AsProvider[ProviderInterface](func() (*ProviderStruct, error) { +// return &ProviderStruct{ +// value: "value provided inside the ProviderMethod", +// }, nil +// })) +// +// BindingSource can be only used as the first argument to Bind method. func AsProvider[T any, S any](provider ProviderMethod[S]) BindingSource[T] { return &bindingSource[T]{ binding: provider, @@ -86,14 +145,30 @@ func AsProvider[T any, S any](provider ProviderMethod[S]) BindingSource[T] { } } +// instanceBinding is a concrete implementation for Binding interface. type instanceBinding[S any] struct { instance S } -func (b *instanceBinding[S]) Instance(bool) (interface{}, error) { - return b.instance, nil +// Instance delivers the concrete instance of type S, by returning already +// initialized instance that instanceBinding holds. +// +// It respects Binding interface. +func (s *instanceBinding[S]) Instance(bool) (interface{}, error) { + return s.instance, nil } +// AsInstance delivers a BindingSource for a type T, by using a concrete +// instance that is passed as an argument to AsInstance method, to returns +// that instance whenever it is required from Binding. +// +// Example: +// +// err := genjector.Bind(genjector.AsInstance[*InstanceStruct](&InstanceStruct{ +// value: "value provided in concrete instance", +// })) +// +// BindingSource can be only used as the first argument to Bind method. func AsInstance[T any, S any](instance S) BindingSource[T] { return &bindingSource[T]{ binding: &instanceBinding[S]{ @@ -103,29 +178,45 @@ func AsInstance[T any, S any](instance S) BindingSource[T] { } } +// bindingOption is a concrete implementation for BindingOption interface. type bindingOption struct { bindingFunc func(binding Binding) (Binding, error) keyOption KeyOption } +// Binding executes the inner bindingFunc method. +// +// It respects BindingOption interface. func (b *bindingOption) Binding(binding Binding) (Binding, error) { return b.bindingFunc(binding) } +// Key executes the same method from inner KeyOption instance. +// +// It respects BindingOption interface. func (b *bindingOption) Key(key Key) Key { return b.keyOption.Key(key) } +// Container executes the same method from inner KeyOption instance. +// +// It respects BindingOption interface. func (b *bindingOption) Container(container Container) Container { return b.keyOption.Container(container) } +// singletonBinding is a concrete implementation for Binding interface. type singletonBinding struct { parent Binding singleton interface{} initialized bool } +// Instance delivers already stored instance, which should be present if this +// method was already executed before. Otherwise it retrieves the instance from +// a child Binding and stores it internally for the next calls. +// +// It respects Binding interface. func (b *singletonBinding) Instance(initialize bool) (interface{}, error) { if b.initialized { return b.singleton, nil @@ -141,6 +232,21 @@ func (b *singletonBinding) Instance(initialize bool) (interface{}, error) { return instance, nil } +// AsSingleton delivers a BindingOption that defines the instance of desired +// Binding as a singleton. That means only first time the Init method (or ProviderMethod) +// will be called, and every next time the same instance will be delivered +// as a result of Initialize method. +// +// Example: +// err := genjector.Bind( +// +// genjector.AsReference[SingletonInterface, *SingletonStruct](), +// genjector.AsSingleton(), +// +// ) +// +// AsSingleton should be only used as a BindingOption for Bind method, as it +// does not affect functionality if it is used in Initialize method. func AsSingleton() BindingOption { return &bindingOption{ bindingFunc: func(binding Binding) (Binding, error) { @@ -152,6 +258,19 @@ func AsSingleton() BindingOption { } } +// WithAnnotation delivers a BindingOption that allows to name specific Binding +// with any annotation desired. +// +// Example: +// err = genjector.Bind( +// +// genjector.AsReference[AnnotationInterface, *AnnotationStruct](), +// genjector.WithAnnotation("first"), +// +// ) +// +// To properly use a customer Container, WithAnnotation should be used in both +// Bind and Initialize methods. func WithAnnotation(annotation string) BindingOption { return &bindingOption{ bindingFunc: func(binding Binding) (Binding, error) { @@ -163,6 +282,20 @@ func WithAnnotation(annotation string) BindingOption { } } +// WithContainer delivers a BindingOption that overrides the usage of standard +// internal (global) Container. It allows to provide a fresh, a custom instance +// of Container, that can be made from NewContainer method. +// +// Example: +// err := genjector.Bind( +// +// genjector.AsReference[ContainerInterface, *ContainerStruct](), +// genjector.WithContainer(customContainer), +// +// ) +// +// To properly use a customer Container, WithContainer should be used in both +// Bind and Initialize methods. func WithContainer(container Container) BindingOption { return &bindingOption{ bindingFunc: func(binding Binding) (Binding, error) { diff --git a/chain.go b/chain.go index 30d1ff2..309b7ac 100644 --- a/chain.go +++ b/chain.go @@ -2,11 +2,17 @@ package genjector import "fmt" +// sliceBinding is a concrete implementation for Binding interface. type sliceBinding[T any] struct { previous *sliceBinding[T] current Binding } +// Instance returns a slice of T types by executing current Binding and +// all other preceding ones. First it places previous in a slice, and +// then stores the instance of the current. +// +// It respects Binding interface. func (b *sliceBinding[T]) Instance(initialize bool) (interface{}, error) { var result []T if initialize && b.previous != nil { @@ -36,14 +42,21 @@ func (b *sliceBinding[T]) Instance(initialize bool) (interface{}, error) { return result, err } +// sliceBindingSource is a concrete implementation for BindingSource interface. type sliceBindingSource[T any] struct { previous Binding source BindingSource[T] keySource KeySource } -func (s *sliceBindingSource[T]) Binding() (Binding, error) { - binding, err := s.source.Binding() +// Binding returns an instance of a new Binding. If there is no any +// stored predecessor, it will deliver new Binding without containing any +// previous Binding. In case predecessor is defined, all will be returned +// together. +// +// It respects BindingSource interface. +func (b *sliceBindingSource[T]) Binding() (Binding, error) { + binding, err := b.source.Binding() if err != nil { return nil, err } @@ -58,7 +71,7 @@ func (s *sliceBindingSource[T]) Binding() (Binding, error) { return nil, fmt.Errorf(`binding is not possible for "%v" and "%v"`, initial, instance) } - previous, ok := s.previous.(*sliceBinding[T]) + previous, ok := b.previous.(*sliceBinding[T]) if !ok { return &sliceBinding[T]{ current: binding, @@ -71,14 +84,37 @@ func (s *sliceBindingSource[T]) Binding() (Binding, error) { }, nil } -func (s *sliceBindingSource[T]) SetPrevious(binding Binding) { - s.previous = binding +// SetPrevious stores preceding Binding as a previous one. +// +// It respects FollowingBindingSource interface. +func (b *sliceBindingSource[T]) SetPrevious(binding Binding) { + b.previous = binding } -func (s *sliceBindingSource[T]) Key() Key { - return s.keySource.Key() +// Key executes the same method from inner KeyOption instance. +// +// It respects BindingOption interface. +func (b *sliceBindingSource[T]) Key() Key { + return b.keySource.Key() } +// InSlice delivers a BindingSource for a slice of types T.. It is used as a wrapping +// BindingSource for any other inner. It creates complex Binding in the background +// that stores all T types in a slice and delivers it upon request by executing Initialize +// method for a slice of T types. +// +// Example: +// err := :genjector.Bind( +// +// genjector.InSlice( +// genjector.AsInstance[SliceInterface](&SliceStruct{ +// value: "concrete value", +// }), +// ), +// +// ) +// +// BindingSource can be only used as the first argument to Bind method. func InSlice[T any](source BindingSource[T]) BindingSource[T] { return &sliceBindingSource[T]{ source: source, @@ -86,12 +122,17 @@ func InSlice[T any](source BindingSource[T]) BindingSource[T] { } } +// mapBinding is a concrete implementation for Binding interface. type mapBinding[K comparable, T any] struct { previous *mapBinding[K, T] key K current Binding } +// Instance returns a map of K-T pairs by executing current Binding and +// all other preceding ones. +// +// It respects Binding interface. func (b *mapBinding[K, T]) Instance(initialize bool) (interface{}, error) { result := map[K]T{} if initialize && b.previous != nil { @@ -121,6 +162,7 @@ func (b *mapBinding[K, T]) Instance(initialize bool) (interface{}, error) { return result, err } +// mapBindingSource is a concrete implementation for BindingSource interface. type mapBindingSource[K comparable, T any] struct { previous Binding source BindingSource[T] @@ -128,8 +170,14 @@ type mapBindingSource[K comparable, T any] struct { keySource KeySource } -func (s *mapBindingSource[K, T]) Binding() (Binding, error) { - binding, err := s.source.Binding() +// Binding returns an instance of a new Binding. If there is no any +// stored predecessor, it will deliver new Binding without containing any +// previous Binding. In case predecessor is defined, all will be returned +// together. +// +// It respects BindingSource interface. +func (b *mapBindingSource[K, T]) Binding() (Binding, error) { + binding, err := b.source.Binding() if err != nil { return nil, err } @@ -140,29 +188,53 @@ func (s *mapBindingSource[K, T]) Binding() (Binding, error) { return nil, fmt.Errorf(`binding is not possible for "%v" and "%v"`, initial, instance) } - previous, ok := s.previous.(*mapBinding[K, T]) + previous, ok := b.previous.(*mapBinding[K, T]) if !ok { return &mapBinding[K, T]{ - key: s.key, + key: b.key, current: binding, }, nil } return &mapBinding[K, T]{ previous: previous, - key: s.key, + key: b.key, current: binding, }, nil } -func (s *mapBindingSource[K, T]) SetPrevious(binding Binding) { - s.previous = binding +// SetPrevious stores preceding Binding as a previous one. +// +// It respects FollowingBindingSource interface. +func (b *mapBindingSource[K, T]) SetPrevious(binding Binding) { + b.previous = binding } -func (s *mapBindingSource[K, T]) Key() Key { - return s.keySource.Key() +// Key executes the same method from inner KeyOption instance. +// +// It respects BindingOption interface. +func (b *mapBindingSource[K, T]) Key() Key { + return b.keySource.Key() } +// InMap delivers a BindingSource for a type T and key's type K, that creates a map +// of K-T pairs. It is used as a wrapping BindingSource for any other inner. It creates +// complex Binding in the background that stores all T types in a map and delivers it +// upon request by executing Initialize method for a K-T map. +// +// Example: +// err := :genjector.Bind( +// +// genjector.InMap( +// "third", +// genjector.AsInstance[MapInterface](&MapStruct{ +// value: "concrete value", +// }), +// ), +// +// ) +// +// BindingSource can be only used as the first argument to Bind method. func InMap[K comparable, T any](key K, source BindingSource[T]) BindingSource[T] { return &mapBindingSource[K, T]{ key: key, diff --git a/example_test.go b/example_test.go deleted file mode 100644 index 6c952cc..0000000 --- a/example_test.go +++ /dev/null @@ -1,310 +0,0 @@ -package genjector_test - -import ( - "github.com/ompluscator/genjector" - "strconv" - "testing" -) - -type ExampleInterface interface { - First() string - Second(arg string) int -} - -type ExampleOne struct { - first string - second int -} - -func (e *ExampleOne) Init() { - e.first = "example" - e.second = 10000 -} - -func (e *ExampleOne) First() string { - return e.first -} - -func (e *ExampleOne) Second(arg string) int { - value, _ := strconv.ParseInt(arg, 10, 64) - return e.second + int(value) -} - -func TestExampleOne(t *testing.T) { - genjector.Clean() - - err := genjector.Bind(genjector.AsReference[ExampleInterface, *ExampleOne]()) - if err != nil { - t.Error("binding should not cause an error") - } - - instance, err := genjector.Initialize[ExampleInterface]() - if err != nil { - t.Error("initialization should not cause an error") - } - - if instance.First() != "example" { - t.Error("unexpected value from method First()") - } - - if instance.Second("1") != 10001 { - t.Error(`unexpected value from method Second("1")`) - } - - err = genjector.Bind(genjector.AsReference[*ExampleOne, *ExampleOne]()) - if err != nil { - t.Error("binding should not cause an error") - } - - instance, err = genjector.Initialize[*ExampleOne]() - if err != nil { - t.Error("initialization should not cause an error") - } - - if instance.First() != "example" { - t.Error("unexpected value from method First()") - } - - if instance.Second("1") != 10001 { - t.Error(`unexpected value from method Second("1")`) - } -} - -type ExampleTwo struct{} - -func (e ExampleTwo) First() string { - return "example2" -} - -func (e ExampleTwo) Second(arg string) int { - value, _ := strconv.ParseInt(arg, 10, 64) - return 20000 + int(value) -} - -func TestExampleTwo(t *testing.T) { - genjector.Clean() - - err := genjector.Bind(genjector.AsValue[ExampleInterface, ExampleTwo]()) - if err != nil { - t.Error("binding should not cause an error") - } - - instance, err := genjector.Initialize[ExampleInterface]() - if err != nil { - t.Error("initialization should not cause an error") - } - - if instance.First() != "example2" { - t.Error("unexpected value from method First()") - } - - if instance.Second("1") != 20001 { - t.Error(`unexpected value from method Second("1")`) - } - - err = genjector.Bind(genjector.AsValue[ExampleTwo, ExampleTwo]()) - if err != nil { - t.Error("binding should not cause an error") - } - - instance, err = genjector.Initialize[ExampleTwo]() - if err != nil { - t.Error("initialization should not cause an error") - } - - if instance.First() != "example2" { - t.Error("unexpected value from method First()") - } - - if instance.Second("1") != 20001 { - t.Error(`unexpected value from method Second("1")`) - } -} - -func TestExampleThree(t *testing.T) { - genjector.Clean() - - err := genjector.Bind(genjector.InSlice(genjector.AsReference[ExampleInterface, *ExampleOne]())) - if err != nil { - t.Error("binding should not cause an error") - } - - err = genjector.Bind(genjector.InSlice(genjector.AsValue[ExampleInterface, ExampleTwo]())) - if err != nil { - t.Error("binding should not cause an error") - } - - err = genjector.Bind(genjector.InSlice(genjector.AsProvider[ExampleInterface](func() (*ExampleOne, error) { - return &ExampleOne{ - first: "example3", - second: 30000, - }, nil - }))) - if err != nil { - t.Error("binding should not cause an error") - } - - err = genjector.Bind(genjector.InSlice(genjector.AsInstance[ExampleInterface](&ExampleOne{ - first: "example4", - second: 40000, - }))) - if err != nil { - t.Error("binding should not cause an error") - } - - instance, err := genjector.Initialize[[]ExampleInterface]() - if err != nil { - t.Error("initialization should not cause an error") - } - - if instance[0].First() != "example" { - t.Error("unexpected value from method First()") - } - - if instance[0].Second("1") != 10001 { - t.Error(`unexpected value from method Second("1")`) - } - - if instance[1].First() != "example2" { - t.Error("unexpected value from method First()") - } - - if instance[1].Second("1") != 20001 { - t.Error(`unexpected value from method Second("1")`) - } - - if instance[2].First() != "example3" { - t.Error("unexpected value from method First()") - } - - if instance[2].Second("1") != 30001 { - t.Error(`unexpected value from method Second("1")`) - } - - if instance[3].First() != "example4" { - t.Error("unexpected value from method First()") - } - - if instance[3].Second("1") != 40001 { - t.Error(`unexpected value from method Second("1")`) - } -} - -func TestExampleFour(t *testing.T) { - genjector.Clean() - - err := genjector.Bind(genjector.InMap("first", genjector.AsReference[ExampleInterface, *ExampleOne]())) - if err != nil { - t.Error("binding should not cause an error") - } - - err = genjector.Bind(genjector.InMap("second", genjector.AsValue[ExampleInterface, ExampleTwo]())) - if err != nil { - t.Error("binding should not cause an error") - } - - err = genjector.Bind(genjector.InMap("third", genjector.AsProvider[ExampleInterface](func() (*ExampleOne, error) { - return &ExampleOne{ - first: "example3", - second: 30000, - }, nil - }))) - if err != nil { - t.Error("binding should not cause an error") - } - - err = genjector.Bind(genjector.InMap("fourth", genjector.AsInstance[ExampleInterface](&ExampleOne{ - first: "example4", - second: 40000, - }))) - if err != nil { - t.Error("binding should not cause an error") - } - - instance, err := genjector.Initialize[map[string]ExampleInterface]() - if err != nil { - t.Error("initialization should not cause an error") - } - - if instance["first"].First() != "example" { - t.Error("unexpected value from method First()") - } - - if instance["first"].Second("1") != 10001 { - t.Error(`unexpected value from method Second("1")`) - } - - if instance["second"].First() != "example2" { - t.Error("unexpected value from method First()") - } - - if instance["second"].Second("1") != 20001 { - t.Error(`unexpected value from method Second("1")`) - } - - if instance["third"].First() != "example3" { - t.Error("unexpected value from method First()") - } - - if instance["third"].Second("1") != 30001 { - t.Error(`unexpected value from method Second("1")`) - } - - if instance["fourth"].First() != "example4" { - t.Error("unexpected value from method First()") - } - - if instance["fourth"].Second("1") != 40001 { - t.Error(`unexpected value from method Second("1")`) - } -} - -type ExampleThree struct { - first string - second int - child ExampleInterface -} - -func (e *ExampleThree) Init() { - e.first = "parent" - e.second = 100000 - e.child = genjector.MustInitialize[ExampleInterface](genjector.AnnotatedWith("child")) -} - -func (e *ExampleThree) First() string { - return e.first + e.child.First() -} - -func (e *ExampleThree) Second(arg string) int { - value, _ := strconv.ParseInt(arg, 10, 64) - return e.second + int(value) + e.child.Second(arg) -} - -func TestExampleFive(t *testing.T) { - genjector.Clean() - - err := genjector.Bind(genjector.AsReference[ExampleInterface, *ExampleThree](), genjector.WithAnnotation("parent")) - if err != nil { - t.Error("binding should not cause an error") - } - - err = genjector.Bind(genjector.AsReference[ExampleInterface, *ExampleOne](), genjector.WithAnnotation("child")) - if err != nil { - t.Error("binding should not cause an error") - } - - instance, err := genjector.Initialize[ExampleInterface](genjector.AnnotatedWith("parent")) - if err != nil { - t.Error("initialization should not cause an error") - } - - first := instance.First() - if first != "parentexample" { - t.Errorf(`unexpected value from method First(): "%s" != "parentexample"`, first) - } - - second := instance.Second("1") - if second != 110002 { - t.Errorf(`unexpected value from method Second("1"): %d != 110002`, second) - } -} diff --git a/examples/annotation_test.go b/examples/annotation_test.go index 60bff8c..4617879 100644 --- a/examples/annotation_test.go +++ b/examples/annotation_test.go @@ -15,8 +15,8 @@ type AnnotationStruct struct { } func (s *AnnotationStruct) Init() { - firstChild := genjector.MustInitialize[*AnnotationChildStruct](genjector.AnnotatedWith("first")) - secondChild := genjector.MustInitialize[*AnnotationChildStruct](genjector.AnnotatedWith("second")) + firstChild := genjector.MustInitialize[*AnnotationChildStruct](genjector.WithAnnotation("first")) + secondChild := genjector.MustInitialize[*AnnotationChildStruct](genjector.WithAnnotation("second")) s.value = fmt.Sprintf("%s | %s", firstChild.value, secondChild.value) } diff --git a/injection.go b/injection.go index 16d5292..028b0fb 100644 --- a/injection.go +++ b/injection.go @@ -4,11 +4,16 @@ import ( "fmt" ) +// Key is a struct that contains information for Binding keys +// inside a Container. +// +// It is meant to be used only for internal purposes. type Key struct { Annotation string Value interface{} } +// Generate delivers a final Binding key for the Container. func (k Key) Generate() interface{} { if len(k.Annotation) > 0 { return [2]interface{}{k.Annotation, k.Value} @@ -16,42 +21,69 @@ func (k Key) Generate() interface{} { return k.Value } +// KeySource represents an interface that builds a Key for Binding. type KeySource interface { Key() Key } +// KeyOption represents an interface that overrides creation of Key +// and Container. type KeyOption interface { Key(key Key) Key Container(container Container) Container } +// Binding represents an interface that delivers new instance for +// particular interface (or a struct). type Binding interface { Instance(initialize bool) (interface{}, error) } +// BindingSource represents an interface that delivers starting Key and +// Binding instances, that later could be overriden by KeyOption or +// BindingOption. type BindingSource[T any] interface { Key() Key Binding() (Binding, error) } +// FollowingBindingSource represents an interface for a Binding instance +// that requires to get previous instance of Binding inside Container, +// before the new one should be stored on that place. type FollowingBindingSource[T any] interface { SetPrevious(binding Binding) } +// BindingOption represents an interface that overrides creation of Key, +// Binding and Container. type BindingOption interface { Key(key Key) Key Container(container Container) Container Binding(binding Binding) (Binding, error) } +// Container is a child type used for storing all Binding instances. type Container map[interface{}]Binding +// global is a concrete global Container var global = NewContainer() +// NewContainer delivers a new instance of Container. func NewContainer() Container { return map[interface{}]Binding{} } +// Bind executes complete logic for binding particular value (or reference) to +// desired interface (or struct). By default, it stores all Binding instances +// into default inner Container. +// +// It requires only BindingSource to be passed as an argument and all other +// instances of BindingOption are optional. +// +// At this point, if Binding for particular interface (or struct) is not defined, +// it uses its own fallback Binding. Still, it works fully only for values, not references, +// as for references it returns nil value. That means that reference Binding +// should be always defined. func Bind[T any](source BindingSource[T], options ...BindingOption) error { key := source.Key() @@ -86,6 +118,9 @@ func Bind[T any](source BindingSource[T], options ...BindingOption) error { return nil } +// MustBind wraps Bind method, by making sure error is not returned as an arguement. +// +// Still, in case of error, it panics. func MustBind[T any](source BindingSource[T], options ...BindingOption) { err := Bind(source, options...) if err != nil { @@ -93,6 +128,17 @@ func MustBind[T any](source BindingSource[T], options ...BindingOption) { } } +// Initialize executes complete logic for initializing value (or reference) for +// desired interface (or struct). By default, it uses Binding instance from default +// inner Container. If such Binding can not be found, it tries to make its own +// fallback Binding. +// +// All instances of BindingOption are optional. +// +// At this point, if Binding for particular interface (or struct) is not defined, +// it uses its own fallback Binding. Still, it works fully only for values, not references, +// as for references it returns nil value. That means that reference Binding +// should be always defined. func Initialize[T any](options ...KeyOption) (T, error) { var empty T source := &baseKeySource[T]{} @@ -129,6 +175,9 @@ func Initialize[T any](options ...KeyOption) (T, error) { return result, nil } +// MustInitialize wraps Initialize method, by making sure error is not returned as an arguement. +// +// Still, in case of error, it panics. func MustInitialize[T any](options ...KeyOption) T { instance, err := Initialize[T](options...) if err != nil { @@ -138,10 +187,12 @@ func MustInitialize[T any](options ...KeyOption) T { return instance } +// Clean creates a new instance of inner Container. func Clean() { global = NewContainer() } +// getFallbackBinding creates a new instance of fallback Binding. func getFallbackBinding[T any]() (Binding, error) { var binding Binding var err error diff --git a/key.go b/key.go index dd90bd4..1f5ab34 100644 --- a/key.go +++ b/key.go @@ -1,55 +1,87 @@ package genjector +// baseKeySource is a concrete implementation for KeyOption interface. type baseKeySource[T any] struct{} +// Key returns the instance of Key that represents a Container key for T type. +// +// It respects KeyOption interface. func (baseKeySource[T]) Key() Key { return Key{ Value: (*T)(nil), } } +// Container returns the same instance of Container struct provided as an argument. +// +// It respects KeyOption interface. func (baseKeySource[T]) Container(container Container) Container { return container } +// sliceKeySource is a concrete implementation for KeyOption interface. type sliceKeySource[T any] struct{} +// Key returns the instance of Key that represents a Container key for slice T types. +// +// It respects KeyOption interface. func (sliceKeySource[T]) Key() Key { return Key{ Value: (*[]T)(nil), } } +// Container returns the same instance of Container struct provided as an argument. +// +// It respects KeyOption interface. func (sliceKeySource[T]) Container(container Container) Container { return container } +// mapKeySource is a concrete implementation for KeyOption interface. type mapKeySource[K comparable, T any] struct{} +// Key returns the instance of Key that represents a Container key for map K-T pairs. +// +// It respects KeyOption interface. func (mapKeySource[K, T]) Key() Key { return Key{ Value: (*map[K]T)(nil), } } +// Container returns the same instance of Container struct provided as an argument. +// +// It respects KeyOption interface. func (mapKeySource[K, T]) Container(container Container) Container { return container } +// sameKeyOption is a concrete implementation for KeyOption interface. type sameKeyOption struct{} +// Key returns the same instance of Key struct provided as an argument. +// +// It respects KeyOption interface. func (sameKeyOption) Key(key Key) Key { return key } +// Container returns the same instance of Container struct provided as an argument. +// +// It respects KeyOption interface. func (sameKeyOption) Container(container Container) Container { return container } +// annotatedKeyOption is a concrete implementation for KeyOption interface. type annotatedKeyOption struct { annotation string } +// Key wrapped instance of Key with a new value for the annotation. +// +// It respects KeyOption interface. func (o *annotatedKeyOption) Key(key Key) Key { return Key{ Annotation: o.annotation, @@ -57,24 +89,28 @@ func (o *annotatedKeyOption) Key(key Key) Key { } } +// Container returns the same instance of Container struct provided as an argument. +// +// It respects KeyOption interface. func (*annotatedKeyOption) Container(container Container) Container { return container } -func AnnotatedWith(annotation string) KeyOption { - return &annotatedKeyOption{ - annotation: annotation, - } -} - +// containerKeyOption is a concrete implementation for KeyOption interface. type containerKeyOption struct { container Container } +// Key returns the same instance of Key struct provided as an argument. +// +// It respects KeyOption interface. func (*containerKeyOption) Key(key Key) Key { return key } +// Container returns an inner instance of Container. +// +// It respects KeyOption interface. func (o *containerKeyOption) Container(Container) Container { return o.container } diff --git a/key_test.go b/key_test.go index 5c4b3fa..f548d0d 100644 --- a/key_test.go +++ b/key_test.go @@ -214,20 +214,6 @@ func Test_annotatedKeyOption_Container(t *testing.T) { } } -func TestAnnotatedWith(t *testing.T) { - result := AnnotatedWith("annotation") - if !reflect.DeepEqual(&annotatedKeyOption{ - annotation: "annotation", - }, result) { - t.Error("annotatedKeyOption does not contain the right value") - } - - result = AnnotatedWith("") - if !reflect.DeepEqual(new(annotatedKeyOption), result) { - t.Error("annotatedKeyOption does not contain the right value") - } -} - func Test_containerKeyOption_Key(t *testing.T) { key := Key{ Value: 2,