From 387c90b350d5ea83d74a16e2815735ba624440f6 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 14 Feb 2020 23:34:56 +0200 Subject: [PATCH] fix https://github.com/kataras/iris/issues/1450 and continue on implementing 1449 --- .../embedding-templates-into-app/bindata.go | 25 +- .../view/template_jet_1_embedded/main.go | 3 + .../view/template_pug_2/templates/index.pug | 4 +- _examples/view/template_pug_3/bindata.go | 269 ++++++++++++++++++ _examples/view/template_pug_3/main.go | 5 +- .../view/template_pug_3/templates/index.pug | 2 +- context/context.go | 21 ++ go.mod | 2 +- hero/di.go | 26 +- hero/di/di.go | 59 +++- hero/di/func.go | 66 +++-- hero/di/object.go | 48 ++-- hero/di/reflect.go | 8 +- hero/di/struct.go | 10 +- hero/func_result.go | 21 +- hero/handler.go | 17 +- hero/handler_test.go | 40 ++- hero/hero.go | 18 +- hero/param.go | 3 +- mvc/controller.go | 18 +- mvc/mvc.go | 9 +- mvc/reflect.go | 4 +- view/html.go | 8 +- view/pug.go | 33 ++- 24 files changed, 578 insertions(+), 141 deletions(-) create mode 100644 _examples/view/template_pug_3/bindata.go diff --git a/_examples/view/embedding-templates-into-app/bindata.go b/_examples/view/embedding-templates-into-app/bindata.go index 896c18071..e2a2821be 100644 --- a/_examples/view/embedding-templates-into-app/bindata.go +++ b/_examples/view/embedding-templates-into-app/bindata.go @@ -1,11 +1,9 @@ -// Code generated by go-bindata. +// Code generated for package main by go-bindata DO NOT EDIT. (@generated) // sources: // templates/layouts/layout.html // templates/layouts/mylayout.html // templates/page1.html // templates/partials/page1_partial1.html -// DO NOT EDIT! - package main import ( @@ -52,21 +50,32 @@ type bindataFileInfo struct { 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 false + return fi.mode&os.ModeDir != 0 } + +// Sys return file is sys mode func (fi bindataFileInfo) Sys() interface{} { return nil } @@ -86,7 +95,7 @@ func templatesLayoutsLayoutHtml() (*asset, error) { 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} return a, nil } @@ -106,7 +115,7 @@ func templatesLayoutsMylayoutHtml() (*asset, error) { 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} return a, nil } @@ -126,7 +135,7 @@ func templatesPage1Html() (*asset, error) { 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} return a, nil } @@ -146,7 +155,7 @@ func templatesPartialsPage1_partial1Html() (*asset, error) { 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} return a, nil } diff --git a/_examples/view/template_jet_1_embedded/main.go b/_examples/view/template_jet_1_embedded/main.go index 2b981b9c6..2d9e4d6f6 100644 --- a/_examples/view/template_jet_1_embedded/main.go +++ b/_examples/view/template_jet_1_embedded/main.go @@ -11,6 +11,9 @@ import ( "github.com/kataras/iris/v12" ) +// $ go get -u github.com/go-bindata/go-bindata/... +// $ go-bindata ./views/... +// $ go build func main() { app := iris.New() tmpl := iris.Jet("./views", ".jet").Binary(Asset, AssetNames) diff --git a/_examples/view/template_pug_2/templates/index.pug b/_examples/view/template_pug_2/templates/index.pug index a006f8651..ee11d9b4d 100644 --- a/_examples/view/template_pug_2/templates/index.pug +++ b/_examples/view/template_pug_2/templates/index.pug @@ -1,7 +1,7 @@ doctype html html - include templates/header.pug + include header.pug body h1 My Site p {{ bold "Welcome to my super lame site."}} - include templates/footer.pug \ No newline at end of file + include footer.pug \ No newline at end of file diff --git a/_examples/view/template_pug_3/bindata.go b/_examples/view/template_pug_3/bindata.go new file mode 100644 index 000000000..19e036883 --- /dev/null +++ b/_examples/view/template_pug_3/bindata.go @@ -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, "/")...)...) +} diff --git a/_examples/view/template_pug_3/main.go b/_examples/view/template_pug_3/main.go index ef3766939..ac7b0b55c 100644 --- a/_examples/view/template_pug_3/main.go +++ b/_examples/view/template_pug_3/main.go @@ -2,10 +2,13 @@ package main import "github.com/kataras/iris/v12" +// $ go get -u github.com/go-bindata/go-bindata/... +// $ go-bindata ./templates/... +// $ go build func main() { app := iris.New() - tmpl := iris.Pug("./templates", ".pug") + tmpl := iris.Pug("./templates", ".pug").Binary(Asset, AssetNames) app.RegisterView(tmpl) diff --git a/_examples/view/template_pug_3/templates/index.pug b/_examples/view/template_pug_3/templates/index.pug index 257f4a7cf..f061806c2 100644 --- a/_examples/view/template_pug_3/templates/index.pug +++ b/_examples/view/template_pug_3/templates/index.pug @@ -1,4 +1,4 @@ -extends templates/layout.pug +extends layout.pug block title title Article Title diff --git a/context/context.go b/context/context.go index 8a38656f6..9800fd30a 100644 --- a/context/context.go +++ b/context/context.go @@ -16,6 +16,7 @@ import ( "os" "path" "path/filepath" + "reflect" "regexp" "strconv" "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. 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. // Worth to notice that this function returns an interface // 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) } +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. // Worth to notice that this function returns an interface // of the Application, which contains methods that are safe diff --git a/go.mod b/go.mod index 8ff2797a1..1021a91a9 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.13 require ( github.com/BurntSushi/toml v0.3.1 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/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible 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/pongo2 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/kataras/golog v0.0.10 github.com/kataras/neffos v0.0.14 diff --git a/hero/di.go b/hero/di.go index 80c3d3d3e..35c8158bd 100644 --- a/hero/di.go +++ b/hero/di.go @@ -3,22 +3,12 @@ package hero import ( "reflect" + "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/hero/di" ) func init() { 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) { return nil, false } @@ -29,8 +19,8 @@ func init() { return &di.BindObject{ Type: contextTyp, BindType: di.Dynamic, - ReturnValue: func(ctxValue []reflect.Value) reflect.Value { - return ctxValue[0] + ReturnValue: func(ctx context.Context) reflect.Value { + return ctx.ReflectValue()[0] }, }, 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). 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() + }) } diff --git a/hero/di/di.go b/hero/di/di.go index 83eb80aa6..9f23ca469 100644 --- a/hero/di/di.go +++ b/hero/di/di.go @@ -1,8 +1,12 @@ // 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 -import "reflect" +import ( + "reflect" + + "github.com/kataras/iris/v12/context" +) type ( // 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 // is valid to be binded. 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 ( // DefaultHijacker is the hijacker used on the package-level Struct & Func functions. DefaultHijacker Hijacker // DefaultTypeChecker is the typechecker used on the package-level Struct & Func functions. 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 @@ -26,7 +50,7 @@ var ( // with the injector's `Inject` and `InjectElem` methods. func Struct(s interface{}, values ...reflect.Value) *StructInjector { if s == nil { - return &StructInjector{Has: false} + return &StructInjector{} } return MakeStructInjector( @@ -45,13 +69,14 @@ func Struct(s interface{}, values ...reflect.Value) *StructInjector { // with the injector's `Inject` method. func Func(fn interface{}, values ...reflect.Value) *FuncInjector { if fn == nil { - return &FuncInjector{Has: false} + return &FuncInjector{} } return MakeFuncInjector( ValueOf(fn), DefaultHijacker, DefaultTypeChecker, + DefaultErrorHandler, values..., ) } @@ -63,9 +88,10 @@ func Func(fn interface{}, values ...reflect.Value) *FuncInjector { type D struct { Values - hijacker Hijacker - goodFunc TypeChecker - sorter Sorter + hijacker Hijacker + goodFunc TypeChecker + errorHandler ErrorHandler + sorter Sorter } // New creates and returns a new Dependency Injection container. @@ -87,6 +113,13 @@ func (d *D) GoodFunc(fn TypeChecker) *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. func (d *D) Sort(with Sorter) *D { 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. func (d *D) Clone() *D { return &D{ - Values: d.Values.Clone(), - hijacker: d.hijacker, - goodFunc: d.goodFunc, - sorter: d.sorter, + Values: d.Values.Clone(), + hijacker: d.hijacker, + goodFunc: d.goodFunc, + errorHandler: d.errorHandler, + sorter: d.sorter, } } @@ -136,6 +170,7 @@ func (d *D) Func(fn interface{}) *FuncInjector { ValueOf(fn), d.hijacker, d.goodFunc, + d.errorHandler, d.Values..., - ) + ).ErrorHandler(d.errorHandler) } diff --git a/hero/di/func.go b/hero/di/func.go index f234c5ca2..76c316107 100644 --- a/hero/di/func.go +++ b/hero/di/func.go @@ -3,6 +3,8 @@ package di import ( "fmt" "reflect" + + "github.com/kataras/iris/v12/context" ) type ( @@ -16,9 +18,10 @@ type ( FuncInjector struct { // the original function, is being used // only the .Call, which is referring to the same function, always. - fn reflect.Value - typ reflect.Type - goodFunc TypeChecker + fn reflect.Value + typ reflect.Type + goodFunc TypeChecker + errorHandler ErrorHandler inputs []*targetFuncInput // Length is the number of the valid, final binded input arguments. @@ -32,13 +35,15 @@ type ( ) type missingInput struct { - index int // the function's input argument's index. - found bool + index int // the function's input argument's index. + found bool + remaining Values } -func (s *FuncInjector) miss(index int) { +func (s *FuncInjector) miss(index int, remaining Values) { 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. // // 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()) s := &FuncInjector{ - fn: fn, - typ: typ, - goodFunc: goodFunc, + fn: fn, + typ: typ, + goodFunc: goodFunc, + errorHandler: errorHandler, } 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 // inputs, so they can be used for a re-try // 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) // 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 { 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) { // fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n", // i, b.Type.String(), inTyp.String(), inTyp.Pointer()) @@ -141,9 +164,15 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool { 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 // 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 { if missing.found { continue @@ -152,7 +181,7 @@ func (s *FuncInjector) Retry(retryFn func(inIndex int, inTyp reflect.Type) (refl invalidIndex := missing.index inTyp := s.typ.In(invalidIndex) - v, ok := retryFn(invalidIndex, inTyp) + v, ok := retryFn(invalidIndex, inTyp, missing.remaining) if !ok { continue } @@ -186,12 +215,13 @@ func (s *FuncInjector) String() (trace string) { // Inject accepts an already created slice of input arguments // 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". -func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) { +func (s *FuncInjector) Inject(ctx context.Context, in *[]reflect.Value) { args := *in for _, input := range s.inputs { input.Object.Assign(ctx, func(v reflect.Value) { // fmt.Printf("assign input index: %d for value: %v of type: %s\n", // input.InputIndex, v.String(), v.Type().Name()) + 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 // the caller should be able to in[0] = receiver before injection, // 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) - s.Inject(&in, ctx...) + s.Inject(ctx, &in) return s.fn.Call(in) } diff --git a/hero/di/object.go b/hero/di/object.go index 047d73620..96ed4dded 100644 --- a/hero/di/object.go +++ b/hero/di/object.go @@ -3,6 +3,8 @@ package di import ( "errors" "reflect" + + "github.com/kataras/iris/v12/context" ) // BindType is the type of a binded object/value, it's being used to @@ -35,7 +37,7 @@ type BindObject struct { Value reflect.Value 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 @@ -43,10 +45,10 @@ type BindObject struct { // 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 // 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) { b.BindType = Dynamic - b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc) + b.ReturnValue, b.Type, err = MakeReturnValue(v, goodFunc, errorHandler) } else { b.BindType = Static b.Type = v.Type() @@ -67,9 +69,9 @@ var errBad = errors.New("bad") // The "fn" can have the following form: // `func(myService) MyViewModel`. // -// The return type of the "fn" should be a value instance, not a pointer, for your own protection. -// The binder function should return only one value. -func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Value) reflect.Value, reflect.Type, error) { +// The return type of the "fn" should be a value instance, not a pointer. +// The binder function should return just one value. +func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker, errorHandler ErrorHandler) (func(ctx context.Context) reflect.Value, reflect.Type, error) { typ := IndirectType(fn.Type()) // invalid if not a func. @@ -93,33 +95,31 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Val firstOutTyp := typ.Out(0) firstZeroOutVal := reflect.New(firstOutTyp).Elem() - bf := func(ctxValue []reflect.Value) reflect.Value { - results := fn.Call(ctxValue) - - v := results[0] - if !v.IsValid() { // check the first value, second is error. - return firstZeroOutVal - } - + bf := func(ctx context.Context) reflect.Value { + results := fn.Call(ctx.ReflectValue()) if n == 2 { // two, second is always error. errVal := results[1] if !errVal.IsNil() { - // 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() + if errorHandler != nil { + errorHandler.HandleError(ctx, errVal.Interface().(error)) } return firstZeroOutVal } } + v := results[0] + if !v.IsValid() { // check the first value, second is error. + return firstZeroOutVal + } + + // if firstOutTyp == reflectValueType { + // converted := v.Convert(typ.In(0)) + // fmt.Printf("object.go#124: converted: %#+v\n", converted) + // return converted //reflect.ValueOf(v.Interface()) + // } + // if v.String() == "" { // println("di/object.go: " + v.String()) // // println("di/object.go: because it's interface{} it should be returned as: " + v.Elem().Type().String() + " and its value: " + v.Elem().Interface().(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 // 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 { toSetter(b.ReturnValue(ctx)) return diff --git a/hero/di/reflect.go b/hero/di/reflect.go index ba5985469..ccdc54977 100644 --- a/hero/di/reflect.go +++ b/hero/di/reflect.go @@ -138,10 +138,14 @@ func IsFunc(kindable interface { return kindable.Kind() == reflect.Func } +var reflectValueType = reflect.TypeOf(reflect.Value{}) + func equalTypes(got reflect.Type, expected reflect.Type) bool { if got == expected { return true } + + // fmt.Printf("got: %s expected: %s\n", got.String(), expected.String()) // if accepts an interface, check if the given "got" type does // implement this "expected" user handler's input argument. if expected.Kind() == reflect.Interface { @@ -151,10 +155,6 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool { return got.AssignableTo(expected) } - // if got.String() == "interface {}" { - // return true - // } - return false } diff --git a/hero/di/struct.go b/hero/di/struct.go index bb3d0873a..8a165db06 100644 --- a/hero/di/struct.go +++ b/hero/di/struct.go @@ -4,6 +4,8 @@ import ( "fmt" "reflect" "sort" + + "github.com/kataras/iris/v12/context" ) // 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. - b, err := MakeBindObject(val, goodFunc) + b, err := MakeBindObject(val, goodFunc, nil) if err != nil { 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), // 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. -func (s *StructInjector) Inject(dest interface{}, ctx ...reflect.Value) { +func (s *StructInjector) Inject(ctx context.Context, dest interface{}) { if dest == nil { return } 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. -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 { f.Object.Assign(ctx, func(v reflect.Value) { ff := destElem.FieldByIndex(f.FieldIndex) diff --git a/hero/func_result.go b/hero/func_result.go index 23b38828a..54cae1e1d 100644 --- a/hero/func_result.go +++ b/hero/func_result.go @@ -20,23 +20,6 @@ type Result interface { 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} // 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... // // 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 { return } @@ -314,7 +297,7 @@ func DispatchFuncResult(ctx context.Context, errorHandler ErrorHandler, values [ if errorHandler != nil { errorHandler.HandleError(ctx, value) - break + return } err = value diff --git a/hero/handler.go b/hero/handler.go index 3dd585d47..cb646350b 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -18,6 +18,14 @@ func IsContext(inTyp reflect.Type) bool { 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). func isContextHandler(handler interface{}) (context.Handler, bool) { 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, // 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. -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 { return nil, err } @@ -64,6 +72,8 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler, } funcInjector := di.Func(fn, values...) + funcInjector.ErrorHandler(errorHandler) + valid := funcInjector.Length == n 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. // We don't have access to the path, so neither to the macros 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() fpc := runtime.FuncForPC(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) // funcInjector.Inject(&in, reflect.ValueOf(ctx)) // DispatchFuncResult(ctx, fn.Call(in)) - DispatchFuncResult(ctx, nil, funcInjector.Call(reflect.ValueOf(ctx))) + DispatchFuncResult(ctx, nil, funcInjector.Call(ctx)) } return h, nil diff --git a/hero/handler_test.go b/hero/handler_test.go index 91a5385a9..3ee73a6bb 100644 --- a/hero/handler_test.go +++ b/hero/handler_test.go @@ -4,6 +4,7 @@ package hero_test import ( "fmt" + "reflect" "testing" "github.com/kataras/iris/v12" @@ -14,8 +15,8 @@ import ( // dynamic func type testUserStruct struct { - ID int64 - Username string + ID int64 `json:"id"` + Username string `json:"username"` } func testBinderFunc(ctx iris.Context) testUserStruct { @@ -126,3 +127,38 @@ func TestBindFunctionAsFunctionInputArgument(t *testing.T) { e.POST("/").WithFormField("username", 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") +} diff --git a/hero/hero.go b/hero/hero.go index 369a37af0..0570be28d 100644 --- a/hero/hero.go +++ b/hero/hero.go @@ -1,10 +1,10 @@ package hero import ( + "github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/hero/di" "github.com/kataras/golog" - "github.com/kataras/iris/v12/context" ) // 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". type Hero struct { - values di.Values + values di.Values + errorHandler di.ErrorHandler } // 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. func New() *Hero { return &Hero{ - values: di.NewValues(), + values: di.NewValues(), + errorHandler: di.DefaultErrorHandler, } } @@ -83,6 +85,14 @@ func (h *Hero) Clone() *Hero { 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 // with the Hero's `Dependencies` and any output result; like string, int (string,int), // 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, // as middleware or as simple route handler or subdomain's 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 { golog.Errorf("hero handler: %v", err) } diff --git a/hero/param.go b/hero/param.go index 4c68fd34c..e41ccdea9 100644 --- a/hero/param.go +++ b/hero/param.go @@ -4,6 +4,7 @@ import ( "reflect" "github.com/kataras/iris/v12/context" + "github.com/kataras/iris/v12/hero/di" ) // weak because we don't have access to the path, neither @@ -17,7 +18,7 @@ type params struct { 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 v, ok := context.ParamResolverByTypeAndIndex(typ, currentParamIndex) diff --git a/mvc/controller.go b/mvc/controller.go index 2093001a1..63f43404c 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -91,7 +91,7 @@ type ControllerActivator struct { dependencies di.Values sorter di.Sorter - errorHandler hero.ErrorHandler + errorHandler di.ErrorHandler // initialized on the first `Handle` or immediately when "servesWebsocket" is true. injector *di.StructInjector @@ -112,7 +112,7 @@ func NameOf(v interface{}) string { 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) c := &ControllerActivator{ @@ -425,8 +425,9 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref c.attachInjector() // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) - funcInjector := di.Func(m.Func, funcDependencies...) + funcInjector.ErrorHandler(c.errorHandler) + // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) if funcInjector.Has { 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) { var ( ctrl = c.injector.Acquire() - ctxValue reflect.Value errorHandler = c.errorHandler ) // inject struct fields first before the BeginRequest and EndRequest, if any, // in order to be able to have access there. if hasBindableFields { - ctxValue = reflect.ValueOf(ctx) - c.injector.InjectElem(ctrl.Elem(), ctxValue) + c.injector.InjectElem(ctx, ctrl.Elem()) } // 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 { - errorHandler = ctrl.Interface().(hero.ErrorHandler) + errorHandler = ctrl.Interface().(di.ErrorHandler) } if hasBindableFuncInputs { // 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[0] = ctrl - funcInjector.Inject(&in, ctxValue) + funcInjector.Inject(ctx, &in) if ctx.IsStopped() { return // stop as soon as possible, although it would stop later on if `ctx.StopExecution` called. diff --git a/mvc/mvc.go b/mvc/mvc.go index 2051db6ce..fd1121ba5 100644 --- a/mvc/mvc.go +++ b/mvc/mvc.go @@ -48,13 +48,14 @@ type Application struct { Router router.Party Controllers []*ControllerActivator websocketControllers []websocket.ConnHandler - ErrorHandler hero.ErrorHandler + ErrorHandler di.ErrorHandler } func newApp(subRouter router.Party, values di.Values) *Application { return &Application{ Router: subRouter, 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 { v := injector.Acquire() if injector.CanInject { - injector.InjectElem(v.Elem(), reflect.ValueOf(websocket.GetContext(nsConn.Conn))) + injector.InjectElem(websocket.GetContext(nsConn.Conn), v.Elem()) } return v } @@ -258,8 +259,8 @@ func (app *Application) handle(controller interface{}) *ControllerActivator { // HandleError registers a `hero.ErrorHandlerFunc` which will be fired when // application's controllers' functions returns an non-nil error. // Each controller can override it by implementing the `hero.ErrorHandler`. -func (app *Application) HandleError(errHandler hero.ErrorHandlerFunc) *Application { - app.ErrorHandler = errHandler +func (app *Application) HandleError(errorHandler func(ctx context.Context, err error)) *Application { + app.ErrorHandler = di.ErrorHandlerFunc(errorHandler) return app } diff --git a/mvc/reflect.go b/mvc/reflect.go index eb2b871ee..c993b939a 100644 --- a/mvc/reflect.go +++ b/mvc/reflect.go @@ -3,12 +3,12 @@ package mvc import ( "reflect" - "github.com/kataras/iris/v12/hero" + "github.com/kataras/iris/v12/hero/di" ) var ( 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() ) diff --git a/view/html.go b/view/html.go index 1d720db48..c030ad7df 100644 --- a/view/html.go +++ b/view/html.go @@ -340,7 +340,7 @@ func (s *HTMLEngine) loadAssets() error { rel, err := filepath.Rel(virtualDirectory, path) if err != nil { templateErr = err - continue + break } // // take the current working directory @@ -358,7 +358,7 @@ func (s *HTMLEngine) loadAssets() error { buf, err := assetFn(path) if err != nil { templateErr = fmt.Errorf("%v for path '%s'", err, path) - continue + break } contents := string(buf) @@ -372,14 +372,14 @@ func (s *HTMLEngine) loadAssets() error { contents, err = s.middleware(name, buf) if err != nil { templateErr = fmt.Errorf("%v for name '%s'", err, name) - continue + break } } // Add our funcmaps. if _, err = tmpl.Funcs(emptyFuncs).Funcs(s.funcs).Parse(contents); err != nil { templateErr = err - continue + break } } } diff --git a/view/pug.go b/view/pug.go index 5fd05f12e..9c312d297 100644 --- a/view/pug.go +++ b/view/pug.go @@ -1,7 +1,11 @@ package view import ( - "github.com/Joker/jade" + "bytes" + "io/ioutil" + "path" + + "github.com/iris-contrib/jade" ) // Pug (or Jade) returns a new pug view engine. @@ -9,7 +13,7 @@ import ( // html view engine, it uses the same exactly configuration. // It has got some features and a lot of functions // 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: // 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 func Pug(directory, extension string) *HTMLEngine { 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 }