diff --git a/hero/binding.go b/hero/binding.go index b30886dc..10a1ba7f 100644 --- a/hero/binding.go +++ b/hero/binding.go @@ -8,11 +8,13 @@ import ( "github.com/kataras/iris/v12/context" ) -type Binding struct { +// binding contains the Dependency and the Input, it's the result of a function or struct + dependencies. +type binding struct { Dependency *Dependency Input *Input } +// Input contains the input reference of which a dependency is binded to. type Input struct { Index int // for func inputs StructFieldIndex []int // for struct fields in order to support embedded ones. @@ -32,7 +34,8 @@ func newInput(typ reflect.Type, index int, structFieldIndex []int) *Input { return in } -func (b *Binding) String() string { +// String returns the string representation of a binding. +func (b *binding) String() string { index := fmt.Sprintf("%d", b.Input.Index) if len(b.Input.StructFieldIndex) > 0 { for j, i := range b.Input.StructFieldIndex { @@ -47,7 +50,8 @@ func (b *Binding) String() string { return fmt.Sprintf("[%s:%s] maps to [%s]", index, b.Input.Type.String(), b.Dependency) } -func (b *Binding) Equal(other *Binding) bool { +// Equal compares "b" and "other" bindings and reports whether they are referring to the same values. +func (b *binding) Equal(other *binding) bool { if b == nil { return other == nil } @@ -107,7 +111,7 @@ func matchDependency(dep *Dependency, in reflect.Type) bool { return dep.DestType == nil || equalTypes(dep.DestType, in) } -func getBindingsFor(inputs []reflect.Type, deps []*Dependency, paramStartIndex int) (bindings []*Binding) { +func getBindingsFor(inputs []reflect.Type, deps []*Dependency, paramStartIndex int) (bindings []*binding) { bindedInput := make(map[int]struct{}) // lastParamIndex is used to bind parameters correctly when: @@ -170,7 +174,7 @@ func getBindingsFor(inputs []reflect.Type, deps []*Dependency, paramStartIndex i d.OriginalValue = nil } - bindings = append(bindings, &Binding{ + bindings = append(bindings, &binding{ Dependency: d, Input: newInput(in, i, nil), }) @@ -201,7 +205,7 @@ func getBindingsFor(inputs []reflect.Type, deps []*Dependency, paramStartIndex i return } -func getBindingsForFunc(fn reflect.Value, dependencies []*Dependency, paramStartIndex int) []*Binding { +func getBindingsForFunc(fn reflect.Value, dependencies []*Dependency, paramStartIndex int) []*binding { fnTyp := fn.Type() if !isFunc(fnTyp) { panic("bindings: unresolved: not a func type") @@ -221,7 +225,7 @@ func getBindingsForFunc(fn reflect.Value, dependencies []*Dependency, paramStart return bindings } -func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, paramStartIndex int, sorter Sorter) (bindings []*Binding) { +func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, paramStartIndex int, sorter Sorter) (bindings []*binding) { typ := indirectType(v.Type()) if typ.Kind() != reflect.Struct { panic("bindings: unresolved: no struct type") @@ -232,7 +236,7 @@ func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, paramStar nonZero := lookupNonZeroFieldValues(elem) for _, f := range nonZero { // fmt.Printf("Controller [%s] | NonZero | Field Index: %v | Field Type: %s\n", typ, f.Index, f.Type) - bindings = append(bindings, &Binding{ + bindings = append(bindings, &binding{ Dependency: NewDependency(elem.FieldByIndex(f.Index).Interface()), Input: newInput(f.Type, f.Index[0], f.Index), }) @@ -268,7 +272,7 @@ func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, paramStar binding.Input.StructFieldIndex = structFieldIndex } - // fmt.Printf("Controller [%s] | Binding Index: %v | Binding Type: %s\n", typ, binding.Input.StructFieldIndex, binding.Input.Type) + // fmt.Printf("Controller [%s] | binding Index: %v | binding Type: %s\n", typ, binding.Input.StructFieldIndex, binding.Input.Type) // fmt.Printf("Controller [%s] Set [%s] to struct field index: %v\n", typ.String(), binding.Input.Type.String(), structFieldIndex) } @@ -280,8 +284,8 @@ func getBindingsForStruct(v reflect.Value, dependencies []*Dependency, paramStar Builtin dynamic bindings. */ -func paramBinding(index, paramIndex int, typ reflect.Type) *Binding { - return &Binding{ +func paramBinding(index, paramIndex int, typ reflect.Type) *binding { + return &binding{ Dependency: &Dependency{Handle: paramDependencyHandler(paramIndex), DestType: typ, Source: getSource()}, Input: newInput(typ, index, nil), } @@ -299,8 +303,8 @@ func paramDependencyHandler(paramIndex int) DependencyHandler { // registered if input parameters are more than matched dependencies. // It binds an input to a request body based on the request content-type header (JSON, XML, YAML, Query, Form). -func payloadBinding(index int, typ reflect.Type) *Binding { - return &Binding{ +func payloadBinding(index int, typ reflect.Type) *binding { + return &binding{ Dependency: &Dependency{ Handle: func(ctx context.Context, input *Input) (newValue reflect.Value, err error) { wasPtr := input.Type.Kind() == reflect.Ptr diff --git a/hero/binding_test.go b/hero/binding_test.go index a5164192..54d2b864 100644 --- a/hero/binding_test.go +++ b/hero/binding_test.go @@ -11,8 +11,14 @@ import ( "github.com/kataras/iris/v12/sessions" ) -func contextBinding(index int) *Binding { - return &Binding{ +var ( + stdContextTyp = reflect.TypeOf((*stdContext.Context)(nil)).Elem() + sessionTyp = reflect.TypeOf((*sessions.Session)(nil)) + timeTyp = reflect.TypeOf((*time.Time)(nil)).Elem() +) + +func contextBinding(index int) *binding { + return &binding{ Dependency: BuiltinDependencies[0], Input: &Input{Type: BuiltinDependencies[0].DestType, Index: index}, } @@ -64,55 +70,55 @@ func TestGetBindingsForFunc(t *testing.T) { var tests = []struct { Func interface{} - Expected []*Binding + Expected []*binding }{ { // 0 Func: func(ctx context.Context) { ctx.WriteString("t1") }, - Expected: []*Binding{contextBinding(0)}, + Expected: []*binding{contextBinding(0)}, }, { // 1 Func: func(ctx context.Context) error { return fmt.Errorf("err1") }, - Expected: []*Binding{contextBinding(0)}, + Expected: []*binding{contextBinding(0)}, }, { // 2 Func: func(ctx context.Context) testResponse { return testResponse{Name: "name"} }, - Expected: []*Binding{contextBinding(0)}, + Expected: []*binding{contextBinding(0)}, }, { // 3 Func: func(in testRequest) (testResponse, error) { return testResponse{Name: "email of " + in.Email}, nil }, - Expected: []*Binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}}, + Expected: []*binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}}, }, { // 4 Func: func(in testRequest) (testResponse, error) { return testResponse{Name: "not valid "}, fmt.Errorf("invalid") }, - Expected: []*Binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}}, + Expected: []*binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}}, }, { // 5 Func: func(ctx context.Context, in testRequest) testResponse { return testResponse{Name: "(with ctx) email of " + in.Email} }, - Expected: []*Binding{contextBinding(0), {Dependency: deps[2], Input: &Input{Index: 1, Type: testRequestTyp}}}, + Expected: []*binding{contextBinding(0), {Dependency: deps[2], Input: &Input{Index: 1, Type: testRequestTyp}}}, }, { // 6 Func: func(in testRequest, ctx context.Context) testResponse { // reversed. return testResponse{Name: "(with ctx) email of " + in.Email} }, - Expected: []*Binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}, contextBinding(1)}, + Expected: []*binding{{Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}}, contextBinding(1)}, }, { // 7 Func: func(in testRequest, ctx context.Context, in2 string) testResponse { // reversed. return testResponse{Name: "(with ctx) email of " + in.Email + "and in2: " + in2} }, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}, @@ -128,7 +134,7 @@ func TestGetBindingsForFunc(t *testing.T) { Func: func(in testRequest, ctx context.Context, in2, in3 string) testResponse { // reversed. return testResponse{Name: "(with ctx) email of " + in.Email + " | in2: " + in2 + " in3: " + in3} }, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: deps[2], Input: &Input{Index: 0, Type: testRequestTyp}, @@ -148,7 +154,7 @@ func TestGetBindingsForFunc(t *testing.T) { Func: func(ctx context.Context, in testRequest, in2 testRequest2) testResponse { return testResponse{Name: fmt.Sprintf("(with ctx) email of %s and in2.Age %d", in.Email, in2.Age)} }, - Expected: []*Binding{ + Expected: []*binding{ contextBinding(0), { Dependency: deps[2], @@ -170,7 +176,7 @@ func TestGetBindingsForFunc(t *testing.T) { Func: func(userID string, age int) testResponse { return testResponse{Name: "in from path parameters"} }, - Expected: []*Binding{ + Expected: []*binding{ paramBinding(0, 0, reflect.TypeOf("")), paramBinding(1, 1, reflect.TypeOf(0)), }, @@ -180,7 +186,7 @@ func TestGetBindingsForFunc(t *testing.T) { Func: func(ctx stdContext.Context, s *sessions.Session, t time.Time) testResponse { return testResponse{"from std context and session"} }, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: NewDependency(BuiltinDependencies[1]), Input: &Input{Index: 0, Type: stdContextTyp}, @@ -191,7 +197,7 @@ func TestGetBindingsForFunc(t *testing.T) { }, { Dependency: NewDependency(BuiltinDependencies[3]), - Input: &Input{Index: 2, Type: reflect.TypeOf(time.Time{})}, + Input: &Input{Index: 2, Type: timeTyp}, }, }, }, @@ -307,7 +313,7 @@ func TestBindingsForStruct(t *testing.T) { }), } - var autoBindings = []*Binding{ + var autoBindings = []*binding{ payloadBinding(0, reflect.TypeOf(embedded1{})), payloadBinding(1, reflect.TypeOf(embedded2{})), } @@ -319,12 +325,12 @@ func TestBindingsForStruct(t *testing.T) { var tests = []struct { Value interface{} Registered []*Dependency - Expected []*Binding + Expected []*binding }{ { // 0. Value: &controller{}, Registered: deps, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: deps[0], Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf("")}, @@ -338,7 +344,7 @@ func TestBindingsForStruct(t *testing.T) { // 1. test controller with pre-defined variables. { Value: &controller{Name: "name_struct", Service: new(serviceImpl)}, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: NewDependency("name_struct"), Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf("")}, @@ -354,7 +360,7 @@ func TestBindingsForStruct(t *testing.T) { { Value: &controller{Name: "name_struct", Service: new(serviceImpl)}, Registered: deps, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: NewDependency("name_struct"), Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf("")}, @@ -369,7 +375,7 @@ func TestBindingsForStruct(t *testing.T) { { Value: &controllerEmbeddingExported{}, Registered: depsForAnonymousEmbedded, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: depsForAnonymousEmbedded[0], Input: &Input{Index: 0, StructFieldIndex: []int{0, 0}, Type: reflect.TypeOf(0)}, @@ -384,7 +390,7 @@ func TestBindingsForStruct(t *testing.T) { { Value: &controllerEmbeddingUnexported{}, Registered: depsForAnonymousEmbedded, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: depsForAnonymousEmbedded[0], Input: &Input{Index: 0, StructFieldIndex: []int{0, 0}, Type: reflect.TypeOf(0)}, @@ -411,7 +417,7 @@ func TestBindingsForStruct(t *testing.T) { { Value: &controller2{}, Registered: depsForFieldsOfStruct, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: depsForFieldsOfStruct[0], Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf(embedded1{})}, @@ -426,7 +432,7 @@ func TestBindingsForStruct(t *testing.T) { { Value: &controller3{}, Registered: []*Dependency{depsForFieldsOfStruct[0]}, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: depsForFieldsOfStruct[0], Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf(embedded1{})}, @@ -437,7 +443,7 @@ func TestBindingsForStruct(t *testing.T) { { Value: &controller3{}, Registered: depsForFieldsOfStruct, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: depsForFieldsOfStruct[0], Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf(embedded1{})}, @@ -448,7 +454,7 @@ func TestBindingsForStruct(t *testing.T) { { Value: &controller{}, Registered: depsInterfaces, - Expected: []*Binding{ + Expected: []*binding{ { Dependency: depsInterfaces[0], Input: &Input{Index: 0, StructFieldIndex: []int{0}, Type: reflect.TypeOf("")}, diff --git a/hero/container.go b/hero/container.go index b38f8488..00f64ece 100644 --- a/hero/container.go +++ b/hero/container.go @@ -2,17 +2,12 @@ package hero import ( stdContext "context" - "fmt" "time" "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/sessions" ) -func fatalf(format string, args ...interface{}) { - panic(fmt.Sprintf(format, args...)) -} - // Default is the default container value which can be used for dependencies share. var Default = New() @@ -40,13 +35,15 @@ type Container struct { GetErrorHandler func(context.Context) ErrorHandler // cannot be nil. } +// BuiltinDependencies is a list of builtin dependencies that are added on Container's initilization. +// Contains the iris context, standard context, iris sessions and time dependencies. var BuiltinDependencies = []*Dependency{ // iris context dependency. - NewDependency(func(ctx context.Context) context.Context { return ctx }), + NewDependency(func(ctx context.Context) context.Context { return ctx }).Explicitly(), // standard context dependency. NewDependency(func(ctx context.Context) stdContext.Context { return ctx.Request().Context() - }), + }).Explicitly(), // iris session dependency. NewDependency(func(ctx context.Context) *sessions.Session { session := sessions.Get(ctx) @@ -55,11 +52,11 @@ var BuiltinDependencies = []*Dependency{ } return session - }), + }).Explicitly(), // time.Time to time.Now dependency. NewDependency(func(ctx context.Context) time.Time { return time.Now() - }), + }).Explicitly(), // payload and param bindings are dynamically allocated and declared at the end of the `binding` source file. } @@ -161,6 +158,9 @@ func (c *Container) Handler(fn interface{}) context.Handler { return makeHandler(fn, c) } +// Struct accepts a pointer to a struct value and returns a structure which +// contains bindings for the struct's fields and a method to +// extract a Handler from this struct's method. func (c *Container) Struct(ptrValue interface{}) *Struct { return makeStruct(ptrValue, c) } diff --git a/hero/dependency.go b/hero/dependency.go index 28c598eb..073c9718 100644 --- a/hero/dependency.go +++ b/hero/dependency.go @@ -9,8 +9,11 @@ import ( ) type ( + // DependencyHandler is the native function declaration which implementors should return a value match to an input. DependencyHandler func(ctx context.Context, input *Input) (reflect.Value, error) - + // Dependency describes the design-time dependency to be injected at serve time. + // Contains its source location, the dependency handler (provider) itself and information + // such as static for static struct values or explicit to bind a value to its exact DestType and not if just assignable to it (interfaces). Dependency struct { OriginalValue interface{} // Used for debugging and for logging only. Source Source diff --git a/hero/dependency_source.go b/hero/dependency_source.go index aa94202b..87f5aff9 100644 --- a/hero/dependency_source.go +++ b/hero/dependency_source.go @@ -9,6 +9,7 @@ import ( "strings" ) +// Source describes where a dependency is located at the source code itself. type Source struct { File string Line int diff --git a/hero/handler.go b/hero/handler.go index 3670893f..ef8d7973 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -8,12 +8,19 @@ import ( ) type ( + // ErrorHandler describes an interface to handle errors per hero handler and its dependencies. + // + // Handles non-nil errors return from a hero handler or a controller's method (see `getBindingsFor` and `Handler`) + // the error may return from a request-scoped dependency too (see `Handler`). ErrorHandler interface { HandleError(context.Context, error) } + // ErrorHandlerFunc implements the `ErrorHandler`. + // It describes the type defnition for an error function handler. ErrorHandlerFunc func(context.Context, error) ) +// HandleError fires when a non-nil error returns from a request-scoped dependency at serve-time or the handler itself. func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) { fn(ctx, err) } @@ -41,7 +48,7 @@ var ( ctx.StatusCode(DefaultErrStatusCode) } - ctx.WriteString(err.Error()) + _, _ = ctx.WriteString(err.Error()) } ctx.StopExecution() diff --git a/hero/reflect.go b/hero/reflect.go index 43e65d96..fdbd6a62 100644 --- a/hero/reflect.go +++ b/hero/reflect.go @@ -1,12 +1,9 @@ package hero import ( - stdContext "context" "reflect" - "time" "github.com/kataras/iris/v12/context" - "github.com/kataras/iris/v12/sessions" ) func valueOf(v interface{}) reflect.Value { @@ -45,7 +42,6 @@ func isFunc(kindable interface{ Kind() reflect.Kind }) bool { } var inputTyp = reflect.TypeOf((*Input)(nil)) -var timeTyp = reflect.TypeOf((*time.Time)(nil)).Elem() var errTyp = reflect.TypeOf((*error)(nil)).Elem() @@ -69,20 +65,6 @@ func isContext(typ reflect.Type) bool { return typ.Implements(contextTyp) } -var stdContextTyp = reflect.TypeOf((*stdContext.Context)(nil)).Elem() - -// isStdContext returns true if the "typ" is a type of standard Context. -func isStdContext(typ reflect.Type) bool { - return typ.Implements(stdContextTyp) -} - -var sessionTyp = reflect.TypeOf((*sessions.Session)(nil)) - -// isStdContext returns true if the "typ" is a type of standard Context. -func isSession(typ reflect.Type) bool { - return typ == sessionTyp -} - var errorHandlerTyp = reflect.TypeOf((*ErrorHandler)(nil)).Elem() func isErrorHandler(typ reflect.Type) bool { diff --git a/hero/struct.go b/hero/struct.go index c4871678..ce17ebcb 100644 --- a/hero/struct.go +++ b/hero/struct.go @@ -31,11 +31,13 @@ var sortByNumMethods Sorter = func(t1 reflect.Type, t2 reflect.Type) bool { return true } +// Struct keeps a record of a particular struct value injection. +// See `Container.Struct` and `mvc#Application.Handle` methods. type Struct struct { ptrType reflect.Type ptrValue reflect.Value // the original ptr struct value. elementType reflect.Type // the original struct type. - bindings []*Binding // struct field bindings. + bindings []*binding // struct field bindings. Container *Container Singleton bool @@ -102,6 +104,10 @@ func makeStruct(structPtr interface{}, c *Container) *Struct { return s } +// Acquire returns a struct value based on the request. +// If the dependencies are all static then these are already set-ed at the initialization of this Struct +// and the same struct value instance will be returned, ignoring the Context. Otherwise +// a new struct value with filled fields by its pre-calculated bindings will be returned instead. func (s *Struct) Acquire(ctx context.Context) (reflect.Value, error) { if s.Singleton { ctx.Values().Set(context.ControllerContextKey, s.ptrValue) @@ -130,6 +136,8 @@ func (s *Struct) Acquire(ctx context.Context) (reflect.Value, error) { return ctrl, nil } +// MethodHandler accepts a "methodName" that should be a valid an exported +// method of the struct and returns its converted Handler. func (s *Struct) MethodHandler(methodName string) context.Handler { m, ok := s.ptrValue.Type().MethodByName(methodName) if !ok { diff --git a/mvc/reflect.go b/mvc/reflect.go index dfd2902f..130f7ca4 100644 --- a/mvc/reflect.go +++ b/mvc/reflect.go @@ -2,15 +2,9 @@ package mvc import ( "reflect" - - "github.com/kataras/iris/v12/hero" ) -var ( - baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem() - errorHandlerTyp = reflect.TypeOf((*hero.ErrorHandler)(nil)).Elem() - errorTyp = reflect.TypeOf((*error)(nil)).Elem() -) +var baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem() func isBaseController(ctrlTyp reflect.Type) bool { return ctrlTyp.Implements(baseControllerTyp) @@ -26,33 +20,3 @@ func indirectType(typ reflect.Type) reflect.Type { } return typ } - -func isErrorHandler(ctrlTyp reflect.Type) bool { - return ctrlTyp.Implements(errorHandlerTyp) -} - -func hasErrorOutArgs(fn reflect.Method) bool { - n := fn.Type.NumOut() - if n == 0 { - return false - } - - for i := 0; i < n; i++ { - if out := fn.Type.Out(i); out.Kind() == reflect.Interface { - if out.Implements(errorTyp) { - return true - } - } - } - - return false -} - -func getInputArgsFromFunc(funcTyp reflect.Type) []reflect.Type { - n := funcTyp.NumIn() - funcIn := make([]reflect.Type, n) - for i := 0; i < n; i++ { - funcIn[i] = funcTyp.In(i) - } - return funcIn -}