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
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-03-05 19:49:45 +02:00
parent 5ee06f9a92
commit b6445c7238
4 changed files with 133 additions and 14 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}