From b6445c72380be10f711e1beebcd9a2ca2e20f276 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 5 Mar 2020 19:49:45 +0200 Subject: [PATCH] next version preparation: hero: add a Container.Inject method to inject values outside of HTTP lifecycle, e.g. a database may be used by other services outside of Iris, the hero container (and API's Builder.GetContainer()) should provide it. Former-commit-id: 89863055a3a3ab108a3f4b753072a35321a3a193 --- hero/binding.go | 14 +++++---- hero/container.go | 69 ++++++++++++++++++++++++++++++++++++++++++ hero/container_test.go | 46 ++++++++++++++++++++++++++-- hero/reflect.go | 18 ++++++++--- 4 files changed, 133 insertions(+), 14 deletions(-) diff --git a/hero/binding.go b/hero/binding.go index 68ceba0a..c34ebe62 100644 --- a/hero/binding.go +++ b/hero/binding.go @@ -125,13 +125,15 @@ func getBindingsFor(inputs []reflect.Type, deps []*Dependency, paramsCount int) // Therefore, count the inputs that can be path parameters first. shouldBindParams := make(map[int]struct{}) totalParamsExpected := 0 - for i, in := range inputs { - if _, canBePathParameter := context.ParamResolvers[in]; !canBePathParameter { - continue - } - shouldBindParams[i] = struct{}{} + if paramsCount != -1 { + for i, in := range inputs { + if _, canBePathParameter := context.ParamResolvers[in]; !canBePathParameter { + continue + } + shouldBindParams[i] = struct{}{} - totalParamsExpected++ + totalParamsExpected++ + } } startParamIndex := paramsCount - totalParamsExpected diff --git a/hero/container.go b/hero/container.go index a9a60d8c..609ecd9e 100644 --- a/hero/container.go +++ b/hero/container.go @@ -2,7 +2,9 @@ package hero import ( stdContext "context" + "errors" "net/http" + "reflect" "time" "github.com/kataras/iris/v12/context" @@ -177,3 +179,70 @@ func (c *Container) HandlerWithParams(fn interface{}, paramsCount int) context.H func (c *Container) Struct(ptrValue interface{}, partyParamsCount int) *Struct { return makeStruct(ptrValue, c, partyParamsCount) } + +/* +func (c *Container) Inject(ctx context.Context, toPtr ...interface{}) error { + types := make([]reflect.Type, 0, len(toPtr)) + for _, ptr := range toPtr { + types = append(types, indirectType(typeOf(ptr))) + } + + bindings := getBindingsFor(types, c.Dependencies, -1) + + for _, b := range bindings { + v, err := b.Dependency.Handle(ctx, b.Input) + if err != nil { + if err == ErrSeeOther { + continue + } + + return err + } + + reflect.ValueOf(toPtr).Set(v) + } + + return nil +}*/ + +// ErrMissingDependency may returned only from the `Container.Inject` method +// when not a matching dependency found for "toPtr". +var ErrMissingDependency = errors.New("missing dependency") + +// Inject SHOULD only be used outside of HTTP handlers (performance is not priority for this method) +// as it does not pre-calculate the available list of bindings for the "toPtr" and the registered dependencies. +// +// It sets a static-only matching dependency to the value of "toPtr". +// The parameter "toPtr" SHOULD be a pointer to a value corresponding to a dependency, +// like input parameter of a handler or field of a struct. +// +// If no matching dependency found, the `Inject` method returns an `ErrMissingDependency` and +// "toPtr" keeps its original state (e.g. nil). +// +// Example Code: +// c.Register(&LocalDatabase{...}) +// [...] +// var db Database +// err := c.Inject(&db) +func (c *Container) Inject(toPtr interface{}) error { + val := reflect.Indirect(valueOf(toPtr)) + typ := val.Type() + + for _, d := range c.Dependencies { + if d.Static && matchDependency(d, typ) { + v, err := d.Handle(nil, &Input{Type: typ}) + if err != nil { + if err == ErrSeeOther { + continue + } + + return err + } + + val.Set(v) + return nil + } + } + + return ErrMissingDependency +} diff --git a/hero/container_test.go b/hero/container_test.go index 037fbfe3..1159f0a2 100644 --- a/hero/container_test.go +++ b/hero/container_test.go @@ -46,14 +46,54 @@ var ( } ) -func TestHeroHandler(t *testing.T) { +func TestContainerHandler(t *testing.T) { app := iris.New() - b := New() - postHandler := b.Handler(fn) + c := New() + postHandler := c.Handler(fn) app.Post("/{id:int}", postHandler) e := httptest.New(t, app) path := fmt.Sprintf("/%d", expectedOutput.ID) e.POST(path).WithJSON(input).Expect().Status(httptest.StatusOK).JSON().Equal(expectedOutput) } + +func TestContainerInject(t *testing.T) { + c := New() + + expected := testInput{Name: "test"} + c.Register(expected) + c.Register(&expected) + + // struct value. + var got1 testInput + if err := c.Inject(&got1); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(expected, got1) { + t.Fatalf("[struct value] expected: %#+v but got: %#+v", expected, got1) + } + + // ptr. + var got2 *testInput + if err := c.Inject(&got2); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(&expected, got2) { + t.Fatalf("[ptr] expected: %#+v but got: %#+v", &expected, got2) + } + + // register implementation, expect interface. + expected3 := &testServiceImpl{prefix: "prefix: "} + c.Register(expected3) + + var got3 testService + if err := c.Inject(&got3); err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected3, got3) { + t.Fatalf("[service] expected: %#+v but got: %#+v", expected3, got3) + } +} diff --git a/hero/reflect.go b/hero/reflect.go index fdbd6a62..655033b5 100644 --- a/hero/reflect.go +++ b/hero/reflect.go @@ -7,14 +7,23 @@ import ( ) func valueOf(v interface{}) reflect.Value { - if v, ok := v.(reflect.Value); ok { + if val, ok := v.(reflect.Value); ok { // check if it's already a reflect.Value. - return v + return val } return reflect.ValueOf(v) } +func typeOf(typ interface{}) reflect.Type { + if v, ok := typ.(reflect.Type); ok { + // check if it's already a reflect.Type. + return v + } + + return reflect.TypeOf(typ) +} + // indirectType returns the value of a pointer-type "typ". // If "typ" is a pointer, array, chan, map or slice it returns its Elem, // otherwise returns the typ as it's. @@ -82,9 +91,8 @@ func equalTypes(binding reflect.Type, input reflect.Type) bool { // if accepts an interface, check if the given "got" type does // implement this "expected" user handler's input argument. if input.Kind() == reflect.Interface { - // fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String()) - // return got.Implements(expected) - // return expected.AssignableTo(got) + // fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", binding.String(), input.String()) + // return input.Implements(binding) return binding.AssignableTo(input) }