fix https://github.com/kataras/iris/issues/1450 and continue on implementing 1449

Former-commit-id: 617f64d061e88f050a443ea1751fa244164656c5
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-02-14 23:34:56 +02:00
parent 09a410c6cb
commit c13fd84354
24 changed files with 581 additions and 144 deletions

View File

@ -1,11 +1,9 @@
// Code generated by go-bindata. // Code generated for package main by go-bindata DO NOT EDIT. (@generated)
// sources: // sources:
// templates/layouts/layout.html // templates/layouts/layout.html
// templates/layouts/mylayout.html // templates/layouts/mylayout.html
// templates/page1.html // templates/page1.html
// templates/partials/page1_partial1.html // templates/partials/page1_partial1.html
// DO NOT EDIT!
package main package main
import ( import (
@ -52,21 +50,32 @@ type bindataFileInfo struct {
modTime time.Time modTime time.Time
} }
// Name return file name
func (fi bindataFileInfo) Name() string { func (fi bindataFileInfo) Name() string {
return fi.name return fi.name
} }
// Size return file size
func (fi bindataFileInfo) Size() int64 { func (fi bindataFileInfo) Size() int64 {
return fi.size return fi.size
} }
// Mode return file mode
func (fi bindataFileInfo) Mode() os.FileMode { func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode return fi.mode
} }
// Mode return file modify time
func (fi bindataFileInfo) ModTime() time.Time { func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime return fi.modTime
} }
// IsDir return file whether a directory
func (fi bindataFileInfo) IsDir() bool { func (fi bindataFileInfo) IsDir() bool {
return false return fi.mode&os.ModeDir != 0
} }
// Sys return file is sys mode
func (fi bindataFileInfo) Sys() interface{} { func (fi bindataFileInfo) Sys() interface{} {
return nil return nil
} }
@ -86,7 +95,7 @@ func templatesLayoutsLayoutHtml() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "templates/layouts/layout.html", size: 180, mode: os.FileMode(438), modTime: time.Unix(1499700235, 0)} info := bindataFileInfo{name: "templates/layouts/layout.html", size: 180, mode: os.FileMode(438), modTime: time.Unix(1565946441, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -106,7 +115,7 @@ func templatesLayoutsMylayoutHtml() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "templates/layouts/mylayout.html", size: 215, mode: os.FileMode(438), modTime: time.Unix(1499700235, 0)} info := bindataFileInfo{name: "templates/layouts/mylayout.html", size: 215, mode: os.FileMode(438), modTime: time.Unix(1565946441, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -126,7 +135,7 @@ func templatesPage1Html() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "templates/page1.html", size: 157, mode: os.FileMode(438), modTime: time.Unix(1499700235, 0)} info := bindataFileInfo{name: "templates/page1.html", size: 157, mode: os.FileMode(438), modTime: time.Unix(1565946441, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }
@ -146,7 +155,7 @@ func templatesPartialsPage1_partial1Html() (*asset, error) {
return nil, err return nil, err
} }
info := bindataFileInfo{name: "templates/partials/page1_partial1.html", size: 89, mode: os.FileMode(438), modTime: time.Unix(1499700235, 0)} info := bindataFileInfo{name: "templates/partials/page1_partial1.html", size: 89, mode: os.FileMode(438), modTime: time.Unix(1565946441, 0)}
a := &asset{bytes: bytes, info: info} a := &asset{bytes: bytes, info: info}
return a, nil return a, nil
} }

View File

@ -11,6 +11,9 @@ import (
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
) )
// $ go get -u github.com/go-bindata/go-bindata/...
// $ go-bindata ./views/...
// $ go build
func main() { func main() {
app := iris.New() app := iris.New()
tmpl := iris.Jet("./views", ".jet").Binary(Asset, AssetNames) tmpl := iris.Jet("./views", ".jet").Binary(Asset, AssetNames)

View File

@ -1,7 +1,7 @@
doctype html doctype html
html html
include templates/header.pug include header.pug
body body
h1 My Site h1 My Site
p {{ bold "Welcome to my super lame site."}} p {{ bold "Welcome to my super lame site."}}
include templates/footer.pug include footer.pug

View File

@ -0,0 +1,269 @@
// Code generated for package main by go-bindata DO NOT EDIT. (@generated)
// sources:
// templates/index.pug
// templates/layout.pug
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
// Name return file name
func (fi bindataFileInfo) Name() string {
return fi.name
}
// Size return file size
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
// Mode return file mode
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
// Mode return file modify time
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
// IsDir return file whether a directory
func (fi bindataFileInfo) IsDir() bool {
return fi.mode&os.ModeDir != 0
}
// Sys return file is sys mode
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _templatesIndexPug = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xad\x28\x49\xcd\x4b\x29\x56\xc8\x49\xac\xcc\x2f\x2d\xd1\x2b\x28\x4d\xe7\xe5\xe2\xe5\x4a\xca\xc9\x4f\xce\x56\x28\xc9\x2c\xc9\x49\xe5\xe5\x52\x80\x30\x14\x1c\x8b\x4a\x32\x93\x73\x52\x15\x42\x20\xc2\x30\x55\xc9\xf9\x79\x25\xa9\x79\x25\x20\x75\x19\x86\x0a\xbe\x95\x30\x75\x80\x00\x00\x00\xff\xff\xa6\xfd\x18\x8c\x5a\x00\x00\x00")
func templatesIndexPugBytes() ([]byte, error) {
return bindataRead(
_templatesIndexPug,
"templates/index.pug",
)
}
func templatesIndexPug() (*asset, error) {
bytes, err := templatesIndexPugBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/index.pug", size: 90, mode: os.FileMode(438), modTime: time.Unix(1581711973, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
var _templatesLayoutPug = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x4a\xc9\x4f\x2e\xa9\x2c\x48\x55\xc8\x28\xc9\xcd\xe1\xe5\x82\x90\x0a\x0a\x19\xa9\x89\x29\x20\x5a\x41\x21\x29\x27\x3f\x39\x5b\xa1\x24\xb3\x24\x27\x15\x22\xa0\x00\xe1\x28\xb8\xa4\xa6\x25\x96\xe6\x94\x20\xa4\x92\xf2\x53\x2a\x91\xf5\x24\xe7\xe7\x95\xa4\xe6\x95\x00\x02\x00\x00\xff\xff\x5f\xa5\x93\xf9\x61\x00\x00\x00")
func templatesLayoutPugBytes() ([]byte, error) {
return bindataRead(
_templatesLayoutPug,
"templates/layout.pug",
)
}
func templatesLayoutPug() (*asset, error) {
bytes, err := templatesLayoutPugBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/layout.pug", size: 97, mode: os.FileMode(438), modTime: time.Unix(1565946441, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"templates/index.pug": templatesIndexPug,
"templates/layout.pug": templatesLayoutPug,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"templates": {nil, map[string]*bintree{
"index.pug": {templatesIndexPug, map[string]*bintree{}},
"layout.pug": {templatesLayoutPug, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@ -2,10 +2,13 @@ package main
import "github.com/kataras/iris/v12" import "github.com/kataras/iris/v12"
// $ go get -u github.com/go-bindata/go-bindata/...
// $ go-bindata ./templates/...
// $ go build
func main() { func main() {
app := iris.New() app := iris.New()
tmpl := iris.Pug("./templates", ".pug") tmpl := iris.Pug("./templates", ".pug").Binary(Asset, AssetNames)
app.RegisterView(tmpl) app.RegisterView(tmpl)

View File

@ -1,4 +1,4 @@
extends templates/layout.pug extends layout.pug
block title block title
title Article Title title Article Title

View File

@ -16,6 +16,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -994,6 +995,10 @@ type Context interface {
// It will search from the current subdomain of context's host, if not inside the root domain. // It will search from the current subdomain of context's host, if not inside the root domain.
RouteExists(method, path string) bool RouteExists(method, path string) bool
// ReflectValue caches and returns a []reflect.Value{reflect.ValueOf(ctx)}.
// It's just a helper to maintain variable inside the context itself.
ReflectValue() []reflect.Value
// Application returns the iris app instance which belongs to this context. // Application returns the iris app instance which belongs to this context.
// Worth to notice that this function returns an interface // Worth to notice that this function returns an interface
// of the Application, which contains methods that are safe // of the Application, which contains methods that are safe
@ -4608,6 +4613,22 @@ func (ctx *context) RouteExists(method, path string) bool {
return ctx.Application().RouteExists(ctx, method, path) return ctx.Application().RouteExists(ctx, method, path)
} }
const (
reflectValueContextKey = "_iris_context_reflect_value"
)
// ReflectValue caches and returns a []reflect.Value{reflect.ValueOf(ctx)}.
// It's just a helper to maintain variable inside the context itself.
func (ctx *context) ReflectValue() []reflect.Value {
if v := ctx.Values().Get(reflectValueContextKey); v != nil {
return v.([]reflect.Value)
}
v := []reflect.Value{reflect.ValueOf(ctx)}
ctx.Values().Set(reflectValueContextKey, v)
return v
}
// Application returns the iris app instance which belongs to this context. // Application returns the iris app instance which belongs to this context.
// Worth to notice that this function returns an interface // Worth to notice that this function returns an interface
// of the Application, which contains methods that are safe // of the Application, which contains methods that are safe

2
go.mod
View File

@ -5,7 +5,6 @@ go 1.13
require ( require (
github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml v0.3.1
github.com/CloudyKit/jet/v3 v3.0.0 github.com/CloudyKit/jet/v3 v3.0.0
github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398 github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible
github.com/dgraph-io/badger v1.6.0 github.com/dgraph-io/badger v1.6.0
@ -19,6 +18,7 @@ require (
github.com/iris-contrib/go.uuid v2.0.0+incompatible github.com/iris-contrib/go.uuid v2.0.0+incompatible
github.com/iris-contrib/pongo2 v0.0.1 github.com/iris-contrib/pongo2 v0.0.1
github.com/iris-contrib/schema v0.0.1 github.com/iris-contrib/schema v0.0.1
github.com/iris-contrib/jade v1.1.1
github.com/json-iterator/go v1.1.9 github.com/json-iterator/go v1.1.9
github.com/kataras/golog v0.0.10 github.com/kataras/golog v0.0.10
github.com/kataras/neffos v0.0.14 github.com/kataras/neffos v0.0.14

View File

@ -3,22 +3,12 @@ package hero
import ( import (
"reflect" "reflect"
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/hero/di" "github.com/kataras/iris/v12/hero/di"
) )
func init() { func init() {
di.DefaultHijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) { di.DefaultHijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) {
// if IsExpectingStore(fieldOrFuncInput) {
// return &di.BindObject{
// Type: memstoreTyp,
// BindType: di.Dynamic,
// ReturnValue: func(ctxValue []reflect.Value) reflect.Value {
// // return ctxValue[0].MethodByName("Params").Call(di.EmptyIn)[0]
// return ctxValue[0].MethodByName("Params").Call(di.EmptyIn)[0].Field(0) // the Params' memstore.Store.
// },
// }, true
// }
if !IsContext(fieldOrFuncInput) { if !IsContext(fieldOrFuncInput) {
return nil, false return nil, false
} }
@ -29,8 +19,8 @@ func init() {
return &di.BindObject{ return &di.BindObject{
Type: contextTyp, Type: contextTyp,
BindType: di.Dynamic, BindType: di.Dynamic,
ReturnValue: func(ctxValue []reflect.Value) reflect.Value { ReturnValue: func(ctx context.Context) reflect.Value {
return ctxValue[0] return ctx.ReflectValue()[0]
}, },
}, true }, true
} }
@ -40,4 +30,14 @@ func init() {
// or first argument is context.Context and second argument is a variadic, which is ignored (i.e new sessions#Start). // or first argument is context.Context and second argument is a variadic, which is ignored (i.e new sessions#Start).
return (fn.NumIn() == 1 || (fn.NumIn() == 2 && fn.IsVariadic())) && IsContext(fn.In(0)) return (fn.NumIn() == 1 || (fn.NumIn() == 2 && fn.IsVariadic())) && IsContext(fn.In(0))
} }
di.DefaultErrorHandler = di.ErrorHandlerFunc(func(ctx context.Context, err error) {
if err == nil {
return
}
ctx.StatusCode(400)
ctx.WriteString(err.Error())
ctx.StopExecution()
})
} }

View File

@ -1,8 +1,12 @@
// Package di provides dependency injection for the Iris Hero and Iris MVC new features. // Package di provides dependency injection for the Iris Hero and Iris MVC new features.
// It's used internally by "hero" and "mvc" packages directly. // It's used internally by "hero" and "mvc" packages.
package di package di
import "reflect" import (
"reflect"
"github.com/kataras/iris/v12/context"
)
type ( type (
// Hijacker is a type which is used to catch fields or function's input argument // Hijacker is a type which is used to catch fields or function's input argument
@ -11,13 +15,33 @@ type (
// TypeChecker checks if a specific field's or function input argument's // TypeChecker checks if a specific field's or function input argument's
// is valid to be binded. // is valid to be binded.
TypeChecker func(reflect.Type) bool TypeChecker func(reflect.Type) bool
// ErrorHandler is the optional interface to handle errors per hero func,
// see `mvc/Application#HandleError` for MVC application-level error handler registration too.
//
// Handles non-nil errors return from a hero handler or a controller's method (see `DispatchFuncResult`)
// and (from v12.1.8) the error may return from a request-scoped dynamic dependency (see `MakeReturnValue`).
ErrorHandler interface {
HandleError(ctx context.Context, err error)
}
// ErrorHandlerFunc implements the `ErrorHandler`.
// It describes the type defnition for an error handler.
ErrorHandlerFunc func(ctx context.Context, err error)
) )
// HandleError fires when the `DispatchFuncResult` or `MakereturnValue` return a non-nil error.
func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) {
fn(ctx, err)
}
var ( var (
// DefaultHijacker is the hijacker used on the package-level Struct & Func functions. // DefaultHijacker is the hijacker used on the package-level Struct & Func functions.
DefaultHijacker Hijacker DefaultHijacker Hijacker
// DefaultTypeChecker is the typechecker used on the package-level Struct & Func functions. // DefaultTypeChecker is the typechecker used on the package-level Struct & Func functions.
DefaultTypeChecker TypeChecker DefaultTypeChecker TypeChecker
// DefaultErrorHandler is the error handler used on the package-level `Func` function
// to catch any errors from dependencies or handlers.
DefaultErrorHandler ErrorHandler
) )
// Struct is being used to return a new injector based on // Struct is being used to return a new injector based on
@ -26,7 +50,7 @@ var (
// with the injector's `Inject` and `InjectElem` methods. // with the injector's `Inject` and `InjectElem` methods.
func Struct(s interface{}, values ...reflect.Value) *StructInjector { func Struct(s interface{}, values ...reflect.Value) *StructInjector {
if s == nil { if s == nil {
return &StructInjector{Has: false} return &StructInjector{}
} }
return MakeStructInjector( return MakeStructInjector(
@ -45,13 +69,14 @@ func Struct(s interface{}, values ...reflect.Value) *StructInjector {
// with the injector's `Inject` method. // with the injector's `Inject` method.
func Func(fn interface{}, values ...reflect.Value) *FuncInjector { func Func(fn interface{}, values ...reflect.Value) *FuncInjector {
if fn == nil { if fn == nil {
return &FuncInjector{Has: false} return &FuncInjector{}
} }
return MakeFuncInjector( return MakeFuncInjector(
ValueOf(fn), ValueOf(fn),
DefaultHijacker, DefaultHijacker,
DefaultTypeChecker, DefaultTypeChecker,
DefaultErrorHandler,
values..., values...,
) )
} }
@ -63,9 +88,10 @@ func Func(fn interface{}, values ...reflect.Value) *FuncInjector {
type D struct { type D struct {
Values Values
hijacker Hijacker hijacker Hijacker
goodFunc TypeChecker goodFunc TypeChecker
sorter Sorter errorHandler ErrorHandler
sorter Sorter
} }
// New creates and returns a new Dependency Injection container. // New creates and returns a new Dependency Injection container.
@ -87,6 +113,13 @@ func (d *D) GoodFunc(fn TypeChecker) *D {
return d return d
} }
// ErrorHandler adds a handler which will be fired when a handler's second output argument is error and it's not nil
// or when a request-scoped dynamic function dependency's second output argument is error and it's not nil.
func (d *D) ErrorHandler(errorHandler ErrorHandler) *D {
d.errorHandler = errorHandler
return d
}
// Sort sets the fields and valid bindable values sorter for struct injection. // Sort sets the fields and valid bindable values sorter for struct injection.
func (d *D) Sort(with Sorter) *D { func (d *D) Sort(with Sorter) *D {
d.sorter = with d.sorter = with
@ -97,10 +130,11 @@ func (d *D) Sort(with Sorter) *D {
// parent's (current "D") hijacker, good func type checker, sorter and all dependencies values. // parent's (current "D") hijacker, good func type checker, sorter and all dependencies values.
func (d *D) Clone() *D { func (d *D) Clone() *D {
return &D{ return &D{
Values: d.Values.Clone(), Values: d.Values.Clone(),
hijacker: d.hijacker, hijacker: d.hijacker,
goodFunc: d.goodFunc, goodFunc: d.goodFunc,
sorter: d.sorter, errorHandler: d.errorHandler,
sorter: d.sorter,
} }
} }
@ -136,6 +170,7 @@ func (d *D) Func(fn interface{}) *FuncInjector {
ValueOf(fn), ValueOf(fn),
d.hijacker, d.hijacker,
d.goodFunc, d.goodFunc,
d.errorHandler,
d.Values..., d.Values...,
) ).ErrorHandler(d.errorHandler)
} }

View File

@ -3,6 +3,8 @@ package di
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/kataras/iris/v12/context"
) )
type ( type (
@ -16,9 +18,10 @@ type (
FuncInjector struct { FuncInjector struct {
// the original function, is being used // the original function, is being used
// only the .Call, which is referring to the same function, always. // only the .Call, which is referring to the same function, always.
fn reflect.Value fn reflect.Value
typ reflect.Type typ reflect.Type
goodFunc TypeChecker goodFunc TypeChecker
errorHandler ErrorHandler
inputs []*targetFuncInput inputs []*targetFuncInput
// Length is the number of the valid, final binded input arguments. // Length is the number of the valid, final binded input arguments.
@ -32,13 +35,15 @@ type (
) )
type missingInput struct { type missingInput struct {
index int // the function's input argument's index. index int // the function's input argument's index.
found bool found bool
remaining Values
} }
func (s *FuncInjector) miss(index int) { func (s *FuncInjector) miss(index int, remaining Values) {
s.lost = append(s.lost, &missingInput{ s.lost = append(s.lost, &missingInput{
index: index, index: index,
remaining: remaining,
}) })
} }
@ -46,12 +51,13 @@ func (s *FuncInjector) miss(index int) {
// that the caller should use to bind input arguments of the "fn" function. // that the caller should use to bind input arguments of the "fn" function.
// //
// The hijack and the goodFunc are optional, the "values" is the dependencies collection. // The hijack and the goodFunc are optional, the "values" is the dependencies collection.
func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, values ...reflect.Value) *FuncInjector { func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, errorHandler ErrorHandler, values ...reflect.Value) *FuncInjector {
typ := IndirectType(fn.Type()) typ := IndirectType(fn.Type())
s := &FuncInjector{ s := &FuncInjector{
fn: fn, fn: fn,
typ: typ, typ: typ,
goodFunc: goodFunc, goodFunc: goodFunc,
errorHandler: errorHandler,
} }
if !IsFunc(typ) { if !IsFunc(typ) {
@ -100,7 +106,7 @@ func MakeFuncInjector(fn reflect.Value, hijack Hijacker, goodFunc TypeChecker, v
// but before this let's make a list of failed // but before this let's make a list of failed
// inputs, so they can be used for a re-try // inputs, so they can be used for a re-try
// with different set of binding "values". // with different set of binding "values".
s.miss(i) s.miss(i, values) // send the remaining dependencies values.
} }
} }
@ -123,11 +129,28 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool {
inTyp := s.typ.In(inputIndex) inTyp := s.typ.In(inputIndex)
// the binded values to the func's inputs. // the binded values to the func's inputs.
b, err := MakeBindObject(value, s.goodFunc) b, err := MakeBindObject(value, s.goodFunc, s.errorHandler)
if err != nil { if err != nil {
return false return false
} }
// TODO: expose that (need to push a fix for issue #1450 first)
if b.Type == reflectValueType {
b.Type = inTyp
// returnValue := b.ReturnValue
b.ReturnValue = func(ctx context.Context) reflect.Value {
newValue := reflect.New(inTyp)
if err := ctx.ReadJSON(newValue.Interface()); err != nil {
if s.errorHandler != nil {
s.errorHandler.HandleError(ctx, err)
}
}
return newValue.Elem()
}
}
if b.IsAssignable(inTyp) { if b.IsAssignable(inTyp) {
// fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n", // fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n",
// i, b.Type.String(), inTyp.String(), inTyp.Pointer()) // i, b.Type.String(), inTyp.String(), inTyp.Pointer())
@ -141,9 +164,15 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool {
return false return false
} }
// ErrorHandler registers an error handler for this FuncInjector.
func (s *FuncInjector) ErrorHandler(errorHandler ErrorHandler) *FuncInjector {
s.errorHandler = errorHandler
return s
}
// Retry used to add missing dependencies, i.e path parameter builtin bindings if not already exists // Retry used to add missing dependencies, i.e path parameter builtin bindings if not already exists
// in the `hero.Handler`, once, only for that func injector. // in the `hero.Handler`, once, only for that func injector.
func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type) (reflect.Value, bool)) bool { func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type, remainingValues Values) (reflect.Value, bool)) bool {
for _, missing := range s.lost { for _, missing := range s.lost {
if missing.found { if missing.found {
continue continue
@ -152,7 +181,7 @@ func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type) (refl
invalidIndex := missing.index invalidIndex := missing.index
inTyp := s.typ.In(invalidIndex) inTyp := s.typ.In(invalidIndex)
v, ok := retryFn(invalidIndex, inTyp) v, ok := retryFn(invalidIndex, inTyp, missing.remaining)
if !ok { if !ok {
continue continue
} }
@ -186,12 +215,13 @@ func (s *FuncInjector) String() (trace string) {
// Inject accepts an already created slice of input arguments // Inject accepts an already created slice of input arguments
// and fills them, the "ctx" is optional and it's used // and fills them, the "ctx" is optional and it's used
// on the dependencies that depends on one or more input arguments, these are the "ctx". // on the dependencies that depends on one or more input arguments, these are the "ctx".
func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) { func (s *FuncInjector) Inject(ctx context.Context, in *[]reflect.Value) {
args := *in args := *in
for _, input := range s.inputs { for _, input := range s.inputs {
input.Object.Assign(ctx, func(v reflect.Value) { input.Object.Assign(ctx, func(v reflect.Value) {
// fmt.Printf("assign input index: %d for value: %v of type: %s\n", // fmt.Printf("assign input index: %d for value: %v of type: %s\n",
// input.InputIndex, v.String(), v.Type().Name()) // input.InputIndex, v.String(), v.Type().Name())
args[input.InputIndex] = v args[input.InputIndex] = v
}) })
} }
@ -205,8 +235,8 @@ func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) {
// If the function needs a receiver, so // If the function needs a receiver, so
// the caller should be able to in[0] = receiver before injection, // the caller should be able to in[0] = receiver before injection,
// then the `Inject` method should be used instead. // then the `Inject` method should be used instead.
func (s *FuncInjector) Call(ctx ...reflect.Value) []reflect.Value { func (s *FuncInjector) Call(ctx context.Context) []reflect.Value {
in := make([]reflect.Value, s.Length) in := make([]reflect.Value, s.Length)
s.Inject(&in, ctx...) s.Inject(ctx, &in)
return s.fn.Call(in) return s.fn.Call(in)
} }

View File

@ -3,6 +3,8 @@ package di
import ( import (
"errors" "errors"
"reflect" "reflect"
"github.com/kataras/iris/v12/context"
) )
// BindType is the type of a binded object/value, it's being used to // BindType is the type of a binded object/value, it's being used to
@ -35,7 +37,7 @@ type BindObject struct {
Value reflect.Value Value reflect.Value
BindType BindType BindType BindType
ReturnValue func([]reflect.Value) reflect.Value ReturnValue func(ctx context.Context) reflect.Value
} }
// MakeBindObject accepts any "v" value, struct, pointer or a function // MakeBindObject accepts any "v" value, struct, pointer or a function
@ -43,10 +45,10 @@ type BindObject struct {
// or the input arguments (if "v.elem()" is func) // or the input arguments (if "v.elem()" is func)
// are valid to be included as the final object's dependencies, even if the caller added more // are valid to be included as the final object's dependencies, even if the caller added more
// the "di" is smart enough to select what each "v" needs and what not before serve time. // the "di" is smart enough to select what each "v" needs and what not before serve time.
func MakeBindObject(v reflect.Value, goodFunc TypeChecker) (b BindObject, err error) { func MakeBindObject(v reflect.Value, goodFunc TypeChecker, errorHandler ErrorHandler) (b BindObject, err error) {
if IsFunc(v) { if IsFunc(v) {
b.BindType = Dynamic b.BindType = Dynamic
b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc) b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc, errorHandler)
} else { } else {
b.BindType = Static b.BindType = Static
b.Type = v.Type() b.Type = v.Type()
@ -67,9 +69,9 @@ var errBad = errors.New("bad")
// The "fn" can have the following form: // The "fn" can have the following form:
// `func(myService) MyViewModel`. // `func(myService) MyViewModel`.
// //
// The return type of the "fn" should be a value instance, not a pointer, for your own protection. // The return type of the "fn" should be a value instance, not a pointer.
// The binder function should return only one value. // The binder function should return just one value.
func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Value) reflect.Value, reflect.Type, error) { func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker, errorHandler ErrorHandler) (func(ctx context.Context) reflect.Value, reflect.Type, error) {
typ := IndirectType(fn.Type()) typ := IndirectType(fn.Type())
// invalid if not a func. // invalid if not a func.
@ -93,32 +95,30 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Val
firstOutTyp := typ.Out(0) firstOutTyp := typ.Out(0)
firstZeroOutVal := reflect.New(firstOutTyp).Elem() firstZeroOutVal := reflect.New(firstOutTyp).Elem()
bf := func(ctxValue []reflect.Value) reflect.Value { bf := func(ctx context.Context) reflect.Value {
results := fn.Call(ctxValue) results := fn.Call(ctx.ReflectValue())
if n == 2 {
// two, second is always error.
errVal := results[1]
if !errVal.IsNil() {
if errorHandler != nil {
errorHandler.HandleError(ctx, errVal.Interface().(error))
}
return firstZeroOutVal
}
}
v := results[0] v := results[0]
if !v.IsValid() { // check the first value, second is error. if !v.IsValid() { // check the first value, second is error.
return firstZeroOutVal return firstZeroOutVal
} }
if n == 2 { // if firstOutTyp == reflectValueType {
// two, second is always error. // converted := v.Convert(typ.In(0))
errVal := results[1] // fmt.Printf("object.go#124: converted: %#+v\n", converted)
if !errVal.IsNil() { // return converted //reflect.ValueOf(v.Interface())
// error is not nil, do something with it. // }
if ctx, ok := ctxValue[0].Interface().(interface {
StatusCode(int)
WriteString(string) (int, error)
StopExecution()
}); ok {
ctx.StatusCode(400)
ctx.WriteString(errVal.Interface().(error).Error())
ctx.StopExecution()
}
return firstZeroOutVal
}
}
// if v.String() == "<interface {} Value>" { // if v.String() == "<interface {} Value>" {
// println("di/object.go: " + v.String()) // println("di/object.go: " + v.String())
@ -138,7 +138,7 @@ func (b *BindObject) IsAssignable(to reflect.Type) bool {
// Assign sets the values to a setter, "toSetter" contains the setter, so the caller // Assign sets the values to a setter, "toSetter" contains the setter, so the caller
// can use it for multiple and different structs/functions as well. // can use it for multiple and different structs/functions as well.
func (b *BindObject) Assign(ctx []reflect.Value, toSetter func(reflect.Value)) { func (b *BindObject) Assign(ctx context.Context, toSetter func(reflect.Value)) {
if b.BindType == Dynamic { if b.BindType == Dynamic {
toSetter(b.ReturnValue(ctx)) toSetter(b.ReturnValue(ctx))
return return

View File

@ -138,10 +138,14 @@ func IsFunc(kindable interface {
return kindable.Kind() == reflect.Func return kindable.Kind() == reflect.Func
} }
var reflectValueType = reflect.TypeOf(reflect.Value{})
func equalTypes(got reflect.Type, expected reflect.Type) bool { func equalTypes(got reflect.Type, expected reflect.Type) bool {
if got == expected { if got == expected {
return true return true
} }
// fmt.Printf("got: %s expected: %s\n", got.String(), expected.String())
// if accepts an interface, check if the given "got" type does // if accepts an interface, check if the given "got" type does
// implement this "expected" user handler's input argument. // implement this "expected" user handler's input argument.
if expected.Kind() == reflect.Interface { if expected.Kind() == reflect.Interface {
@ -151,10 +155,6 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool {
return got.AssignableTo(expected) return got.AssignableTo(expected)
} }
// if got.String() == "interface {}" {
// return true
// }
return false return false
} }

View File

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"sort" "sort"
"github.com/kataras/iris/v12/context"
) )
// Scope is the struct injector's struct value scope/permant state. // Scope is the struct injector's struct value scope/permant state.
@ -155,7 +157,7 @@ func MakeStructInjector(v reflect.Value, hijack Hijacker, goodFunc TypeChecker,
} }
// the binded values to the struct's fields. // the binded values to the struct's fields.
b, err := MakeBindObject(val, goodFunc) b, err := MakeBindObject(val, goodFunc, nil)
if err != nil { if err != nil {
return s // if error stop here. return s // if error stop here.
} }
@ -288,17 +290,17 @@ func (s *StructInjector) String() (trace string) {
// Inject accepts a destination struct and any optional context value(s), // Inject accepts a destination struct and any optional context value(s),
// hero and mvc takes only one context value and this is the `context.Context`. // hero and mvc takes only one context value and this is the `context.Context`.
// It applies the bindings to the "dest" struct. It calls the InjectElem. // It applies the bindings to the "dest" struct. It calls the InjectElem.
func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) { func (s *StructInjector) Inject(ctx context.Context, dest interface{}) {
if dest == nil { if dest == nil {
return return
} }
v := IndirectValue(ValueOf(dest)) v := IndirectValue(ValueOf(dest))
s.InjectElem(v, ctx...) s.InjectElem(ctx, v)
} }
// InjectElem same as `Inject` but accepts a reflect.Value and bind the necessary fields directly. // InjectElem same as `Inject` but accepts a reflect.Value and bind the necessary fields directly.
func (s *StructInjector) InjectElem(destElem reflect.Value, ctx ...reflect.Value) { func (s *StructInjector) InjectElem(ctx context.Context, destElem reflect.Value) {
for _, f := range s.fields { for _, f := range s.fields {
f.Object.Assign(ctx, func(v reflect.Value) { f.Object.Assign(ctx, func(v reflect.Value) {
ff := destElem.FieldByIndex(f.FieldIndex) ff := destElem.FieldByIndex(f.FieldIndex)

View File

@ -20,23 +20,6 @@ type Result interface {
Dispatch(ctx context.Context) Dispatch(ctx context.Context)
} }
type (
// ErrorHandler is the optional interface to handle errors per hero func,
// see `mvc/Application#HandleError` for MVC application-level error handler registration too.
ErrorHandler interface {
HandleError(ctx context.Context, err error)
}
// ErrorHandlerFunc implements the `ErrorHandler`.
// It describes the type defnition for an error handler.
ErrorHandlerFunc func(ctx context.Context, err error)
)
// HandleError fires when the `DispatchFuncResult` returns a non-nil error.
func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) {
fn(ctx, err)
}
var defaultFailureResponse = Response{Code: DefaultErrStatusCode} var defaultFailureResponse = Response{Code: DefaultErrStatusCode}
// Try will check if "fn" ran without any panics, // Try will check if "fn" ran without any panics,
@ -180,7 +163,7 @@ func DispatchCommon(ctx context.Context,
// Result or (Result, error) and so on... // Result or (Result, error) and so on...
// //
// where Get is an HTTP METHOD. // where Get is an HTTP METHOD.
func DispatchFuncResult(ctx context.Context, errorHandler ErrorHandler, values []reflect.Value) { func DispatchFuncResult(ctx context.Context, errorHandler di.ErrorHandler, values []reflect.Value) {
if len(values) == 0 { if len(values) == 0 {
return return
} }
@ -314,7 +297,7 @@ func DispatchFuncResult(ctx context.Context, errorHandler ErrorHandler, values [
if errorHandler != nil { if errorHandler != nil {
errorHandler.HandleError(ctx, value) errorHandler.HandleError(ctx, value)
break return
} }
err = value err = value

View File

@ -18,6 +18,14 @@ func IsContext(inTyp reflect.Type) bool {
return inTyp.Implements(contextTyp) return inTyp.Implements(contextTyp)
} }
// var genericFuncTyp = reflect.TypeOf(func(context.Context) interface{} { return nil })
var genericFuncTyp = reflect.TypeOf(func(context.Context) reflect.Value { return reflect.Value{} })
// IsGenericFunc reports whether the "inTyp" is a type of func(Context) interface{}.
func IsGenericFunc(inTyp reflect.Type) bool {
return inTyp == genericFuncTyp
}
// checks if "handler" is context.Handler: func(context.Context). // checks if "handler" is context.Handler: func(context.Context).
func isContextHandler(handler interface{}) (context.Handler, bool) { func isContextHandler(handler interface{}) (context.Handler, bool) {
h, is := handler.(context.Handler) h, is := handler.(context.Handler)
@ -42,7 +50,7 @@ func validateHandler(handler interface{}) error {
// custom structs, Result(View | Response) and anything that you can imagine, // custom structs, Result(View | Response) and anything that you can imagine,
// and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application, // and returns a low-level `context/iris.Handler` which can be used anywhere in the Iris Application,
// as middleware or as simple route handler or party handler or subdomain handler-router. // as middleware or as simple route handler or party handler or subdomain handler-router.
func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler, error) { func makeHandler(handler interface{}, errorHandler di.ErrorHandler, values ...reflect.Value) (context.Handler, error) {
if err := validateHandler(handler); err != nil { if err := validateHandler(handler); err != nil {
return nil, err return nil, err
} }
@ -64,6 +72,8 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler,
} }
funcInjector := di.Func(fn, values...) funcInjector := di.Func(fn, values...)
funcInjector.ErrorHandler(errorHandler)
valid := funcInjector.Length == n valid := funcInjector.Length == n
if !valid { if !valid {
@ -73,7 +83,8 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler,
// using binders for path parameters: string, int, int64, uint8, uint64, bool and so on. // using binders for path parameters: string, int, int64, uint8, uint64, bool and so on.
// We don't have access to the path, so neither to the macros here, // We don't have access to the path, so neither to the macros here,
// but in mvc. So we have to do it here. // but in mvc. So we have to do it here.
if valid = funcInjector.Retry(new(params).resolve); !valid { valid = funcInjector.Retry(new(params).resolve)
if !valid {
pc := fn.Pointer() pc := fn.Pointer()
fpc := runtime.FuncForPC(pc) fpc := runtime.FuncForPC(pc)
callerFileName, callerLineNumber := fpc.FileLine(pc) callerFileName, callerLineNumber := fpc.FileLine(pc)
@ -89,7 +100,7 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler,
// in := make([]reflect.Value, n, n) // in := make([]reflect.Value, n, n)
// funcInjector.Inject(&in, reflect.ValueOf(ctx)) // funcInjector.Inject(&in, reflect.ValueOf(ctx))
// DispatchFuncResult(ctx, fn.Call(in)) // DispatchFuncResult(ctx, fn.Call(in))
DispatchFuncResult(ctx, nil, funcInjector.Call(reflect.ValueOf(ctx))) DispatchFuncResult(ctx, nil, funcInjector.Call(ctx))
} }
return h, nil return h, nil

View File

@ -4,6 +4,7 @@ package hero_test
import ( import (
"fmt" "fmt"
"reflect"
"testing" "testing"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
@ -14,8 +15,8 @@ import (
// dynamic func // dynamic func
type testUserStruct struct { type testUserStruct struct {
ID int64 ID int64 `json:"id"`
Username string Username string `json:"username"`
} }
func testBinderFunc(ctx iris.Context) testUserStruct { func testBinderFunc(ctx iris.Context) testUserStruct {
@ -126,3 +127,38 @@ func TestBindFunctionAsFunctionInputArgument(t *testing.T) {
e.POST("/").WithFormField("username", expectedUsername). e.POST("/").WithFormField("username", expectedUsername).
Expect().Status(iris.StatusOK).Body().Equal(expectedUsername) Expect().Status(iris.StatusOK).Body().Equal(expectedUsername)
} }
func TestBindReflectValue(t *testing.T) {
// TODO: THINK of simplify this,
// as 'hero' and 'mvc' are not depend on the root kataras/iris/v12 package, smart decision back then.
// e.g.
// app := iris.New()
// app.RegisterDependency(...)
// app.HandleFunc("GET POST", "/", func(input MyInput) MyOutput {})
// instead of:
// app := iris.New()
// h := hero.New()
// h.Register(...) or hero.Register for shared deps across Iris different applications.
// handler := h.Handler(func(input MyInput) MyOutput {})
// app.HandleMany("GET POST", "/", handler)
h := New()
h.Register(func(ctx iris.Context) reflect.Value {
var v interface{}
err := ctx.ReadJSON(&v)
if err != nil {
t.Fatal(err)
}
return reflect.ValueOf(v)
// return reflect.Value{}
})
postHandler := h.Handler(func(input testUserStruct) string {
return input.Username
})
app := iris.New()
app.Post("/", postHandler)
e := httptest.New(t, app)
e.POST("/").WithJSON(iris.Map{"username": "makis"}).Expect().Status(httptest.StatusOK).Body().Equal("makis")
}

View File

@ -1,10 +1,10 @@
package hero package hero
import ( import (
"github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/hero/di" "github.com/kataras/iris/v12/hero/di"
"github.com/kataras/golog" "github.com/kataras/golog"
"github.com/kataras/iris/v12/context"
) )
// def is the default hero value which can be used for dependencies share. // def is the default hero value which can be used for dependencies share.
@ -22,7 +22,8 @@ var def = New()
// //
// For a more high-level structure please take a look at the "mvc.go#Application". // For a more high-level structure please take a look at the "mvc.go#Application".
type Hero struct { type Hero struct {
values di.Values values di.Values
errorHandler di.ErrorHandler
} }
// New returns a new Hero, a container for dependencies and a factory // New returns a new Hero, a container for dependencies and a factory
@ -30,7 +31,8 @@ type Hero struct {
// Please take a look at the structure's documentation for more information. // Please take a look at the structure's documentation for more information.
func New() *Hero { func New() *Hero {
return &Hero{ return &Hero{
values: di.NewValues(), values: di.NewValues(),
errorHandler: di.DefaultErrorHandler,
} }
} }
@ -83,6 +85,14 @@ func (h *Hero) Clone() *Hero {
return child return child
} }
// ErrorHandler sets a handler for this hero instance
// which will be fired when a handler's second output argument is error and it's not nil
// or when a request-scoped dynamic function dependency's second output argument is error and it's not nil.
func (h *Hero) ErrorHandler(errorHandler func(ctx context.Context, err error)) *Hero {
h.errorHandler = di.ErrorHandlerFunc(errorHandler)
return h
}
// Handler accepts a "handler" function which can accept any input arguments that match // Handler accepts a "handler" function which can accept any input arguments that match
// with the Hero's `Dependencies` and any output result; like string, int (string,int), // with the Hero's `Dependencies` and any output result; like string, int (string,int),
// custom structs, Result(View | Response) and anything you can imagine. // custom structs, Result(View | Response) and anything you can imagine.
@ -98,7 +108,7 @@ func Handler(handler interface{}) context.Handler {
// It returns a standard `iris/context.Handler` which can be used anywhere in an Iris Application, // It returns a standard `iris/context.Handler` which can be used anywhere in an Iris Application,
// as middleware or as simple route handler or subdomain's handler. // as middleware or as simple route handler or subdomain's handler.
func (h *Hero) Handler(fn interface{}) context.Handler { func (h *Hero) Handler(fn interface{}) context.Handler {
handler, err := makeHandler(fn, h.values.Clone()...) handler, err := makeHandler(fn, h.errorHandler, h.values.Clone()...)
if err != nil { if err != nil {
golog.Errorf("hero handler: %v", err) golog.Errorf("hero handler: %v", err)
} }

View File

@ -4,6 +4,7 @@ import (
"reflect" "reflect"
"github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/context"
"github.com/kataras/iris/v12/hero/di"
) )
// weak because we don't have access to the path, neither // weak because we don't have access to the path, neither
@ -17,7 +18,7 @@ type params struct {
next int next int
} }
func (p *params) resolve(index int, typ reflect.Type) (reflect.Value, bool) { func (p *params) resolve(index int, typ reflect.Type, _ di.Values) (reflect.Value, bool) {
currentParamIndex := p.next currentParamIndex := p.next
v, ok := context.ParamResolverByTypeAndIndex(typ, currentParamIndex) v, ok := context.ParamResolverByTypeAndIndex(typ, currentParamIndex)

View File

@ -91,7 +91,7 @@ type ControllerActivator struct {
dependencies di.Values dependencies di.Values
sorter di.Sorter sorter di.Sorter
errorHandler hero.ErrorHandler errorHandler di.ErrorHandler
// initialized on the first `Handle` or immediately when "servesWebsocket" is true. // initialized on the first `Handle` or immediately when "servesWebsocket" is true.
injector *di.StructInjector injector *di.StructInjector
@ -112,7 +112,7 @@ func NameOf(v interface{}) string {
return fullname return fullname
} }
func newControllerActivator(router router.Party, controller interface{}, dependencies []reflect.Value, sorter di.Sorter, errorHandler hero.ErrorHandler) *ControllerActivator { func newControllerActivator(router router.Party, controller interface{}, dependencies []reflect.Value, sorter di.Sorter, errorHandler di.ErrorHandler) *ControllerActivator {
typ := reflect.TypeOf(controller) typ := reflect.TypeOf(controller)
c := &ControllerActivator{ c := &ControllerActivator{
@ -425,8 +425,9 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
c.attachInjector() c.attachInjector()
// fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies)
funcInjector := di.Func(m.Func, funcDependencies...) funcInjector := di.Func(m.Func, funcDependencies...)
funcInjector.ErrorHandler(c.errorHandler)
// fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length)
if funcInjector.Has { if funcInjector.Has {
golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.fullName, m.Name, funcInjector.String()) golog.Debugf("MVC dependencies of method '%s.%s':\n%s", c.fullName, m.Name, funcInjector.String())
@ -452,15 +453,13 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
return func(ctx context.Context) { return func(ctx context.Context) {
var ( var (
ctrl = c.injector.Acquire() ctrl = c.injector.Acquire()
ctxValue reflect.Value
errorHandler = c.errorHandler errorHandler = c.errorHandler
) )
// inject struct fields first before the BeginRequest and EndRequest, if any, // inject struct fields first before the BeginRequest and EndRequest, if any,
// in order to be able to have access there. // in order to be able to have access there.
if hasBindableFields { if hasBindableFields {
ctxValue = reflect.ValueOf(ctx) c.injector.InjectElem(ctx, ctrl.Elem())
c.injector.InjectElem(ctrl.Elem(), ctxValue)
} }
// check if has BeginRequest & EndRequest, before try to bind the method's inputs. // check if has BeginRequest & EndRequest, before try to bind the method's inputs.
@ -479,18 +478,15 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref
} }
if funcHasErrorOut && implementsErrorHandler { if funcHasErrorOut && implementsErrorHandler {
errorHandler = ctrl.Interface().(hero.ErrorHandler) errorHandler = ctrl.Interface().(di.ErrorHandler)
} }
if hasBindableFuncInputs { if hasBindableFuncInputs {
// means that ctxValue is not initialized before by the controller's struct injector. // means that ctxValue is not initialized before by the controller's struct injector.
if !hasBindableFields {
ctxValue = reflect.ValueOf(ctx)
}
in := make([]reflect.Value, n) in := make([]reflect.Value, n)
in[0] = ctrl in[0] = ctrl
funcInjector.Inject(&in, ctxValue) funcInjector.Inject(ctx, &in)
if ctx.IsStopped() { if ctx.IsStopped() {
return // stop as soon as possible, although it would stop later on if `ctx.StopExecution` called. return // stop as soon as possible, although it would stop later on if `ctx.StopExecution` called.

View File

@ -48,13 +48,14 @@ type Application struct {
Router router.Party Router router.Party
Controllers []*ControllerActivator Controllers []*ControllerActivator
websocketControllers []websocket.ConnHandler websocketControllers []websocket.ConnHandler
ErrorHandler hero.ErrorHandler ErrorHandler di.ErrorHandler
} }
func newApp(subRouter router.Party, values di.Values) *Application { func newApp(subRouter router.Party, values di.Values) *Application {
return &Application{ return &Application{
Router: subRouter, Router: subRouter,
Dependencies: values, Dependencies: values,
ErrorHandler: di.DefaultErrorHandler,
} }
} }
@ -211,7 +212,7 @@ func makeInjector(injector *di.StructInjector) websocket.StructInjector {
return func(_ reflect.Type, nsConn *websocket.NSConn) reflect.Value { return func(_ reflect.Type, nsConn *websocket.NSConn) reflect.Value {
v := injector.Acquire() v := injector.Acquire()
if injector.CanInject { if injector.CanInject {
injector.InjectElem(v.Elem(), reflect.ValueOf(websocket.GetContext(nsConn.Conn))) injector.InjectElem(websocket.GetContext(nsConn.Conn), v.Elem())
} }
return v return v
} }
@ -258,8 +259,8 @@ func (app *Application) handle(controller interface{}) *ControllerActivator {
// HandleError registers a `hero.ErrorHandlerFunc` which will be fired when // HandleError registers a `hero.ErrorHandlerFunc` which will be fired when
// application's controllers' functions returns an non-nil error. // application's controllers' functions returns an non-nil error.
// Each controller can override it by implementing the `hero.ErrorHandler`. // Each controller can override it by implementing the `hero.ErrorHandler`.
func (app *Application) HandleError(errHandler hero.ErrorHandlerFunc) *Application { func (app *Application) HandleError(errorHandler func(ctx context.Context, err error)) *Application {
app.ErrorHandler = errHandler app.ErrorHandler = di.ErrorHandlerFunc(errorHandler)
return app return app
} }

View File

@ -3,12 +3,12 @@ package mvc
import ( import (
"reflect" "reflect"
"github.com/kataras/iris/v12/hero" "github.com/kataras/iris/v12/hero/di"
) )
var ( var (
baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem() baseControllerTyp = reflect.TypeOf((*BaseController)(nil)).Elem()
errorHandlerTyp = reflect.TypeOf((*hero.ErrorHandler)(nil)).Elem() errorHandlerTyp = reflect.TypeOf((*di.ErrorHandler)(nil)).Elem()
errorTyp = reflect.TypeOf((*error)(nil)).Elem() errorTyp = reflect.TypeOf((*error)(nil)).Elem()
) )

View File

@ -340,7 +340,7 @@ func (s *HTMLEngine) loadAssets() error {
rel, err := filepath.Rel(virtualDirectory, path) rel, err := filepath.Rel(virtualDirectory, path)
if err != nil { if err != nil {
templateErr = err templateErr = err
continue break
} }
// // take the current working directory // // take the current working directory
@ -358,7 +358,7 @@ func (s *HTMLEngine) loadAssets() error {
buf, err := assetFn(path) buf, err := assetFn(path)
if err != nil { if err != nil {
templateErr = fmt.Errorf("%v for path '%s'", err, path) templateErr = fmt.Errorf("%v for path '%s'", err, path)
continue break
} }
contents := string(buf) contents := string(buf)
@ -372,14 +372,14 @@ func (s *HTMLEngine) loadAssets() error {
contents, err = s.middleware(name, buf) contents, err = s.middleware(name, buf)
if err != nil { if err != nil {
templateErr = fmt.Errorf("%v for name '%s'", err, name) templateErr = fmt.Errorf("%v for name '%s'", err, name)
continue break
} }
} }
// Add our funcmaps. // Add our funcmaps.
if _, err = tmpl.Funcs(emptyFuncs).Funcs(s.funcs).Parse(contents); err != nil { if _, err = tmpl.Funcs(emptyFuncs).Funcs(s.funcs).Parse(contents); err != nil {
templateErr = err templateErr = err
continue break
} }
} }
} }

View File

@ -1,7 +1,11 @@
package view package view
import ( import (
"github.com/Joker/jade" "bytes"
"io/ioutil"
"path"
"github.com/iris-contrib/jade"
) )
// Pug (or Jade) returns a new pug view engine. // Pug (or Jade) returns a new pug view engine.
@ -9,7 +13,7 @@ import (
// html view engine, it uses the same exactly configuration. // html view engine, it uses the same exactly configuration.
// It has got some features and a lot of functions // It has got some features and a lot of functions
// which will make your life easier. // which will make your life easier.
// Read more about the Jade Go Template: https://github.com/Joker/jade // Read more about the Jade Go Parser: https://github.com/Joker/jade
// //
// Examples: // Examples:
// https://github.com/kataras/iris/tree/master/_examples/view/template_pug_0 // https://github.com/kataras/iris/tree/master/_examples/view/template_pug_0
@ -18,6 +22,29 @@ import (
// https://github.com/kataras/iris/tree/master/_examples/view/template_pug_3 // https://github.com/kataras/iris/tree/master/_examples/view/template_pug_3
func Pug(directory, extension string) *HTMLEngine { func Pug(directory, extension string) *HTMLEngine {
s := HTML(directory, extension) s := HTML(directory, extension)
s.middleware = jade.Parse s.middleware = func(name string, text []byte) (contents string, err error) {
tmpl := jade.New(name)
// Fixes: https://github.com/kataras/iris/issues/1450
// by adding a custom ReadFunc inside the jade parser.
// And Also able to use relative paths on "extends" and "include" directives:
// e.g. instead of extends "templates/layout.pug" we use "layout.pug"
// so contents of templates are independent of their root location.
tmpl.ReadFunc = func(name string) ([]byte, error) {
name = path.Join(directory, name)
if s.assetFn != nil {
return s.assetFn(name)
}
return ioutil.ReadFile(name)
}
tmpl, err = tmpl.Parse(text)
if err != nil {
return
}
b := new(bytes.Buffer)
tmpl.WriteIn(b)
return b.String(), nil
}
return s return s
} }