Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remy 1.8.2 - Improve DuckType Get for factory + lazy singletone injections #14

Merged
merged 9 commits into from
Jul 11, 2024
3 changes: 3 additions & 0 deletions .github/workflows/build_examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,8 @@ jobs:
- name: Build bindlogger example
run: cd ./examples/bindlogger && go build -v ./...

- name: Build dynamic constructors example
run: cd ./examples/dynamiconstructor && go build -v ./...

- name: Build guessing_types example
run: cd ./examples/guessing_types && go build -v ./...
14 changes: 14 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,17 @@
- Upgrade minimal go-version to _1.20_
- This was made to be able to use any as comparable
- Remove `GenerifyInterfaces=true` from default injector Config

## 20240526 - remy/1.8.2

- Update `GetAll` method (and also duck type get) to retrieve objects stored as **Bind[T]**
- This update add a little more overhead to check if the bind implements the type we're searching for
- Created a new function on Binds to try to prevent stack-overflow and cycle dependency get
- If everything matches, this new way of returning duck-type elements will call the Generate method on bind
- It must be very careful about cycle dependencies now more than ever
- Add tests to cover the new duckType mode with `Bind[T]`
- Add new functions to register constructors with the injector.
- `RegisterConstructor` - `RegisterConstructorErr`
- `RegisterConstructorArgs1` - `RegisterConstructorArgs1Err`
- `RegisterConstructorArgs2` - `RegisterConstructorArgs2Err`
- Add tests to cover new constructor registration.
5 changes: 5 additions & 0 deletions examples/dynamiconstructor/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/wrapped-owls/goremy-di/examples/dynamiconstructor

go 1.20

require github.com/wrapped-owls/goremy-di/remy v1.8.0
2 changes: 2 additions & 0 deletions examples/dynamiconstructor/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/wrapped-owls/goremy-di/remy v1.8.0 h1:dcCV+acizHoa1DJDjMN2nrH7oxi6Ftx7eda8wy2H+tY=
github.com/wrapped-owls/goremy-di/remy v1.8.0/go.mod h1:u3y4TeiYnQNaEhKRbl3tyKPFpH8m/bV5wRenf4KmaDs=
29 changes: 29 additions & 0 deletions examples/dynamiconstructor/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"log"
"time"

"github.com/wrapped-owls/goremy-di/remy"
)

func registerInjections(ij remy.Injector) {
remy.RegisterInstance(ij, "That's a nice test")
remy.RegisterConstructor(ij, remy.Factory[time.Time], time.Now)
remy.RegisterConstructorArgs2(ij, remy.Factory[Note], NewAnnotation)
remy.RegisterConstructorArgs1(ij, remy.LazySingleton[FolderChecker], NewFolderChecker)
}

func main() {
inj := remy.NewInjector(remy.Config{UseReflectionType: false, DuckTypeElements: true})
// Registering injections
registerInjections(inj)

folderChecker := remy.GetGenFunc[FolderChecker](inj, func(injector remy.Injector) error {
remy.RegisterInstance(injector, "Trying to retrieve program current folder")
return nil
})
absPath := folderChecker.RunningAbsolute()
log.Println(absPath)
log.Println(remy.Get[string](inj))
}
34 changes: 34 additions & 0 deletions examples/dynamiconstructor/newtypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import "time"

// Note is a simple structure to hold an annotation
type Note struct {
Message string
Time time.Time
}

// NewAnnotation is a constructor for Note
func NewAnnotation(message string, currentTime time.Time) Note {
return Note{
Message: message,
Time: currentTime,
}
}

// FolderChecker is an implementation of FileFolderChecker
type FolderChecker struct {
Path string
}

// NewFolderChecker is a constructor for FolderChecker
func NewFolderChecker(note Note) FolderChecker {
return FolderChecker{
Path: "/absolute/path/" + note.Message,
}
}

// RunningAbsolute returns the absolute path
func (fc FolderChecker) RunningAbsolute() string {
return fc.Path
}
10 changes: 10 additions & 0 deletions examples/go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
go 1.20

use (
basic
bindlogger
dynamiconstructor
guessing_types

../remy
)
35 changes: 35 additions & 0 deletions remy/internal/binds/binds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package binds

import "github.com/wrapped-owls/goremy-di/remy/internal/types"

type bindWrapper[T any] struct {
types.Bind[T]
}

func (b bindWrapper[T]) PointerValue() any {
return new(T)
}

func (b bindWrapper[T]) GenAsAny(retriever types.DependencyRetriever) (any, error) {
return b.Generates(retriever)
}

func Singleton[T any](binder types.Binder[T]) types.Bind[T] {
return bindWrapper[T]{&SingletonBind[T]{binder: binder}}
}

func LazySingleton[T any](binder types.Binder[T]) types.Bind[T] {
return bindWrapper[T]{&SingletonBind[T]{binder: binder, IsLazy: true}}
}

func Factory[T any](binder types.Binder[T]) types.Bind[T] {
return bindWrapper[T]{FactoryBind[T]{binder: binder, IsFactory: true}}
}

func Instance[T any](element T) types.Bind[T] {
return bindWrapper[T]{FactoryBind[T]{
binder: func(retriever types.DependencyRetriever) (T, error) {
return element, nil
},
}}
}
File renamed without changes.
31 changes: 0 additions & 31 deletions remy/internal/binds/constructors.go

This file was deleted.

33 changes: 32 additions & 1 deletion remy/internal/injector/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ func Register[T any](ij types.Injector, bind types.Bind[T], keys ...string) erro
return ij.Bind(elementType, value)
}

func checkSavedAsBind[T any](
retriever types.DependencyRetriever, checkElem any,
) (foundElem *T, err error) {
if genericBind, assertOk := checkElem.(interface {
PointerValue() any
GenAsAny(injector types.DependencyRetriever) (any, error)
}); assertOk {
// Check if the returned value can implement the requested interface
if _, ok := genericBind.PointerValue().(T); !ok {
return
}
var anyVal any
if anyVal, err = genericBind.GenAsAny(retriever); err != nil {
return
} else if bindElem, ok := anyVal.(T); ok {
foundElem = &bindElem
}
}
return
}

func GetAll[T any](
retriever types.DependencyRetriever, optKey ...string,
) (resultList []T, err error) {
Expand All @@ -46,8 +67,18 @@ func GetAll[T any](

resultList = make([]T, 0, len(elementList))
for _, checkElem := range elementList {
if instanceBind, assertOk := checkElem.(T); assertOk {
switch instanceBind := checkElem.(type) {
case T:
resultList = append(resultList, instanceBind)
default:
var foundElem *T
if foundElem, err = checkSavedAsBind[T](retriever, checkElem); err != nil {
return
}

if foundElem != nil {
resultList = append(resultList, *foundElem)
}
}
}

Expand Down
31 changes: 30 additions & 1 deletion remy/internal/injector/methods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,10 @@ func TestGetGen(t *testing.T) {
}

for _, tCase := range testCases {
i := New(injopts.CacheOptAllowOverride, types.ReflectionOptions{UseReflectionType: tCase.useReflection})
i := New(
injopts.CacheOptAllowOverride,
types.ReflectionOptions{UseReflectionType: tCase.useReflection},
)
_ = Register(
i, binds.Factory(
func(retriever types.DependencyRetriever) (result string, err error) {
Expand Down Expand Up @@ -469,3 +472,29 @@ func TestGet_guessSubtypes(t *testing.T) {
t.Run("Uint8 subtype", testGuestSubtype[SubTypeUint8, uint8])
t.Run("Float64 subtype", testGuestSubtype[SubTypeFloat64, uint8])
}

func TestGetAll_withGeneratedBind(t *testing.T) {
const expectedLanguage = "Portuguese"
i := New(injopts.CacheOptReturnAll, types.ReflectionOptions{})
err := Register(
i,
binds.Factory(func(retriever types.DependencyRetriever) (fixtures.CountryLanguage, error) {
return fixtures.CountryLanguage{Language: expectedLanguage}, nil
}),
)
if err != nil {
t.Fatal(err)
}

var result fixtures.Language
if result, err = Get[fixtures.Language](i); err != nil {
t.Fatalf("Should not have gotten error when trying to find all subtypes")
}

if result.Name() != expectedLanguage {
t.Errorf(
"Result is not the same as expected\nReceived: `%v`\nExpected: `%v`",
result, expectedLanguage,
)
}
}
108 changes: 108 additions & 0 deletions remy/register_constructor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package remy

import (
"github.com/wrapped-owls/goremy-di/remy/internal/types"
)

type (
// ConstructorEmpty defines a constructor function with no arguments that returns a value of type T and an error.
ConstructorEmpty[T any] func() (T, error)

// ConstructorArg1 defines a constructor function with one argument of type K that returns a value of type T and an error.
ConstructorArg1[T, K any] func(K) (T, error)

// ConstructorArg2 defines a constructor function with two arguments of types K and P that returns a value of type T and an error.
ConstructorArg2[T, K, P any] func(K, P) (T, error)
)

// Binder calls the constructor function for ConstructorEmpty and returns the constructed value and any error encountered.
func (cons ConstructorEmpty[T]) Binder(types.DependencyRetriever) (T, error) {
return cons()
}

// Binder retrieves the dependency of type K, then calls the constructor function for ConstructorArg1 and returns the constructed value and any error encountered.
func (cons ConstructorArg1[T, K]) Binder(retriever types.DependencyRetriever) (value T, err error) {
var (
first K
)
if first, err = DoGet[K](retriever); err != nil {
return
}
return cons(first)
}

// Binder retrieves the dependencies of types K and P, then calls the constructor function for ConstructorArg2 and returns the constructed value and any error encountered.
func (cons ConstructorArg2[T, K, P]) Binder(retriever types.DependencyRetriever) (value T, err error) {
var (
first K
second P
)
if first, err = DoGet[K](retriever); err != nil {
return
}
if second, err = DoGet[P](retriever); err != nil {
return
}

return cons(first, second)
}

// RegisterConstructorErr registers a constructor function that returns a value of type T and an error.
func RegisterConstructorErr[T any](
i Injector, bindFunc func(binder types.Binder[T]) Bind[T],
constructor func() (T, error), keys ...string,
) {
var generator = ConstructorEmpty[T](constructor)
Register(mustInjector(i), bindFunc(generator.Binder), keys...)
}

// RegisterConstructor registers a constructor function that returns a value of type T without an error.
func RegisterConstructor[T any](
i Injector, bindFunc func(binder types.Binder[T]) Bind[T],
constructor func() T, keys ...string,
) {
var generator ConstructorEmpty[T] = func() (T, error) {
return constructor(), nil
}
RegisterConstructorErr(i, bindFunc, generator, keys...)
}

// RegisterConstructorArgs1Err registers a constructor function with one argument that returns a value of type T and an error.
func RegisterConstructorArgs1Err[T, K any](
i Injector, bindFunc func(binder types.Binder[T]) Bind[T],
constructor func(K) (T, error), keys ...string,
) {
generator := ConstructorArg1[T, K](constructor)
Register(mustInjector(i), bindFunc(generator.Binder), keys...)
}

// RegisterConstructorArgs1 registers a constructor function with one argument that returns a value of type T without an error.
func RegisterConstructorArgs1[T, K any](
i Injector, bindFunc func(binder types.Binder[T]) Bind[T],
constructor func(K) T, keys ...string,
) {
generator := func(arg K) (T, error) {
return constructor(arg), nil
}
RegisterConstructorArgs1Err(i, bindFunc, generator, keys...)
}

// RegisterConstructorArgs2Err registers a constructor function with two arguments that returns a value of type T and an error.
func RegisterConstructorArgs2Err[T, K, P any](
i Injector, bindFunc func(binder types.Binder[T]) Bind[T],
constructor func(K, P) (T, error), keys ...string,
) {
generator := ConstructorArg2[T, K, P](constructor)
Register(mustInjector(i), bindFunc(generator.Binder), keys...)
}

// RegisterConstructorArgs2 registers a constructor function with two arguments that returns a value of type T without an error.
func RegisterConstructorArgs2[T, K, P any](
i Injector, bindFunc func(binder types.Binder[T]) Bind[T],
constructor func(K, P) T, keys ...string,
) {
generator := func(arg1 K, arg2 P) (T, error) {
return constructor(arg1, arg2), nil
}
RegisterConstructorArgs2Err(i, bindFunc, generator, keys...)
}
Loading
Loading