Skip to content

Commit

Permalink
🌋 Merge pull request #14 from Wrapped-Owls/develop
Browse files Browse the repository at this point in the history
Remy 1.8.2 - Improve DuckType Get for factory + lazy singletone injections
  • Loading branch information
Jictyvoo committed Jul 11, 2024
2 parents 4b84db1 + f8ce2c3 commit b425d50
Show file tree
Hide file tree
Showing 14 changed files with 367 additions and 33 deletions.
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

0 comments on commit b425d50

Please sign in to comment.