diff --git a/README.md b/README.md index 511e902a..a9b18b6f 100644 --- a/README.md +++ b/README.md @@ -12,26 +12,6 @@ Learn what [others say about Iris](https://iris-go.com/testimonials/) and **star [![https://www.facebook.com/iris.framework/posts/3276606095684693](https://iris-go.com/images/iris-112-released.png)](https://www.facebook.com/iris.framework/posts/3276606095684693) -## It's your time to decide - -The Go Team released the go modules[...](https://github.com/kataras/iris/issues/1370) - -[![](https://api.gh-polls.com/poll/01DP9QJSMZK2FHP9K5CM712CQT/Keep%20the%20same%20import%20path)](https://api.gh-polls.com/poll/01DP9QJSMZK2FHP9K5CM712CQT/Keep%20the%20same%20import%20path/vote) -[![](https://api.gh-polls.com/poll/01DP9QJSMZK2FHP9K5CM712CQT/Add%20version%20suffix)](https://api.gh-polls.com/poll/01DP9QJSMZK2FHP9K5CM712CQT/Add%20version%20suffix/vote) - -> Read how [we support you](https://github.com/kataras/iris/wiki/Support). - -[![Iris vs .NET Core(C#) vs Node.js (Express)](https://github.com/kataras/iris/raw/master/_benchmarks/benchmarks_graph_22_october_2018_gray.png)](https://github.com/kataras/iris/blob/master/_benchmarks/README.md) - -
-Third-party benchmark - -[![](https://github.com/kataras/iris/raw/master/_benchmarks/benchmarks_third_party_source_snapshot_go_23_october_2018.png)](https://github.com/iris-contrib/third-party-benchmarks#full-table) - -> Last updated at: 01 March of 2019. Click to the image to view all results. You can run this in your own hardware by following the [steps here](https://github.com/iris-contrib/third-party-benchmarks#usage). - -
- ## Learning Iris
diff --git a/cache/cache_test.go b/cache/cache_test.go index 6c2f8081..166fecdd 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -1,6 +1,7 @@ package cache_test import ( + "fmt" "net/http" "sync/atomic" "testing" @@ -12,7 +13,6 @@ import ( "github.com/kataras/iris" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" "github.com/gavv/httpexpect" "github.com/kataras/iris/httptest" @@ -21,9 +21,17 @@ import ( var ( cacheDuration = 2 * time.Second expectedBodyStr = "Imagine it as a big message to achieve x20 response performance!" - errTestFailed = errors.New("expected the main handler to be executed %d times instead of %d") ) +type testError struct { + expected int + got uint32 +} + +func (h *testError) Error() string { + return fmt.Sprintf("expected the main handler to be executed %d times instead of %d", h.expected, h.got) +} + func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBodyStr string, nocache string) error { e.GET(path).Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) time.Sleep(cacheDuration / 5) // lets wait for a while, cache should be saved and ready @@ -31,7 +39,7 @@ func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBody counter := atomic.LoadUint32(counterPtr) if counter > 1 { // n should be 1 because it doesn't changed after the first call - return errTestFailed.Format(1, counter) + return &testError{1, counter} } time.Sleep(cacheDuration) @@ -42,7 +50,7 @@ func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBody e.GET(path).Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) counter = atomic.LoadUint32(counterPtr) if counter != 2 { - return errTestFailed.Format(2, counter) + return &testError{2, counter} } // we have cache response saved for the path, we have some time more here, but here @@ -51,7 +59,7 @@ func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBody e.GET(path).WithHeader("Authorization", "basic or anything").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) counter = atomic.LoadUint32(counterPtr) if counter != 4 { - return errTestFailed.Format(4, counter) + return &testError{4, counter} } if nocache != "" { @@ -69,14 +77,14 @@ func runTest(e *httpexpect.Expect, path string, counterPtr *uint32, expectedBody e.GET(nocache).Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter should be 6 counter = atomic.LoadUint32(counterPtr) if counter != 6 { // 4 before, 5 with the first call to store the cache, and six with the no cache, again original handler executation - return errTestFailed.Format(6, counter) + return &testError{6, counter} } // let's call again the path the expiration is not passed so it should be cached e.GET(path).Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) counter = atomic.LoadUint32(counterPtr) if counter != 6 { - return errTestFailed.Format(6, counter) + return &testError{6, counter} } // but now check for the No @@ -194,7 +202,7 @@ func TestCacheValidator(t *testing.T) { counter := atomic.LoadUint32(&n) if counter > 1 { // n should be 1 because it doesn't changed after the first call - t.Fatal(errTestFailed.Format(1, counter)) + t.Fatalf("%s: %v", t.Name(), &testError{1, counter}) } // don't execute from cache, execute the original, counter should ++ here e.GET("/invalid").Expect().Status(http.StatusOK).Body().Equal(expectedBodyStr) // counter = 2 @@ -203,6 +211,6 @@ func TestCacheValidator(t *testing.T) { counter = atomic.LoadUint32(&n) if counter != 3 { // n should be 1 because it doesn't changed after the first call - t.Fatalf(t.Name()+": %v", errTestFailed.Format(3, counter)) + t.Fatalf("%s: %v", t.Name(), &testError{3, counter}) } } diff --git a/configuration.go b/configuration.go index ea9d7f38..2c7c2fed 100644 --- a/configuration.go +++ b/configuration.go @@ -3,6 +3,7 @@ package iris import ( "bytes" "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -16,7 +17,6 @@ import ( "gopkg.in/yaml.v2" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" ) const globalConfigurationKeyword = "~" @@ -54,26 +54,24 @@ func homeDir() (home string) { return } -var errConfigurationDecode = errors.New("error while trying to decode configuration") - func parseYAML(filename string) (Configuration, error) { c := DefaultConfiguration() // get the abs // which will try to find the 'filename' from current workind dir too. yamlAbsPath, err := filepath.Abs(filename) if err != nil { - return c, errConfigurationDecode.AppendErr(err) + return c, fmt.Errorf("parse yaml: %w", err) } // read the raw contents of the file data, err := ioutil.ReadFile(yamlAbsPath) if err != nil { - return c, errConfigurationDecode.AppendErr(err) + return c, fmt.Errorf("parse yaml: %w", err) } // put the file's contents as yaml to the default configuration(c) if err := yaml.Unmarshal(data, &c); err != nil { - return c, errConfigurationDecode.AppendErr(err) + return c, fmt.Errorf("parse yaml: %w", err) } return c, nil } @@ -141,18 +139,18 @@ func TOML(filename string) Configuration { // which will try to find the 'filename' from current workind dir too. tomlAbsPath, err := filepath.Abs(filename) if err != nil { - panic(errConfigurationDecode.AppendErr(err)) + panic(fmt.Errorf("toml: %w", err)) } // read the raw contents of the file data, err := ioutil.ReadFile(tomlAbsPath) if err != nil { - panic(errConfigurationDecode.AppendErr(err)) + panic(fmt.Errorf("toml :%w", err)) } // put the file's contents as toml to the default configuration(c) if _, err := toml.Decode(string(data), &c); err != nil { - panic(errConfigurationDecode.AppendErr(err)) + panic(fmt.Errorf("toml :%w", err)) } // Author's notes: // The toml's 'usual thing' for key naming is: the_config_key instead of TheConfigKey diff --git a/context/context.go b/context/context.go index 0e547556..cfc69e87 100644 --- a/context/context.go +++ b/context/context.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "encoding/xml" + "errors" "fmt" "io" "io/ioutil" @@ -21,7 +22,6 @@ import ( "sync/atomic" "time" - "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/memstore" "github.com/Shopify/goreferrer" @@ -656,7 +656,7 @@ type Context interface { // like the HTTP Method is not "GET" or "HEAD" or if the "modtime" is zero // or if parsing time from the header failed. // - // It's mostly used internally, e.g. `context#WriteWithExpiration`. + // It's mostly used internally, e.g. `context#WriteWithExpiration`. See `ErrPreconditionFailed` too. // // Note that modtime.UTC() is being used instead of just modtime, so // you don't have to know the internals in order to make that works. @@ -1916,7 +1916,18 @@ func (ctx *context) URLParamEscape(name string) string { return DecodeQuery(ctx.URLParam(name)) } -var errURLParamNotFound = errors.New("url param '%s' does not exist") +// ErrNotFound is the type error which API users can make use of +// to check if a `Context` action of a `Handler` is type of Not Found, +// e.g. URL Query Parameters. +// Example: +// +// n, err := context.URLParamInt("url_query_param_name") +// if errors.Is(err, context.ErrNotFound) { +// // [handle error...] +// } +// Another usage would be `err == context.ErrNotFound` +// HOWEVER prefer use the new `errors.Is` as API details may change in the future. +var ErrNotFound = errors.New("not found") // URLParamInt returns the url query parameter as int value from a request, // returns -1 and an error if parse failed or not found. @@ -1929,7 +1940,7 @@ func (ctx *context) URLParamInt(name string) (int, error) { return n, nil } - return -1, errURLParamNotFound.Format(name) + return -1, ErrNotFound } // URLParamIntDefault returns the url query parameter as int value from a request, @@ -1969,7 +1980,7 @@ func (ctx *context) URLParamInt64(name string) (int64, error) { return n, nil } - return -1, errURLParamNotFound.Format(name) + return -1, ErrNotFound } // URLParamInt64Default returns the url query parameter as int64 value from a request, @@ -1994,7 +2005,7 @@ func (ctx *context) URLParamFloat64(name string) (float64, error) { return n, nil } - return -1, errURLParamNotFound.Format(name) + return -1, ErrNotFound } // URLParamFloat64Default returns the url query parameter as float64 value from a request, @@ -2178,8 +2189,6 @@ func (ctx *context) PostValueTrim(name string) string { return strings.TrimSpace(ctx.PostValue(name)) } -var errUnableToFindPostValue = errors.New("unable to find post value '%s'") - // PostValueInt returns the parsed form data from POST, PATCH, // or PUT body parameters based on a "name", as int. // @@ -2187,7 +2196,7 @@ var errUnableToFindPostValue = errors.New("unable to find post value '%s'") func (ctx *context) PostValueInt(name string) (int, error) { v := ctx.PostValue(name) if v == "" { - return -1, errUnableToFindPostValue.Format(name) + return -1, ErrNotFound } return strconv.Atoi(v) } @@ -2211,7 +2220,7 @@ func (ctx *context) PostValueIntDefault(name string, def int) int { func (ctx *context) PostValueInt64(name string) (int64, error) { v := ctx.PostValue(name) if v == "" { - return -1, errUnableToFindPostValue.Format(name) + return -1, ErrNotFound } return strconv.ParseInt(v, 10, 64) } @@ -2235,7 +2244,7 @@ func (ctx *context) PostValueInt64Default(name string, def int64) int64 { func (ctx *context) PostValueFloat64(name string) (float64, error) { v := ctx.PostValue(name) if v == "" { - return -1, errUnableToFindPostValue.Format(name) + return -1, ErrNotFound } return strconv.ParseFloat(v, 64) } @@ -2259,7 +2268,7 @@ func (ctx *context) PostValueFloat64Default(name string, def float64) float64 { func (ctx *context) PostValueBool(name string) (bool, error) { v := ctx.PostValue(name) if v == "" { - return false, errUnableToFindPostValue.Format(name) + return false, ErrNotFound } return strconv.ParseBool(v) @@ -2496,7 +2505,7 @@ func GetBody(r *http.Request, resetBody bool) ([]byte, error) { // However you are still free to read the `ctx.Request().Body io.Reader` manually. func (ctx *context) UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error { if ctx.request.Body == nil { - return errors.New("unmarshal: empty body") + return fmt.Errorf("unmarshal: empty body: %w", ErrNotFound) } rawData, err := ctx.GetBody() @@ -2696,6 +2705,19 @@ func (ctx *context) SetLastModified(modtime time.Time) { } } +// ErrPreconditionFailed may be returned from `Context` methods +// that has to perform one or more client side preconditions before the actual check, e.g. `CheckIfModifiedSince`. +// Usage: +// ok, err := context.CheckIfModifiedSince(modTime) +// if err != nil { +// if errors.Is(err, context.ErrPreconditionFailed) { +// [handle missing client conditions,such as not valid request method...] +// }else { +// [the error is probably a time parse error...] +// } +// } +var ErrPreconditionFailed = errors.New("precondition failed") + // CheckIfModifiedSince checks if the response is modified since the "modtime". // Note that it has nothing to do with server-side caching. // It does those checks by checking if the "If-Modified-Since" request header @@ -2707,20 +2729,20 @@ func (ctx *context) SetLastModified(modtime time.Time) { // it's not modified since, because it may return false but without even // had the chance to check the client-side (request) header due to some errors, // like the HTTP Method is not "GET" or "HEAD" or if the "modtime" is zero -// or if parsing time from the header failed. +// or if parsing time from the header failed. See `ErrPreconditionFailed` too. // // It's mostly used internally, e.g. `context#WriteWithExpiration`. func (ctx *context) CheckIfModifiedSince(modtime time.Time) (bool, error) { if method := ctx.Method(); method != http.MethodGet && method != http.MethodHead { - return false, errors.New("skip: method") + return false, fmt.Errorf("method: %w", ErrPreconditionFailed) } ims := ctx.GetHeader(IfModifiedSinceHeaderKey) if ims == "" || IsZeroTime(modtime) { - return false, errors.New("skip: zero time") + return false, fmt.Errorf("zero time: %w", ErrPreconditionFailed) } t, err := ParseTime(ctx, ims) if err != nil { - return false, errors.New("skip: " + err.Error()) + return false, err } // sub-second precision, so // use mtime < t+1s instead of mtime <= t to check for unmodified. @@ -2811,7 +2833,9 @@ func (ctx *context) ClientSupportsGzip() bool { return false } -var errClientDoesNotSupportGzip = errors.New("client doesn't support gzip compression") +// ErrGzipNotSupported may be returned from `WriteGzip` methods if +// the client does not support the "gzip" compression. +var ErrGzipNotSupported = errors.New("client does not support gzip compression") // WriteGzip accepts bytes, which are compressed to gzip format and sent to the client. // returns the number of bytes written and an error ( if the client doesn't support gzip compression) @@ -2820,7 +2844,7 @@ var errClientDoesNotSupportGzip = errors.New("client doesn't support gzip compre // to write more data many times without any troubles. func (ctx *context) WriteGzip(b []byte) (int, error) { if !ctx.ClientSupportsGzip() { - return 0, errClientDoesNotSupportGzip + return 0, ErrGzipNotSupported } return ctx.GzipResponseWriter().Write(b) @@ -2832,7 +2856,7 @@ func (ctx *context) TryWriteGzip(b []byte) (int, error) { n, err := ctx.WriteGzip(b) if err != nil { // check if the error came from gzip not allowed and not the writer itself - if _, ok := err.(errors.Error); ok { + if errors.Is(err, ErrGzipNotSupported) { // client didn't supported gzip, write them uncompressed: return ctx.writer.Write(b) } @@ -4074,8 +4098,6 @@ func (n *NegotiationAcceptBuilder) EncodingGzip() *NegotiationAcceptBuilder { // | Serve files | // +------------------------------------------------------------+ -var errServeContent = errors.New("while trying to serve content to the client. Trace %s") - // ServeContent serves content, headers are autoset // receives three parameters, it's low-level function, instead you can use .ServeFile(string,bool)/SendFile(string,string) // @@ -4103,7 +4125,7 @@ func (ctx *context) ServeContent(content io.ReadSeeker, filename string, modtime out = ctx.writer } _, err := io.Copy(out, content) - return errServeContent.With(err) ///TODO: add an int64 as return value for the content length written like other writers or let it as it's in order to keep the stable api? + return err ///TODO: add an int64 as return value for the content length written like other writers or let it as it's in order to keep the stable api? } // ServeFile serves a view file, to send a file ( zip for example) to the client you should use the SendFile(serverfilename,clientfilename) @@ -4386,8 +4408,14 @@ func (ctx *context) IsRecording() (*ResponseRecorder, bool) { return rr, ok } -// non-detailed error log for transacton unexpected panic -var errTransactionInterrupted = errors.New("transaction interrupted, recovery from panic:\n%s") +// ErrPanicRecovery may be returned from `Context` actions of a `Handler` +// which recovers from a manual panic. +// var ErrPanicRecovery = errors.New("recovery from panic") + +// ErrTransactionInterrupt can be used to manually force-complete a Context's transaction +// and log(warn) the wrapped error's message. +// Usage: `... return fmt.Errorf("my custom error message: %w", context.ErrTransactionInterrupt)`. +var ErrTransactionInterrupt = errors.New("transaction interrupted") // BeginTransaction starts a scoped transaction. // @@ -4415,7 +4443,7 @@ func (ctx *context) BeginTransaction(pipe func(t *Transaction)) { t := newTransaction(ctx) // it calls this *context, so the overriding with a new pool's New of context.Context wil not work here. defer func() { if err := recover(); err != nil { - ctx.Application().Logger().Warn(errTransactionInterrupted.Format(err).Error()) + ctx.Application().Logger().Warn(fmt.Errorf("recovery from panic: %w", ErrTransactionInterrupt)) // complete (again or not , doesn't matters) the scope without loud t.Complete(nil) // we continue as normal, no need to return here* diff --git a/context/response_writer.go b/context/response_writer.go index 3a6b89bc..8343f56b 100644 --- a/context/response_writer.go +++ b/context/response_writer.go @@ -2,13 +2,12 @@ package context import ( "bufio" + "errors" "fmt" "io" "net" "net/http" "sync" - - "github.com/kataras/iris/core/errors" ) // ResponseWriter interface is used by the context to serve an HTTP handler to diff --git a/core/errgroup/errgroup.go b/core/errgroup/errgroup.go new file mode 100644 index 00000000..a29eb325 --- /dev/null +++ b/core/errgroup/errgroup.go @@ -0,0 +1,356 @@ +package errgroup + +import ( + "errors" + "fmt" + "sort" + "strings" +) + +// Check reports whether the "err" is not nil. +// If it is a group then it returns true if that or its children contains any error. +func Check(err error) error { + if isNotNil(err) { + return err + } + + return nil +} + +// Walk loops through each of the errors of "err". +// If "err" is *Group then it fires the "visitor" for each of its errors, including children. +// if "err" is *Error then it fires the "visitor" with its type and wrapped error. +// Otherwise it fires the "visitor" once with typ of nil and err as "err". +func Walk(err error, visitor func(typ interface{}, err error)) error { + if err == nil { + return nil + } + + if group, ok := err.(*Group); ok { + list := group.getAllErrors() + for _, entry := range list { + if e, ok := entry.(*Error); ok { + visitor(e.Type, e.Err) // e.Unwrap() <-no. + } else { + visitor(nil, err) + } + } + } else if e, ok := err.(*Error); ok { + visitor(e.Type, e.Err) + } else { + visitor(nil, err) + } + + return err +} + +/* +func Errors(err error, conv bool) []error { + if err == nil { + return nil + } + + if group, ok := err.(*Group); ok { + list := group.getAllErrors() + if conv { + for i, entry := range list { + if _, ok := entry.(*Error); !ok { + list[i] = &Error{Err: entry, Type: group.Type} + } + } + } + + return list + } + + return []error{err} +} + +func Type(err error) interface{} { + if err == nil { + return nil + } + + if e, ok := err.(*Error); ok && e.Err != nil { + return e.Type + } + + return nil +} + +func Fill(parent *Group, errors []*Error) { + for _, err := range errors { + if err.Type == parent.Type { + parent.Add(err) + continue + } + + parent.Group(err.Type).Err(err) + } + return +} +*/ + +// Error implements the error interface. +// It is a special error type which keep the "Type" of the +// Group that it's created through Group's `Err` and `Errf` methods. +type Error struct { + Err error `json:"error" xml:"Error" yaml:"Error" toml:"Error" sql:"error"` + Type interface{} `json:"type" xml:"Type" yaml:"Type" toml:"Type" sql:"type"` +} + +// Error returns the error message of the "Err". +func (e *Error) Error() string { + return e.Err.Error() +} + +// Unwrap calls and returns the result of the "Err" Unwrap method or nil. +func (e *Error) Unwrap() error { + return errors.Unwrap(e.Err) +} + +// Is reports whether the "err" is an *Error. +func (e *Error) Is(err error) bool { + if err == nil { + return false + } + + ok := errors.Is(e.Err, err) + if !ok { + te, ok := err.(*Error) + if !ok { + return false + } + + return errors.Is(e.Err, te.Err) + } + + return ok +} + +// As reports whether the "target" can be used as &Error{target.Type: ?}. +func (e *Error) As(target interface{}) bool { + if target == nil { + return target == e + } + + ok := errors.As(e.Err, target) + if !ok { + te, ok := target.(*Error) + if !ok { + return false + } + + if te.Type != nil { + if te.Type != e.Type { + return false + } + } + + return errors.As(e.Err, &te.Err) + } + + return ok +} + +// Group is an error container of a specific Type and can have child containers per type too. +type Group struct { + parent *Group + // a list of children groups, used to get or create new group through Group method. + children map[interface{}]*Group + depth int + + Type interface{} + Errors []error // []*Error + + // if true then this Group's Error method will return the messages of the errors made by this Group's Group method. + // Defaults to true. + IncludeChildren bool // it clones. + // IncludeTypeText bool + index int // group index. +} + +// New returns a new empty Group. +func New(typ interface{}) *Group { + return &Group{ + Type: typ, + IncludeChildren: true, + } +} + +const delim = "\n" + +func (g *Group) Error() (s string) { + if len(g.Errors) > 0 { + msgs := make([]string, len(g.Errors), len(g.Errors)) + for i, err := range g.Errors { + msgs[i] = err.Error() + } + + s = strings.Join(msgs, delim) + } + + if g.IncludeChildren && len(g.children) > 0 { + // return with order of definition. + groups := g.getAllChildren() + sortGroups(groups) + + for _, ge := range groups { + for _, childErr := range ge.Errors { + s += childErr.Error() + delim + } + } + + if s != "" { + return s[:len(s)-1] + } + } + + return +} + +func (g *Group) getAllErrors() []error { + list := g.Errors[:] + + if len(g.children) > 0 { + // return with order of definition. + groups := g.getAllChildren() + sortGroups(groups) + + for _, ge := range groups { + list = append(list, ge.Errors...) + } + } + + return list +} + +func (g *Group) getAllChildren() []*Group { + if len(g.children) == 0 { + return nil + } + + var groups []*Group + for _, child := range g.children { + groups = append(groups, append([]*Group{child}, child.getAllChildren()...)...) + } + + return groups +} + +// Unwrap implements the dynamic std errors interface and it returns the parent Group. +func (g *Group) Unwrap() error { + return g.parent +} + +// Group creates a new group of "typ" type, if does not exist, and returns it. +func (g *Group) Group(typ interface{}) *Group { + if g.children == nil { + g.children = make(map[interface{}]*Group) + } else { + for _, child := range g.children { + if child.Type == typ { + return child + } + } + } + + child := &Group{ + Type: typ, + parent: g, + depth: g.depth + 1, + IncludeChildren: g.IncludeChildren, + index: g.index + 1 + len(g.children), + } + + g.children[typ] = child + + return child +} + +// Add adds an error to the group. +func (g *Group) Add(err error) { + if err == nil { + return + } + + g.Errors = append(g.Errors, err) +} + +// Addf adds an error to the group like `fmt.Errorf` and returns it. +func (g *Group) Addf(format string, args ...interface{}) error { + err := fmt.Errorf(format, args...) + g.Add(err) + return err +} + +// Err adds an error the the group, it transforms it to an Error type if necessary and returns it. +func (g *Group) Err(err error) error { + if err == nil { + return nil + } + + e, ok := err.(*Error) + if !ok { + if ge, ok := err.(*Group); ok { + if g.children == nil { + g.children = make(map[interface{}]*Group) + } + + g.children[ge.Type] = ge + return ge + } + + e = &Error{err, 0} + } + e.Type = g.Type + + g.Add(e) + return e +} + +// Errf adds an error like `fmt.Errorf` and returns it. +func (g *Group) Errf(format string, args ...interface{}) error { + return g.Err(fmt.Errorf(format, args...)) +} + +func sortGroups(groups []*Group) { + sort.Slice(groups, func(i, j int) bool { + return groups[i].index < groups[j].index + }) +} + +func tryGetTypeText(typ interface{}) string { + if typ == nil { + return "" + } + + switch v := typ.(type) { + case string: + return v + case fmt.Stringer: + return v.String() + default: + return "" + } +} + +func isNotNil(err error) bool { + if g, ok := err.(*Group); ok { + if len(g.Errors) > 0 { + return true + } + + if len(g.children) > 0 { + for _, child := range g.children { + if isNotNil(child) { + return true + } + } + } + + return false + } + + return err != nil +} diff --git a/core/errgroup/errgroup_test.go b/core/errgroup/errgroup_test.go new file mode 100644 index 00000000..0296f2fd --- /dev/null +++ b/core/errgroup/errgroup_test.go @@ -0,0 +1,173 @@ +package errgroup + +import ( + "errors" + "fmt" + "strings" + "testing" +) + +func TestErrorError(t *testing.T) { + testErr := errors.New("error") + err := &Error{Err: testErr} + if expected, got := testErr.Error(), err.Error(); expected != got { + t.Fatalf("expected %s but got %s", expected, got) + } +} + +func TestErrorUnwrap(t *testing.T) { + wrapped := errors.New("unwrap") + + err := &Error{Err: fmt.Errorf("this wraps:%w", wrapped)} + if expected, got := wrapped, errors.Unwrap(err); expected != got { + t.Fatalf("expected %#+v but got %#+v", expected, got) + } + +} +func TestErrorIs(t *testing.T) { + testErr := errors.New("is") + err := &Error{Err: fmt.Errorf("this is: %w", testErr)} + if expected, got := true, errors.Is(err, testErr); expected != got { + t.Fatalf("expected %v but got %v", expected, got) + } +} + +func TestErrorAs(t *testing.T) { + testErr := errors.New("as") + err := &Error{Err: testErr} + if expected, got := true, errors.As(err, &testErr); expected != got { + t.Fatalf("[testErr as err] expected %v but got %v", expected, got) + } + if expected, got := true, errors.As(testErr, &err); expected != got { + t.Fatalf("[err as testErr] expected %v but got %v", expected, got) + } +} + +func TestGroupError(t *testing.T) { + g := New(0) + tests := []string{"error 1", "error 2", "error 3"} + for _, tt := range tests { + g.Add(errors.New(tt)) + } + + if expected, got := strings.Join(tests, "\n"), g.Error(); expected != got { + t.Fatalf("expected '%s' but got '%s'", expected, got) + } +} + +func TestGroup(t *testing.T) { + const ( + apiErrorsType = iota + 1 + childAPIErrorsType + childAPIErrors2Type = "string type 1" + childAPIErrors2Type1 = "string type 2" + + apiErrorsText = "apiErrors error 1" + childAPIErrorsText = "apiErrors:child error 1" + childAPIErrors2Text = "apiErrors:child2 error 1" + childAPIErrors2Text1 = "apiErrors:child2_1 error 1" + ) + + g := New(nil) + apiErrorsGroup := g.Group(apiErrorsType) + apiErrorsGroup.Errf(apiErrorsText) + + childAPIErrorsGroup := apiErrorsGroup.Group(childAPIErrorsType) + childAPIErrorsGroup.Addf(childAPIErrorsText) + childAPIErrorsGroup2 := apiErrorsGroup.Group(childAPIErrors2Type) + childAPIErrorsGroup2.Addf(childAPIErrors2Text) + childAPIErrorsGroup2Group1 := childAPIErrorsGroup2.Group(childAPIErrors2Type1) + childAPIErrorsGroup2Group1.Addf(childAPIErrors2Text1) + + if apiErrorsGroup.Type != apiErrorsType { + t.Fatal("invalid type") + } + + if childAPIErrorsGroup.Type != childAPIErrorsType { + t.Fatal("invalid type") + } + + if childAPIErrorsGroup2.Type != childAPIErrors2Type { + t.Fatal("invalid type") + } + + if childAPIErrorsGroup2Group1.Type != childAPIErrors2Type1 { + t.Fatal("invalid type") + } + + if expected, got := 2, len(apiErrorsGroup.children); expected != got { + t.Fatalf("expected %d but got %d", expected, got) + } + + if expected, got := 0, len(childAPIErrorsGroup.children); expected != got { + t.Fatalf("expected %d but got %d", expected, got) + } + + if expected, got := 1, len(childAPIErrorsGroup2.children); expected != got { + t.Fatalf("expected %d but got %d", expected, got) + } + + if expected, got := 0, len(childAPIErrorsGroup2Group1.children); expected != got { + t.Fatalf("expected %d but got %d", expected, got) + } + + if expected, got := 1, apiErrorsGroup.index; expected != got { + t.Fatalf("expected index %d but got %d", expected, got) + } + + if expected, got := 2, childAPIErrorsGroup.index; expected != got { + t.Fatalf("expected index %d but got %d", expected, got) + } + + if expected, got := 3, childAPIErrorsGroup2.index; expected != got { + t.Fatalf("expected index %d but got %d", expected, got) + } + + if expected, got := 4, childAPIErrorsGroup2Group1.index; expected != got { + t.Fatalf("expected index %d but got %d", expected, got) + } + + t.Run("Error", func(t *testing.T) { + if expected, got := + strings.Join([]string{apiErrorsText, childAPIErrorsText, childAPIErrors2Text, childAPIErrors2Text1}, delim), g.Error(); expected != got { + t.Fatalf("expected '%s' but got '%s'", expected, got) + } + }) + + t.Run("Walk", func(t *testing.T) { + expectedEntries := 4 + Walk(g, func(typ interface{}, err error) { + g.IncludeChildren = false + childAPIErrorsGroup.IncludeChildren = false + childAPIErrorsGroup2.IncludeChildren = false + childAPIErrorsGroup2Group1.IncludeChildren = false + + expectedEntries-- + var expected string + + switch typ { + case apiErrorsType: + expected = apiErrorsText + case childAPIErrorsType: + expected = childAPIErrorsText + case childAPIErrors2Type: + expected = childAPIErrors2Text + case childAPIErrors2Type1: + expected = childAPIErrors2Text1 + } + + if got := err.Error(); expected != got { + t.Fatalf("[%v] expected '%s' but got '%s'", typ, expected, got) + } + }) + + if expectedEntries != 0 { + t.Fatalf("not valid number of errors [...%d]", expectedEntries) + } + + g.IncludeChildren = true + childAPIErrorsGroup.IncludeChildren = true + childAPIErrorsGroup2.IncludeChildren = true + childAPIErrorsGroup2Group1.IncludeChildren = true + }) +} diff --git a/core/errors/errors.go b/core/errors/errors.go deleted file mode 100644 index 70696144..00000000 --- a/core/errors/errors.go +++ /dev/null @@ -1,166 +0,0 @@ -package errors - -import ( - "fmt" - "runtime" - "strings" - - "github.com/iris-contrib/go.uuid" -) - -// Prefix the error prefix, applies to each error's message. -var Prefix = "" - -// Error holds the error message, this message never really changes -type Error struct { - // ID returns the unique id of the error, it's needed - // when we want to check if a specific error returned - // but the `Error() string` value is not the same because the error may be dynamic - // by a `Format` call. - ID string `json:"id"` - // The message of the error. - Message string `json:"message"` - // Apennded is true whenever it's a child error. - Appended bool `json:"appended"` - // Stack returns the list of the errors that are shown at `Error() string`. - Stack []Error `json:"stack"` // filled on AppendX. -} - -// New creates and returns an Error with a pre-defined user output message -// all methods below that doesn't accept a pointer receiver because actually they are not changing the original message -func New(errMsg string) Error { - uidv4, _ := uuid.NewV4() // skip error. - return Error{ - ID: uidv4.String(), - Message: Prefix + errMsg, - } -} - -// NewFromErr same as `New` but pointer for nil checks without the need of the `Return()` function. -func NewFromErr(err error) *Error { - if err == nil { - return nil - } - - errp := New(err.Error()) - return &errp -} - -// Equal returns true if "e" and "to" are matched, by their IDs if it's a core/errors type otherwise it tries to match their error messages. -// It will always returns true if the "to" is a children of "e" -// or the error messages are exactly the same, otherwise false. -func (e Error) Equal(to error) bool { - if e2, ok := to.(Error); ok { - return e.ID == e2.ID - } else if e2, ok := to.(*Error); ok { - return e.ID == e2.ID - } - - return e.Error() == to.Error() -} - -// Empty returns true if the "e" Error has no message on its stack. -func (e Error) Empty() bool { - return e.Message == "" -} - -// NotEmpty returns true if the "e" Error has got a non-empty message on its stack. -func (e Error) NotEmpty() bool { - return !e.Empty() -} - -// String returns the error message -func (e Error) String() string { - return e.Message -} - -// Error returns the message of the actual error -// implements the error -func (e Error) Error() string { - return e.String() -} - -// Format returns a formatted new error based on the arguments -// it does NOT change the original error's message -func (e Error) Format(a ...interface{}) Error { - e.Message = fmt.Sprintf(e.Message, a...) - return e -} - -func omitNewLine(message string) string { - if strings.HasSuffix(message, "\n") { - return message[0 : len(message)-2] - } else if strings.HasSuffix(message, "\\n") { - return message[0 : len(message)-3] - } - return message -} - -// AppendInline appends an error to the stack. -// It doesn't try to append a new line if needed. -func (e Error) AppendInline(format string, a ...interface{}) Error { - msg := fmt.Sprintf(format, a...) - e.Message += msg - e.Appended = true - e.Stack = append(e.Stack, New(omitNewLine(msg))) - return e -} - -// Append adds a message to the predefined error message and returns a new error -// it does NOT change the original error's message -func (e Error) Append(format string, a ...interface{}) Error { - // if new line is false then append this error but first - // we need to add a new line to the first, if it was true then it has the newline already. - if e.Message != "" { - e.Message += "\n" - } - - return e.AppendInline(format, a...) -} - -// AppendErr adds an error's message to the predefined error message and returns a new error. -// it does NOT change the original error's message -func (e Error) AppendErr(err error) Error { - return e.Append(err.Error()) -} - -// HasStack returns true if the Error instance is created using Append/AppendInline/AppendErr funcs. -func (e Error) HasStack() bool { - return len(e.Stack) > 0 -} - -// With does the same thing as Format but it receives an error type which if it's nil it returns a nil error. -func (e Error) With(err error) error { - if err == nil { - return nil - } - - return e.Format(err.Error()) -} - -// Ignore will ignore the "err" and return nil. -func (e Error) Ignore(err error) error { - if err == nil { - return e - } - if e.Error() == err.Error() { - return nil - } - return e -} - -// Panic output the message and after panics. -func (e Error) Panic() { - _, fn, line, _ := runtime.Caller(1) - errMsg := e.Message - errMsg += "\nCaller was: " + fmt.Sprintf("%s:%d", fn, line) - panic(errMsg) -} - -// Panicf output the formatted message and after panics. -func (e Error) Panicf(args ...interface{}) { - _, fn, line, _ := runtime.Caller(1) - errMsg := e.Format(args...).Error() - errMsg += "\nCaller was: " + fmt.Sprintf("%s:%d", fn, line) - panic(errMsg) -} diff --git a/core/errors/errors_test.go b/core/errors/errors_test.go deleted file mode 100644 index 937b6403..00000000 --- a/core/errors/errors_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// black-box testing -package errors_test - -import ( - "fmt" - "testing" - - "github.com/kataras/iris/core/errors" -) - -var errMessage = "User with mail: %s already exists" -var errUserAlreadyExists = errors.New(errMessage) -var userMail = "user1@mail.go" -var expectedUserAlreadyExists = "User with mail: user1@mail.go already exists" - -func ExampleError() { - fmt.Print(errUserAlreadyExists.Format(userMail).Append("Please change your mail addr")) - - // Output: - // User with mail: user1@mail.go already exists - // Please change your mail addr -} - -func do(method string, testErr errors.Error, expectingMsg string, t *testing.T) { - formattedErr := func() error { - return testErr.Format(userMail) - }() - - if formattedErr.Error() != expectingMsg { - t.Fatalf("error %s failed, expected:\n%s got:\n%s", method, expectingMsg, formattedErr.Error()) - } -} - -func TestFormat(t *testing.T) { - expected := errors.Prefix + expectedUserAlreadyExists - do("Format Test", errUserAlreadyExists, expected, t) -} - -func TestAppendErr(t *testing.T) { - errChangeMailMsg := "Please change your mail addr" - errChangeMail := fmt.Errorf(errChangeMailMsg) // test go standard error - errAppended := errUserAlreadyExists.AppendErr(errChangeMail) - expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + "\n" + errChangeMailMsg - - do("Append Test Standard error type", errAppended, expectedErrorMessage, t) -} - -func TestAppendError(t *testing.T) { - errors.Prefix = "error: " - - errChangeMailMsg := "Please change your mail addr" - errChangeMail := errors.New(errChangeMailMsg) - - errAppended := errUserAlreadyExists.AppendErr(errChangeMail) - expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + "\n" + errChangeMail.Error() - - do("Append Test Error type", errAppended, expectedErrorMessage, t) -} - -func TestAppend(t *testing.T) { - errors.Prefix = "error: " - - errChangeMailMsg := "Please change your mail addr" - expectedErrorMessage := errUserAlreadyExists.Format(userMail).Error() + "\n" + errChangeMailMsg - errAppended := errUserAlreadyExists.Append(errChangeMailMsg) - do("Append Test string Message", errAppended, expectedErrorMessage, t) -} - -func TestNewLine(t *testing.T) { - err := errors.New(errMessage) - expected := errors.Prefix + expectedUserAlreadyExists - do("NewLine Test", err, expected, t) -} - -func TestPrefix(t *testing.T) { - errors.Prefix = "MyPrefix: " - - errUpdatedPrefix := errors.New(errMessage) - expected := errors.Prefix + expectedUserAlreadyExists - do("Prefix Test with "+errors.Prefix, errUpdatedPrefix, expected, t) -} diff --git a/core/errors/reporter.go b/core/errors/reporter.go deleted file mode 100644 index b501586c..00000000 --- a/core/errors/reporter.go +++ /dev/null @@ -1,154 +0,0 @@ -package errors - -import ( - "sync" -) - -// StackError contains the Stack method. -type StackError interface { - Stack() []Error - Error() string -} - -// PrintAndReturnErrors prints the "err" to the given "printer", -// printer will be called multiple times if the "err" is a StackError, where it contains more than one error. -func PrintAndReturnErrors(err error, printer func(string, ...interface{})) error { - if err == nil || err.Error() == "" { - return nil - } - - if stackErr, ok := err.(StackError); ok { - if len(stackErr.Stack()) == 0 { - return nil - } - - stack := stackErr.Stack() - - for _, e := range stack { - if e.HasStack() { - for _, es := range e.Stack { - printer("%v", es) - } - continue - } - printer("%v", e) - } - - return stackErr - } - - printer("%v", err) - return err -} - -// Reporter is a helper structure which can -// stack errors and prints them to a printer of func(string). -type Reporter struct { - mu sync.Mutex - wrapper Error -} - -// NewReporter returns a new empty error reporter. -func NewReporter() *Reporter { - return &Reporter{wrapper: New("")} -} - -// AddErr adds an error to the error stack. -// if "err" is a StackError then -// each of these errors will be printed as individual. -// -// Returns true if this "err" is not nil and it's added to the reporter's stack. -func (r *Reporter) AddErr(err error) bool { - if err == nil { - return false - } - - if stackErr, ok := err.(StackError); ok { - r.addStack("", stackErr.Stack()) - } else { - r.mu.Lock() - r.wrapper = r.wrapper.AppendErr(err) - r.mu.Unlock() - } - - return true -} - -// Add adds a formatted message as an error to the error stack. -// -// Returns true if this "err" is not nil and it's added to the reporter's stack. -func (r *Reporter) Add(format string, a ...interface{}) bool { - if format == "" && len(a) == 0 { - return false - } - - // usually used as: "module: %v", err so - // check if the first argument is error and if that error is empty then don't add it. - if len(a) > 0 { - f := a[0] - if e, ok := f.(interface { - Error() string - }); ok { - if e.Error() == "" { - return false - } - } - } - - r.mu.Lock() - r.wrapper = r.wrapper.Append(format, a...) - r.mu.Unlock() - return true -} - -// Describe same as `Add` but if "err" is nil then it does nothing. -func (r *Reporter) Describe(format string, err error) { - if err == nil { - return - } - if stackErr, ok := err.(StackError); ok { - r.addStack(format, stackErr.Stack()) - return - } - - r.Add(format, err) -} - -// PrintStack prints all the errors to the given "printer". -// Returns itself in order to be used as printer and return the full error in the same time. -func (r *Reporter) PrintStack(printer func(string, ...interface{})) error { - return PrintAndReturnErrors(r, printer) -} - -// Stack returns the list of the errors in the stack. -func (r *Reporter) Stack() []Error { - return r.wrapper.Stack -} - -func (r *Reporter) addStack(format string, stack []Error) { - for _, e := range stack { - if e.Error() == "" { - continue - } - r.mu.Lock() - if format != "" { - e = New(format).Format(e) - } - r.wrapper = r.wrapper.AppendErr(e) - r.mu.Unlock() - } -} - -// Error implements the error, returns the full error string. -func (r *Reporter) Error() string { - return r.wrapper.Error() -} - -// Return returns nil if the error is empty, otherwise returns the full error. -func (r *Reporter) Return() error { - if r.Error() == "" { - return nil - } - - return r -} diff --git a/core/errors/reporter_test.go b/core/errors/reporter_test.go deleted file mode 100644 index c72eabb4..00000000 --- a/core/errors/reporter_test.go +++ /dev/null @@ -1,26 +0,0 @@ -// black-box testing -package errors_test - -import ( - "testing" - - "github.com/kataras/iris/core/errors" -) - -func TestReporterAdd(t *testing.T) { - errors.Prefix = "" - - r := errors.NewReporter() - - tests := []string{"err1", "err3", "err4\nerr5"} - for _, tt := range tests { - r.Add(tt) - } - - for i, e := range r.Stack() { - tt := tests[i] - if expected, got := tt, e.Error(); expected != got { - t.Fatalf("[%d] expected %s but got %s", i, expected, got) - } - } -} diff --git a/core/handlerconv/from_std.go b/core/handlerconv/from_std.go index 57d6de9a..45427c68 100644 --- a/core/handlerconv/from_std.go +++ b/core/handlerconv/from_std.go @@ -1,20 +1,12 @@ package handlerconv import ( + "fmt" "net/http" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" ) -var errHandler = errors.New(` - Passed argument is not a func(context.Context) neither one of these types: - - http.Handler - - func(w http.ResponseWriter, r *http.Request) - - func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) - --------------------------------------------------------------------- - It seems to be a %T points to: %v`) - // FromStd converts native http.Handler & http.HandlerFunc to context.Handler. // // Supported form types: @@ -63,7 +55,13 @@ func FromStd(handler interface{}) context.Handler { // // No valid handler passed // - panic(errHandler.Format(handler, handler)) + panic(fmt.Errorf(` + Passed argument is not a func(context.Context) neither one of these types: + - http.Handler + - func(w http.ResponseWriter, r *http.Request) + - func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) + --------------------------------------------------------------------- + It seems to be a %T points to: %v`, handler, handler)) } } diff --git a/core/host/supervisor.go b/core/host/supervisor.go index 14b235bd..bab3eb66 100644 --- a/core/host/supervisor.go +++ b/core/host/supervisor.go @@ -3,6 +3,7 @@ package host import ( "context" "crypto/tls" + "errors" "net" "net/http" "strings" @@ -12,7 +13,6 @@ import ( "golang.org/x/crypto/acme/autocert" - "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/netutil" ) @@ -260,7 +260,7 @@ func (su *Supervisor) ListenAndServeTLS(certFile string, keyFile string) error { } if su.Server.TLSConfig == nil { - return errors.New("certFile or keyFile missing") + return errors.New("empty certFile or keyFile and Server.TLSConfig") } return su.supervise(func() error { return su.Server.ListenAndServeTLS("", "") }) diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 30a701d0..bc01fb0a 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -11,8 +11,6 @@ import ( "reflect" "strconv" "strings" - - "github.com/kataras/iris/core/errors" ) type ( @@ -102,14 +100,60 @@ func (e Entry) StringTrim() string { return strings.TrimSpace(e.String()) } -var errFindParse = errors.New("unable to find the %s with key: %s") +// ErrEntryNotFound may be returned from memstore methods if +// a key (with a certain kind) was not found. +// Usage: +// +// var e *ErrEntryNotFound +// errors.As(err, &e) +// To check for specific key error: +// errors.As(err, &ErrEntryNotFound{Key: "key"}) +// To check for specific key-kind error: +// errors.As(err, &ErrEntryNotFound{Key: "key", Kind: reflect.Int}) +type ErrEntryNotFound struct { + Key string // the entry's key. + Kind reflect.Kind // i.e bool, int, string... +} + +func (e *ErrEntryNotFound) Error() string { + return fmt.Sprintf("not found: %s as %s", e.Key, e.Kind.String()) +} + +// As can be used to manually check if the error came from the memstore +// is a not found entry, the key must much in order to return true. +// Usage: +// errors.As(err, &ErrEntryNotFound{Key: "key", Kind: reflect.Int}) +// +// Do NOT use this method directly, prefer` errors.As` method as explained above. +// +// Implements: go/src/errors/wrap.go#84 +func (e *ErrEntryNotFound) As(target interface{}) bool { + v, ok := target.(*ErrEntryNotFound) + if !ok { + return false + } + + if v.Key != "" && v.Key != e.Key { + return false + } + + if v.Kind != reflect.Invalid && v.Kind != e.Kind { + return false + } + + return true +} + +func (e Entry) notFound(kind reflect.Kind) *ErrEntryNotFound { + return &ErrEntryNotFound{Key: e.Key, Kind: kind} +} // IntDefault returns the entry's value as int. // If not found returns "def" and a non-nil error. func (e Entry) IntDefault(def int) (int, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("int", e.Key) + return def, e.notFound(reflect.Int) } switch vv := v.(type) { @@ -141,7 +185,7 @@ func (e Entry) IntDefault(def int) (int, error) { return int(vv), nil } - return def, errFindParse.Format("int", e.Key) + return def, e.notFound(reflect.Int) } // Int8Default returns the entry's value as int8. @@ -149,7 +193,7 @@ func (e Entry) IntDefault(def int) (int, error) { func (e Entry) Int8Default(def int8) (int8, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("int8", e.Key) + return def, e.notFound(reflect.Int8) } switch vv := v.(type) { @@ -171,7 +215,7 @@ func (e Entry) Int8Default(def int8) (int8, error) { return int8(vv), nil } - return def, errFindParse.Format("int8", e.Key) + return def, e.notFound(reflect.Int8) } // Int16Default returns the entry's value as int16. @@ -179,7 +223,7 @@ func (e Entry) Int8Default(def int8) (int8, error) { func (e Entry) Int16Default(def int16) (int16, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("int16", e.Key) + return def, e.notFound(reflect.Int16) } switch vv := v.(type) { @@ -201,7 +245,7 @@ func (e Entry) Int16Default(def int16) (int16, error) { return int16(vv), nil } - return def, errFindParse.Format("int16", e.Key) + return def, e.notFound(reflect.Int16) } // Int32Default returns the entry's value as int32. @@ -209,7 +253,7 @@ func (e Entry) Int16Default(def int16) (int16, error) { func (e Entry) Int32Default(def int32) (int32, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("int32", e.Key) + return def, e.notFound(reflect.Int32) } switch vv := v.(type) { @@ -231,7 +275,7 @@ func (e Entry) Int32Default(def int32) (int32, error) { return int32(vv), nil } - return def, errFindParse.Format("int32", e.Key) + return def, e.notFound(reflect.Int32) } // Int64Default returns the entry's value as int64. @@ -239,7 +283,7 @@ func (e Entry) Int32Default(def int32) (int32, error) { func (e Entry) Int64Default(def int64) (int64, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("int64", e.Key) + return def, e.notFound(reflect.Int64) } switch vv := v.(type) { @@ -255,7 +299,7 @@ func (e Entry) Int64Default(def int64) (int64, error) { return int64(vv), nil } - return def, errFindParse.Format("int64", e.Key) + return def, e.notFound(reflect.Int64) } // UintDefault returns the entry's value as uint. @@ -263,7 +307,7 @@ func (e Entry) Int64Default(def int64) (int64, error) { func (e Entry) UintDefault(def uint) (uint, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("uint", e.Key) + return def, e.notFound(reflect.Uint) } x64 := strconv.IntSize == 64 @@ -279,7 +323,7 @@ func (e Entry) UintDefault(def uint) (uint, error) { return def, err } if val > uint64(maxValue) { - return def, errFindParse.Format("uint", e.Key) + return def, e.notFound(reflect.Uint) } return uint(val), nil case uint: @@ -292,17 +336,17 @@ func (e Entry) UintDefault(def uint) (uint, error) { return uint(vv), nil case uint64: if vv > uint64(maxValue) { - return def, errFindParse.Format("uint", e.Key) + return def, e.notFound(reflect.Uint) } return uint(vv), nil case int: if vv < 0 || vv > int(maxValue) { - return def, errFindParse.Format("uint", e.Key) + return def, e.notFound(reflect.Uint) } return uint(vv), nil } - return def, errFindParse.Format("uint", e.Key) + return def, e.notFound(reflect.Uint) } // Uint8Default returns the entry's value as uint8. @@ -310,7 +354,7 @@ func (e Entry) UintDefault(def uint) (uint, error) { func (e Entry) Uint8Default(def uint8) (uint8, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("uint8", e.Key) + return def, e.notFound(reflect.Uint8) } switch vv := v.(type) { @@ -320,39 +364,39 @@ func (e Entry) Uint8Default(def uint8) (uint8, error) { return def, err } if val > math.MaxUint8 { - return def, errFindParse.Format("uint8", e.Key) + return def, e.notFound(reflect.Uint8) } return uint8(val), nil case uint: if vv > math.MaxUint8 { - return def, errFindParse.Format("uint8", e.Key) + return def, e.notFound(reflect.Uint8) } return uint8(vv), nil case uint8: return vv, nil case uint16: if vv > math.MaxUint8 { - return def, errFindParse.Format("uint8", e.Key) + return def, e.notFound(reflect.Uint8) } return uint8(vv), nil case uint32: if vv > math.MaxUint8 { - return def, errFindParse.Format("uint8", e.Key) + return def, e.notFound(reflect.Uint8) } return uint8(vv), nil case uint64: if vv > math.MaxUint8 { - return def, errFindParse.Format("uint8", e.Key) + return def, e.notFound(reflect.Uint8) } return uint8(vv), nil case int: if vv < 0 || vv > math.MaxUint8 { - return def, errFindParse.Format("uint8", e.Key) + return def, e.notFound(reflect.Uint8) } return uint8(vv), nil } - return def, errFindParse.Format("uint8", e.Key) + return def, e.notFound(reflect.Uint8) } // Uint16Default returns the entry's value as uint16. @@ -360,7 +404,7 @@ func (e Entry) Uint8Default(def uint8) (uint8, error) { func (e Entry) Uint16Default(def uint16) (uint16, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("uint16", e.Key) + return def, e.notFound(reflect.Uint16) } switch vv := v.(type) { @@ -370,12 +414,12 @@ func (e Entry) Uint16Default(def uint16) (uint16, error) { return def, err } if val > math.MaxUint16 { - return def, errFindParse.Format("uint16", e.Key) + return def, e.notFound(reflect.Uint16) } return uint16(val), nil case uint: if vv > math.MaxUint16 { - return def, errFindParse.Format("uint16", e.Key) + return def, e.notFound(reflect.Uint16) } return uint16(vv), nil case uint8: @@ -384,22 +428,22 @@ func (e Entry) Uint16Default(def uint16) (uint16, error) { return vv, nil case uint32: if vv > math.MaxUint16 { - return def, errFindParse.Format("uint16", e.Key) + return def, e.notFound(reflect.Uint16) } return uint16(vv), nil case uint64: if vv > math.MaxUint16 { - return def, errFindParse.Format("uint16", e.Key) + return def, e.notFound(reflect.Uint16) } return uint16(vv), nil case int: if vv < 0 || vv > math.MaxUint16 { - return def, errFindParse.Format("uint16", e.Key) + return def, e.notFound(reflect.Uint16) } return uint16(vv), nil } - return def, errFindParse.Format("uint16", e.Key) + return def, e.notFound(reflect.Uint16) } // Uint32Default returns the entry's value as uint32. @@ -407,7 +451,7 @@ func (e Entry) Uint16Default(def uint16) (uint16, error) { func (e Entry) Uint32Default(def uint32) (uint32, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("uint32", e.Key) + return def, e.notFound(reflect.Uint32) } switch vv := v.(type) { @@ -417,12 +461,12 @@ func (e Entry) Uint32Default(def uint32) (uint32, error) { return def, err } if val > math.MaxUint32 { - return def, errFindParse.Format("uint32", e.Key) + return def, e.notFound(reflect.Uint32) } return uint32(val), nil case uint: if vv > math.MaxUint32 { - return def, errFindParse.Format("uint32", e.Key) + return def, e.notFound(reflect.Uint32) } return uint32(vv), nil case uint8: @@ -433,19 +477,19 @@ func (e Entry) Uint32Default(def uint32) (uint32, error) { return vv, nil case uint64: if vv > math.MaxUint32 { - return def, errFindParse.Format("uint32", e.Key) + return def, e.notFound(reflect.Uint32) } return uint32(vv), nil case int32: return uint32(vv), nil case int64: if vv < 0 || vv > math.MaxUint32 { - return def, errFindParse.Format("uint32", e.Key) + return def, e.notFound(reflect.Uint32) } return uint32(vv), nil } - return def, errFindParse.Format("uint32", e.Key) + return def, e.notFound(reflect.Uint32) } // Uint64Default returns the entry's value as uint64. @@ -453,7 +497,7 @@ func (e Entry) Uint32Default(def uint32) (uint32, error) { func (e Entry) Uint64Default(def uint64) (uint64, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("uint64", e.Key) + return def, e.notFound(reflect.Uint64) } switch vv := v.(type) { @@ -463,7 +507,7 @@ func (e Entry) Uint64Default(def uint64) (uint64, error) { return def, err } if val > math.MaxUint64 { - return def, errFindParse.Format("uint64", e.Key) + return def, e.notFound(reflect.Uint64) } return uint64(val), nil case uint8: @@ -480,7 +524,7 @@ func (e Entry) Uint64Default(def uint64) (uint64, error) { return uint64(vv), nil } - return def, errFindParse.Format("uint64", e.Key) + return def, e.notFound(reflect.Uint64) } // Float32Default returns the entry's value as float32. @@ -488,7 +532,7 @@ func (e Entry) Uint64Default(def uint64) (uint64, error) { func (e Entry) Float32Default(key string, def float32) (float32, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("float32", e.Key) + return def, e.notFound(reflect.Float32) } switch vv := v.(type) { @@ -498,21 +542,21 @@ func (e Entry) Float32Default(key string, def float32) (float32, error) { return def, err } if val > math.MaxFloat32 { - return def, errFindParse.Format("float32", e.Key) + return def, e.notFound(reflect.Float32) } return float32(val), nil case float32: return vv, nil case float64: if vv > math.MaxFloat32 { - return def, errFindParse.Format("float32", e.Key) + return def, e.notFound(reflect.Float32) } return float32(vv), nil case int: return float32(vv), nil } - return def, errFindParse.Format("float32", e.Key) + return def, e.notFound(reflect.Float32) } // Float64Default returns the entry's value as float64. @@ -520,7 +564,7 @@ func (e Entry) Float32Default(key string, def float32) (float32, error) { func (e Entry) Float64Default(def float64) (float64, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("float64", e.Key) + return def, e.notFound(reflect.Float64) } switch vv := v.(type) { @@ -544,7 +588,7 @@ func (e Entry) Float64Default(def float64) (float64, error) { return float64(vv), nil } - return def, errFindParse.Format("float64", e.Key) + return def, e.notFound(reflect.Float64) } // BoolDefault returns the user's value as bool. @@ -556,7 +600,7 @@ func (e Entry) Float64Default(def float64) (float64, error) { func (e Entry) BoolDefault(def bool) (bool, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("bool", e.Key) + return def, e.notFound(reflect.Bool) } switch vv := v.(type) { @@ -575,7 +619,7 @@ func (e Entry) BoolDefault(def bool) (bool, error) { return false, nil } - return def, errFindParse.Format("bool", e.Key) + return def, e.notFound(reflect.Bool) } // Value returns the value of the entry, @@ -765,7 +809,7 @@ func (r *Store) GetStringTrim(name string) string { func (r *Store) GetInt(key string) (int, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("int", key) + return 0, v.notFound(reflect.Int) } return v.IntDefault(-1) } @@ -785,7 +829,7 @@ func (r *Store) GetIntDefault(key string, def int) int { func (r *Store) GetInt8(key string) (int8, error) { v, ok := r.GetEntry(key) if !ok { - return -1, errFindParse.Format("int8", key) + return -1, v.notFound(reflect.Int8) } return v.Int8Default(-1) } @@ -805,7 +849,7 @@ func (r *Store) GetInt8Default(key string, def int8) int8 { func (r *Store) GetInt16(key string) (int16, error) { v, ok := r.GetEntry(key) if !ok { - return -1, errFindParse.Format("int16", key) + return -1, v.notFound(reflect.Int16) } return v.Int16Default(-1) } @@ -825,7 +869,7 @@ func (r *Store) GetInt16Default(key string, def int16) int16 { func (r *Store) GetInt32(key string) (int32, error) { v, ok := r.GetEntry(key) if !ok { - return -1, errFindParse.Format("int32", key) + return -1, v.notFound(reflect.Int32) } return v.Int32Default(-1) } @@ -845,7 +889,7 @@ func (r *Store) GetInt32Default(key string, def int32) int32 { func (r *Store) GetInt64(key string) (int64, error) { v, ok := r.GetEntry(key) if !ok { - return -1, errFindParse.Format("int64", key) + return -1, v.notFound(reflect.Int64) } return v.Int64Default(-1) } @@ -865,7 +909,7 @@ func (r *Store) GetInt64Default(key string, def int64) int64 { func (r *Store) GetUint(key string) (uint, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("uint", key) + return 0, v.notFound(reflect.Uint) } return v.UintDefault(0) } @@ -885,7 +929,7 @@ func (r *Store) GetUintDefault(key string, def uint) uint { func (r *Store) GetUint8(key string) (uint8, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("uint8", key) + return 0, v.notFound(reflect.Uint8) } return v.Uint8Default(0) } @@ -905,7 +949,7 @@ func (r *Store) GetUint8Default(key string, def uint8) uint8 { func (r *Store) GetUint16(key string) (uint16, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("uint16", key) + return 0, v.notFound(reflect.Uint16) } return v.Uint16Default(0) } @@ -925,7 +969,7 @@ func (r *Store) GetUint16Default(key string, def uint16) uint16 { func (r *Store) GetUint32(key string) (uint32, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("uint32", key) + return 0, v.notFound(reflect.Uint32) } return v.Uint32Default(0) } @@ -945,7 +989,7 @@ func (r *Store) GetUint32Default(key string, def uint32) uint32 { func (r *Store) GetUint64(key string) (uint64, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("uint64", key) + return 0, v.notFound(reflect.Uint64) } return v.Uint64Default(0) } @@ -965,7 +1009,7 @@ func (r *Store) GetUint64Default(key string, def uint64) uint64 { func (r *Store) GetFloat64(key string) (float64, error) { v, ok := r.GetEntry(key) if !ok { - return -1, errFindParse.Format("float64", key) + return -1, v.notFound(reflect.Float64) } return v.Float64Default(-1) } @@ -989,7 +1033,7 @@ func (r *Store) GetFloat64Default(key string, def float64) float64 { func (r *Store) GetBool(key string) (bool, error) { v, ok := r.GetEntry(key) if !ok { - return false, errFindParse.Format("bool", key) + return false, v.notFound(reflect.Bool) } return v.BoolDefault(false) diff --git a/core/netutil/tcp.go b/core/netutil/tcp.go index 9b2178cf..cc70a42e 100644 --- a/core/netutil/tcp.go +++ b/core/netutil/tcp.go @@ -2,12 +2,13 @@ package netutil import ( "crypto/tls" + "errors" + "fmt" "net" "os" "strings" "time" - "github.com/kataras/iris/core/errors" "golang.org/x/crypto/acme/autocert" ) @@ -32,14 +33,6 @@ func (l tcpKeepAliveListener) Accept() (c net.Conn, err error) { return tc, nil } -var ( - errPortAlreadyUsed = errors.New("port is already used") - errRemoveUnix = errors.New("unexpected error when trying to remove unix socket file. Addr: %s | Trace: %s") - errChmod = errors.New("cannot chmod %#o for %q: %s") - errCertKeyMissing = errors.New("you should provide certFile and keyFile for TLS/SSL") - errParseTLS = errors.New("couldn't load TLS, certFile=%q, keyFile=%q. Trace: %s") -) - // TCP returns a new tcp(ipv6 if supported by network) and an error on failure. func TCP(addr string) (net.Listener, error) { l, err := net.Listen("tcp", addr) @@ -68,16 +61,16 @@ func TCPKeepAlive(addr string) (ln net.Listener, err error) { // UNIX returns a new unix(file) Listener. func UNIX(socketFile string, mode os.FileMode) (net.Listener, error) { if errOs := os.Remove(socketFile); errOs != nil && !os.IsNotExist(errOs) { - return nil, errRemoveUnix.Format(socketFile, errOs.Error()) + return nil, fmt.Errorf("%s: %w", socketFile, errOs) } l, err := net.Listen("unix", socketFile) if err != nil { - return nil, errPortAlreadyUsed.AppendErr(err) + return nil, fmt.Errorf("port already in use: %w", err) } if err = os.Chmod(socketFile, mode); err != nil { - return nil, errChmod.Format(mode, socketFile, err.Error()) + return nil, fmt.Errorf("cannot chmod %#o for %q: %w", mode, socketFile, err) } return l, nil @@ -86,12 +79,12 @@ func UNIX(socketFile string, mode os.FileMode) (net.Listener, error) { // TLS returns a new TLS Listener and an error on failure. func TLS(addr, certFile, keyFile string) (net.Listener, error) { if certFile == "" || keyFile == "" { - return nil, errCertKeyMissing + return nil, errors.New("empty certFile or KeyFile") } cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { - return nil, errParseTLS.Format(certFile, keyFile, err) + return nil, err } return CERT(addr, cert) diff --git a/core/router/api_builder.go b/core/router/api_builder.go index a40447bc..de220024 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -1,14 +1,17 @@ package router import ( + "errors" "net/http" "os" "path" + "path/filepath" + "runtime" "strings" "time" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" + "github.com/kataras/iris/core/errgroup" "github.com/kataras/iris/macro" macroHandler "github.com/kataras/iris/macro/handler" ) @@ -157,6 +160,15 @@ func (repo *repository) register(route *Route) { repo.pos[route.tmpl.Src] = len(repo.routes) - 1 } +type apiError struct { + error +} + +func (e *apiError) Is(err error) bool { + _, ok := err.(*apiError) + return ok +} + // APIBuilder the visible API for constructing the router // and child routers. type APIBuilder struct { @@ -174,7 +186,7 @@ type APIBuilder struct { // the list of possible errors that can be // collected on the build state to log // to the end-user. - reporter *errors.Reporter + errors *errgroup.Group // the per-party handlers, order // of handlers registration matters. @@ -212,7 +224,7 @@ func NewAPIBuilder() *APIBuilder { api := &APIBuilder{ macros: macro.Defaults, errorCodeHandlers: defaultErrorCodeHandlers(), - reporter: errors.NewReporter(), + errors: errgroup.New("API Builder"), relativePath: "/", routes: new(repository), } @@ -228,14 +240,9 @@ func (api *APIBuilder) GetRelPath() string { return api.relativePath } -// GetReport returns an error may caused by party's methods. -func (api *APIBuilder) GetReport() error { - return api.reporter.Return() -} - -// GetReporter returns the reporter for adding errors -func (api *APIBuilder) GetReporter() *errors.Reporter { - return api.reporter +// GetReporter returns the reporter for adding or receiving any errors caused when building the API. +func (api *APIBuilder) GetReporter() *errgroup.Group { + return api.errors } // AllowMethods will re-register the future routes that will be registered @@ -295,9 +302,11 @@ func (api *APIBuilder) createRoutes(methods []string, relativePath string, handl } } + filename, line := getCaller() + fullpath := api.relativePath + relativePath // for now, keep the last "/" if any, "/xyz/" if len(handlers) == 0 { - api.reporter.Add("missing handlers for route %s: %s", strings.Join(methods, ", "), fullpath) + api.errors.Addf("missing handlers for route[%s:%d] %s: %s", filename, line, strings.Join(methods, ", "), fullpath) return nil } @@ -336,10 +345,13 @@ func (api *APIBuilder) createRoutes(methods []string, relativePath string, handl for i, m := range methods { route, err := NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros) if err != nil { // template path parser errors: - api.reporter.Add("%v -> %s:%s:%s", err, m, subdomain, path) + api.errors.Addf("[%s:%d] %v -> %s:%s:%s", filename, line, err, m, subdomain, path) continue } + route.SourceFileName = filename + route.SourceLineNumber = line + // Add UseGlobal & DoneGlobal Handlers route.Use(api.beginGlobalHandlers...) route.Done(api.doneGlobalHandlers...) @@ -350,6 +362,32 @@ func (api *APIBuilder) createRoutes(methods []string, relativePath string, handl return routes } +// https://golang.org/doc/go1.9#callersframes +func getCaller() (string, int) { + var pcs [32]uintptr + n := runtime.Callers(1, pcs[:]) + frames := runtime.CallersFrames(pcs[:n]) + wd, _ := os.Getwd() + for { + frame, more := frames.Next() + file := frame.File + + if !strings.Contains(file, "/kataras/iris") || strings.Contains(file, "/kataras/iris/_examples") || strings.Contains(file, "iris-contrib/examples") { + if relFile, err := filepath.Rel(wd, file); err == nil { + file = "./" + relFile + } + + return file, frame.Line + } + + if !more { + break + } + } + + return "???", 0 +} + // Handle registers a route to the server's api. // if empty method is passed then handler(s) are being registered to all methods, same as .Any. // @@ -381,8 +419,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co // app.Handle("GET", "/user/{id:uint64}", userByIDHandler) // app.Handle("GET", "/user/me", userMeHandler) // -// This method is used behind the scenes at the `Controller` function -// in order to handle more than one paths for the same controller instance. +// app.HandleMany("GET POST", "/path", handler) func (api *APIBuilder) HandleMany(methodOrMulti string, relativePathorMulti string, handlers ...context.Handler) (routes []*Route) { // at least slash // a space @@ -502,7 +539,7 @@ func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) P errorCodeHandlers: api.errorCodeHandlers, beginGlobalHandlers: api.beginGlobalHandlers, doneGlobalHandlers: api.doneGlobalHandlers, - reporter: api.reporter, + errors: api.errors, // per-party/children middleware: middleware, doneHandlers: api.doneHandlers[0:], @@ -542,7 +579,7 @@ func (api *APIBuilder) PartyFunc(relativePath string, partyBuilderFunc func(p Pa func (api *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler) Party { if api.relativePath == SubdomainWildcardIndicator { // cannot concat wildcard subdomain with something else - api.reporter.Add("cannot concat parent wildcard subdomain with anything else -> %s , %s", + api.errors.Addf("cannot concat parent wildcard subdomain with anything else -> %s , %s", api.relativePath, subdomain) return api } @@ -562,7 +599,7 @@ func (api *APIBuilder) Subdomain(subdomain string, middleware ...context.Handler func (api *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party { if hasSubdomain(api.relativePath) { // cannot concat static subdomain with a dynamic one, wildcard should be at the root level - api.reporter.Add("cannot concat static subdomain with a dynamic one. Dynamic subdomains should be at the root level -> %s", + api.errors.Addf("cannot concat static subdomain with a dynamic one. Dynamic subdomains should be at the root level -> %s", api.relativePath) return api } @@ -831,7 +868,7 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route { f, err := os.Open(favPath) if err != nil { - api.reporter.AddErr(errDirectoryFileNotFound.Format(favPath, err.Error())) + api.errors.Addf("favicon: file or directory %s not found: %w", favPath, err) return nil } @@ -848,8 +885,7 @@ func (api *APIBuilder) Favicon(favPath string, requestPath ...string) *Route { // So we could panic but we don't, // we just interrupt with a message // to the (user-defined) logger. - api.reporter.AddErr(errDirectoryFileNotFound. - Format(favPath, "favicon: couldn't read the data bytes for file: "+err.Error())) + api.errors.Addf("favicon: couldn't read the data bytes for %s: %w", favPath, err) return nil } diff --git a/core/router/deprecated.go b/core/router/deprecated.go deleted file mode 100644 index 3dd4819f..00000000 --- a/core/router/deprecated.go +++ /dev/null @@ -1,97 +0,0 @@ -package router - -import ( - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/kataras/iris/context" -) - -/* - Relative to deprecation: - - party.go#L138-154 - - deprecated_example_test.go -*/ - -// https://golang.org/doc/go1.9#callersframes -func getCaller() (string, int) { - var pcs [32]uintptr - n := runtime.Callers(1, pcs[:]) - frames := runtime.CallersFrames(pcs[:n]) - wd, _ := os.Getwd() - for { - frame, more := frames.Next() - file := frame.File - - if (!strings.Contains(file, "/kataras/iris") || - strings.Contains(file, "/kataras/iris/_examples") || - strings.Contains(file, "/iris-contrib/examples") || - (strings.Contains(file, "/kataras/iris/core/router") && !strings.Contains(file, "deprecated.go"))) && - !strings.HasSuffix(frame.Func.Name(), ".getCaller") && !strings.Contains(file, "/go/src/testing") { - - if relFile, err := filepath.Rel(wd, file); err == nil { - file = "./" + relFile - } - - return file, frame.Line - } - - if !more { - break - } - } - - return "?", 0 -} - -// StaticWeb is DEPRECATED. Use HandleDir(requestPath, directory) instead. -func (api *APIBuilder) StaticWeb(requestPath string, directory string) *Route { - file, line := getCaller() - api.reporter.Add(`StaticWeb is DEPRECATED and it will be removed eventually. -Source: %s:%d -Use .HandleDir("%s", "%s") instead.`, file, line, requestPath, directory) - - return nil -} - -// StaticHandler is DEPRECATED. -// Use iris.FileServer(directory, iris.DirOptions{ShowList: true, Gzip: true}) instead. -// -// Example https://github.com/kataras/iris/tree/master/_examples/file-server/basic -func (api *APIBuilder) StaticHandler(directory string, showList bool, gzip bool) context.Handler { - file, line := getCaller() - api.reporter.Add(`StaticHandler is DEPRECATED and it will be removed eventually. -Source: %s:%d -Use iris.FileServer("%s", iris.DirOptions{ShowList: %v, Gzip: %v}) instead.`, file, line, directory, showList, gzip) - return FileServer(directory, DirOptions{ShowList: showList, Gzip: gzip}) -} - -// StaticEmbedded is DEPRECATED. -// Use HandleDir(requestPath, directory, iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead. -// -// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app -func (api *APIBuilder) StaticEmbedded(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route { - file, line := getCaller() - api.reporter.Add(`StaticEmbedded is DEPRECATED and it will be removed eventually. -It is also miss the AssetInfo bindata function, which is required now. -Source: %s:%d -Use .HandleDir("%s", "%s", iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.`, file, line, requestPath, directory) - - return nil -} - -// StaticEmbeddedGzip is DEPRECATED. -// Use HandleDir(requestPath, directory, iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead. -// -// Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app -func (api *APIBuilder) StaticEmbeddedGzip(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route { - file, line := getCaller() - api.reporter.Add(`StaticEmbeddedGzip is DEPRECATED and it will be removed eventually. -It is also miss the AssetInfo bindata function, which is required now. -Source: %s:%d -Use .HandleDir("%s", "%s", iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead.`, file, line, requestPath, directory) - - return nil -} diff --git a/core/router/deprecated_example_test.go b/core/router/deprecated_example_test.go deleted file mode 100644 index cfad42a2..00000000 --- a/core/router/deprecated_example_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package router - -import ( - "fmt" -) - -func ExampleParty_StaticWeb() { - api := NewAPIBuilder() - api.StaticWeb("/static", "./assets") - - err := api.GetReport() - if err == nil { - panic("expected report for deprecation") - } - - fmt.Print(err) - // Output: StaticWeb is DEPRECATED and it will be removed eventually. - // Source: ./deprecated_example_test.go:9 - // Use .HandleDir("/static", "./assets") instead. -} - -func ExampleParty_StaticHandler() { - api := NewAPIBuilder() - api.StaticHandler("./assets", false, true) - - err := api.GetReport() - if err == nil { - panic("expected report for deprecation") - } - - fmt.Print(err) - // Output: StaticHandler is DEPRECATED and it will be removed eventually. - // Source: ./deprecated_example_test.go:24 - // Use iris.FileServer("./assets", iris.DirOptions{ShowList: false, Gzip: true}) instead. -} - -func ExampleParty_StaticEmbedded() { - api := NewAPIBuilder() - api.StaticEmbedded("/static", "./assets", nil, nil) - - err := api.GetReport() - if err == nil { - panic("expected report for deprecation") - } - - fmt.Print(err) - // Output: StaticEmbedded is DEPRECATED and it will be removed eventually. - // It is also miss the AssetInfo bindata function, which is required now. - // Source: ./deprecated_example_test.go:39 - // Use .HandleDir("/static", "./assets", iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead. -} - -func ExampleParty_StaticEmbeddedGzip() { - api := NewAPIBuilder() - api.StaticEmbeddedGzip("/static", "./assets", nil, nil) - - err := api.GetReport() - if err == nil { - panic("expected report for deprecation") - } - - fmt.Print(err) - // Output: StaticEmbeddedGzip is DEPRECATED and it will be removed eventually. - // It is also miss the AssetInfo bindata function, which is required now. - // Source: ./deprecated_example_test.go:55 - // Use .HandleDir("/static", "./assets", iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead. -} diff --git a/core/router/handler.go b/core/router/handler.go index c93ac62c..1199b669 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -6,7 +6,7 @@ import ( "strings" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" + "github.com/kataras/iris/core/errgroup" "github.com/kataras/iris/core/netutil" macroHandler "github.com/kataras/iris/macro/handler" @@ -82,7 +82,7 @@ type RoutesProvider interface { // api builder func (h *routerHandler) Build(provider RoutesProvider) error { h.trees = h.trees[0:0] // reset, inneed when rebuilding. - rp := errors.NewReporter() + rp := errgroup.New("Routes Builder") registeredRoutes := provider.GetRoutes() // before sort. @@ -138,7 +138,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { // the docs better. Or TODO: add a link here in order to help new users. if err := h.addRoute(r); err != nil { // node errors: - rp.Add("%v -> %s", err, r.String()) + rp.Addf("%s: %w", r.String(), err) continue } } @@ -146,7 +146,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error { golog.Debugf(r.Trace()) // keep log different parameter types in the same path as different routes. } - return rp.Return() + return errgroup.Check(rp) } func bindMultiParamTypesHandler(top *Route, r *Route) { diff --git a/core/router/party.go b/core/router/party.go index 340eb604..e248b50e 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -2,7 +2,7 @@ package router import ( "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" + "github.com/kataras/iris/core/errgroup" "github.com/kataras/iris/macro" ) @@ -16,8 +16,8 @@ type Party interface { // if r := app.Party("/users"), then the `r.GetRelPath()` is the "/users". // if r := app.Party("www.") or app.Subdomain("www") then the `r.GetRelPath()` is the "www.". GetRelPath() string - // GetReporter returns the reporter for adding errors - GetReporter() *errors.Reporter + // GetReporter returns the reporter for adding or receiving any errors caused when building the API. + GetReporter() *errgroup.Group // Macros returns the macro collection that is responsible // to register custom macros with their own parameter types and their macro functions for all routes. // @@ -135,23 +135,6 @@ type Party interface { // // Examples can be found at: https://github.com/kataras/iris/tree/master/_examples/file-server HandleDir(requestPath, directory string, opts ...DirOptions) *Route - // StaticWeb is DEPRECATED. Use HandleDir(requestPath, directory) instead. - StaticWeb(requestPath string, directory string) *Route - // StaticHandler is DEPRECATED. - // Use iris.FileServer(directory, iris.DirOptions{ShowList: true, Gzip: true}) instead. - // - // Example https://github.com/kataras/iris/tree/master/_examples/file-server/basic - StaticHandler(directory string, showList bool, gzip bool) context.Handler - // StaticEmbedded is DEPRECATED. - // Use HandleDir(requestPath, directory, iris.DirOptions{Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead. - // - // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-files-into-app - StaticEmbedded(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route - // StaticEmbeddedGzip is DEPRECATED. - // Use HandleDir(requestPath, directory, iris.DirOptions{Gzip: true, Asset: Asset, AssetInfo: AssetInfo, AssetNames: AssetNames}) instead. - // - // Example: https://github.com/kataras/iris/tree/master/_examples/file-server/embedding-gziped-files-into-app - StaticEmbeddedGzip(requestPath string, directory string, assetFn func(name string) ([]byte, error), namesFn func() []string) *Route // None registers an "offline" route // see context.ExecRoute(routeName) and diff --git a/core/router/route.go b/core/router/route.go index 42dcc20b..b25f3b8b 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -34,6 +34,10 @@ type Route struct { // used by Application to validate param values of a Route based on its name. FormattedPath string `json:"formattedPath"` + // the source code's filename:filenumber that this route was created from. + SourceFileName string + SourceLineNumber int + // StaticSites if not empty, refers to the system (or virtual if embedded) directory // and sub directories that this "GET" route was registered to serve files and folders // that contain index.html (a site). The index handler may registered by other @@ -276,7 +280,7 @@ func (r Route) ResolvePath(args ...string) string { // Trace returns some debug infos as a string sentence. // Should be called after Build. func (r Route) Trace() string { - printfmt := fmt.Sprintf("%s:", r.Method) + printfmt := fmt.Sprintf("[%s:%d] %s:", r.SourceFileName, r.SourceLineNumber, r.Method) if r.Subdomain != "" { printfmt += fmt.Sprintf(" %s", r.Subdomain) } diff --git a/core/router/router.go b/core/router/router.go index e6efea9d..fb52587e 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -1,11 +1,11 @@ package router import ( + "errors" "net/http" "sync" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" ) // Router is the "director". diff --git a/go.mod b/go.mod index 2a8fe0f6..7b407800 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/iris-contrib/go.uuid v2.0.0+incompatible github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0 github.com/json-iterator/go v1.1.6 - github.com/kataras/golog v0.0.0-20190624001437-99c81de45f40 + github.com/kataras/golog v0.0.9 github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d // indirect github.com/mediocregopher/radix/v3 v3.3.0 github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38 diff --git a/go.sum b/go.sum index 7ecc8efe..2d180e0f 100644 --- a/go.sum +++ b/go.sum @@ -16,7 +16,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3 github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/kataras/golog v0.0.0-20190624001437-99c81de45f40/go.mod h1:PcaEvfvhGsqwXZ6S3CgCbmjcp+4UDUh2MIfF2ZEul8M= +github.com/kataras/golog v0.0.9 h1:J7Dl82843nbKQDrQM/abbNJZvQjS6PfmkkffhOTXEpM= +github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk= github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38 h1:y0Wmhvml7cGnzPa9nocn/fMraMH/lMDdeG+rkx4VgYY= diff --git a/iris.go b/iris.go index 05e6ffec..55fa2851 100644 --- a/iris.go +++ b/iris.go @@ -4,6 +4,7 @@ import ( // std packages stdContext "context" + "errors" "fmt" "io" "log" @@ -17,8 +18,8 @@ import ( // context for the handlers "github.com/kataras/iris/context" - // core packages, needed to build the application - "github.com/kataras/iris/core/errors" + // core packages, required to build the application + "github.com/kataras/iris/core/errgroup" "github.com/kataras/iris/core/host" "github.com/kataras/iris/core/netutil" "github.com/kataras/iris/core/router" @@ -804,19 +805,31 @@ func Raw(f func() error) Runner { // Build sets up, once, the framework. // It builds the default router with its default macros // and the template functions that are very-closed to iris. +// +// If error occured while building the Application, the returns type of error will be an *errgroup.Group +// which let the callers to inspect the errors and cause, usage: +// +// import "github.com/kataras/iris/core/errgroup" +// +// errgroup.Walk(app.Build(), func(typ interface{}, err error) { +// app.Logger().Errorf("%s: %s", typ, err) +// }) func (app *Application) Build() error { - rp := errors.NewReporter() + rp := errgroup.New("Application Builder") app.once.Do(func() { - rp.Describe("api builder: %v", app.APIBuilder.GetReport()) + rp.Err(app.APIBuilder.GetReporter()) if !app.Router.Downgraded() { // router // create the request handler, the default routing handler routerHandler := router.NewDefaultHandler() + err := app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder, false) + if err != nil { + rp.Err(err) + } - rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder, false)) - // re-build of the router from outside can be done with; + // re-build of the router from outside can be done with // app.RefreshRouter() } @@ -828,11 +841,13 @@ func (app *Application) Build() error { rv := router.NewRoutePathReverser(app.APIBuilder) app.view.AddFunc("urlpath", rv.Path) // app.view.AddFunc("url", rv.URL) - rp.Describe("view: %v", app.view.Load()) + if err := app.view.Load(); err != nil { + rp.Group("View Builder").Err(err) + } } }) - return rp.Return() + return errgroup.Check(rp) } // ErrServerClosed is returned by the Server's Serve, ServeTLS, ListenAndServe, @@ -858,7 +873,8 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error { // first Build because it doesn't need anything from configuration, // this gives the user the chance to modify the router inside a configurator as well. if err := app.Build(); err != nil { - return errors.PrintAndReturnErrors(err, app.logger.Errorf) + app.logger.Error(err) + return err } app.Configure(withOrWithout...) diff --git a/mvc/controller.go b/mvc/controller.go index c469eb03..89a4a15a 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -273,7 +273,11 @@ func (c *ControllerActivator) activate() { } func (c *ControllerActivator) addErr(err error) bool { - return c.router.GetReporter().AddErr(err) + if c.router.GetReporter().Err(err) != nil { + return true + } + + return false } // register all available, exported methods to handlers if possible. diff --git a/sessions/database.go b/sessions/database.go index d4dfc644..c444b828 100644 --- a/sessions/database.go +++ b/sessions/database.go @@ -1,10 +1,10 @@ package sessions import ( + "errors" "sync" "time" - "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/memstore" ) diff --git a/sessions/provider.go b/sessions/provider.go index 0c1f6c4b..1acc3735 100644 --- a/sessions/provider.go +++ b/sessions/provider.go @@ -1,10 +1,9 @@ package sessions import ( + "errors" "sync" "time" - - "github.com/kataras/iris/core/errors" ) type ( @@ -78,9 +77,13 @@ func (p *provider) Init(sid string, expires time.Duration) *Session { return newSession } -// ErrNotFound can be returned when calling `UpdateExpiration` on a non-existing or invalid session entry. -// It can be matched directly, i.e: `isErrNotFound := sessions.ErrNotFound.Equal(err)`. -var ErrNotFound = errors.New("not found") +// ErrNotFound may be returned from `UpdateExpiration` of a non-existing or +// invalid session entry from memory storage or databases. +// Usage: +// if err != nil && err.Is(err, sessions.ErrNotFound) { +// [handle error...] +// } +var ErrNotFound = errors.New("session not found") // UpdateExpiration resets the expiration of a session. // if expires > 0 then it will try to update the expiration and destroy task is delayed. diff --git a/sessions/session.go b/sessions/session.go index 61a269c2..67bb5e9b 100644 --- a/sessions/session.go +++ b/sessions/session.go @@ -1,10 +1,11 @@ package sessions import ( + "reflect" "strconv" "sync" - "github.com/kataras/iris/core/errors" + "github.com/kataras/iris/core/memstore" ) type ( @@ -165,7 +166,54 @@ func (s *Session) GetFlashStringDefault(key string, defaultValue string) string return defaultValue } -var errFindParse = errors.New("Unable to find the %s with key: %s. Found? %#v") +// ErrEntryNotFound similar to core/memstore#ErrEntryNotFound but adds +// the value (if found) matched to the requested key-value pair of the session's memory storage. +type ErrEntryNotFound struct { + Err *memstore.ErrEntryNotFound + Value interface{} +} + +func (e *ErrEntryNotFound) Error() string { + return e.Err.Error() +} + +// Unwrap method implements the dynamic Unwrap interface of the std errors package. +func (e *ErrEntryNotFound) Unwrap() error { + return e.Err +} + +// As method implements the dynamic As interface of the std errors package. +// As should be NOT used directly, use `errors.As` instead. +func (e *ErrEntryNotFound) As(target interface{}) bool { + if v, ok := target.(*memstore.ErrEntryNotFound); ok && e.Err != nil { + return e.Err.As(v) + } + + v, ok := target.(*ErrEntryNotFound) + if !ok { + return false + } + + if v.Value != nil { + if v.Value != e.Value { + return false + } + } + + if v.Err != nil { + if e.Err != nil { + return e.Err.As(v.Err) + } + + return false + } + + return true +} + +func newErrEntryNotFound(key string, kind reflect.Kind, value interface{}) *ErrEntryNotFound { + return &ErrEntryNotFound{Err: &memstore.ErrEntryNotFound{Key: key, Kind: kind}, Value: value} +} // GetInt same as `Get` but returns its int representation, // if key doesn't exist then it returns -1 and a non-nil error. @@ -188,7 +236,7 @@ func (s *Session) GetInt(key string) (int, error) { return strconv.Atoi(vstring) } - return -1, errFindParse.Format("int", key, v) + return -1, newErrEntryNotFound(key, reflect.Int, v) } // GetIntDefault same as `Get` but returns its int representation, @@ -241,7 +289,7 @@ func (s *Session) GetInt64(key string) (int64, error) { return strconv.ParseInt(vstring, 10, 64) } - return -1, errFindParse.Format("int64", key, v) + return -1, newErrEntryNotFound(key, reflect.Int64, v) } // GetInt64Default same as `Get` but returns its int64 representation, @@ -283,7 +331,7 @@ func (s *Session) GetFloat32(key string) (float32, error) { return float32(vfloat64), nil } - return -1, errFindParse.Format("float32", key, v) + return -1, newErrEntryNotFound(key, reflect.Float32, v) } // GetFloat32Default same as `Get` but returns its float32 representation, @@ -321,7 +369,7 @@ func (s *Session) GetFloat64(key string) (float64, error) { return strconv.ParseFloat(vstring, 32) } - return -1, errFindParse.Format("float64", key, v) + return -1, newErrEntryNotFound(key, reflect.Float64, v) } // GetFloat64Default same as `Get` but returns its float64 representation, @@ -339,7 +387,7 @@ func (s *Session) GetFloat64Default(key string, defaultValue float64) float64 { func (s *Session) GetBoolean(key string) (bool, error) { v := s.Get(key) if v == nil { - return false, errFindParse.Format("bool", key, "nil") + return false, newErrEntryNotFound(key, reflect.Bool, nil) } // here we could check for "true", "false" and 0 for false and 1 for true @@ -352,7 +400,7 @@ func (s *Session) GetBoolean(key string) (bool, error) { return strconv.ParseBool(vstring) } - return false, errFindParse.Format("bool", key, v) + return false, newErrEntryNotFound(key, reflect.Bool, v) } // GetBooleanDefault same as `Get` but returns its boolean representation, diff --git a/sessions/sessiondb/badger/database.go b/sessions/sessiondb/badger/database.go index 84c8b0f6..6149bfe9 100644 --- a/sessions/sessiondb/badger/database.go +++ b/sessions/sessiondb/badger/database.go @@ -2,12 +2,12 @@ package badger import ( "bytes" + "errors" "os" "runtime" "sync/atomic" "time" - "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" "github.com/dgraph-io/badger" @@ -47,7 +47,7 @@ var _ sessions.Database = (*Database)(nil) // It will remove any old session files. func New(directoryPath string) (*Database, error) { if directoryPath == "" { - return nil, errors.New("directoryPath is missing") + return nil, errors.New("directoryPath is empty") } lindex := directoryPath[len(directoryPath)-1] diff --git a/sessions/sessiondb/boltdb/database.go b/sessions/sessiondb/boltdb/database.go index e2859095..7756ec0d 100644 --- a/sessions/sessiondb/boltdb/database.go +++ b/sessions/sessiondb/boltdb/database.go @@ -1,12 +1,12 @@ package boltdb import ( + "errors" "os" "path/filepath" "runtime" "time" - "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" bolt "github.com/etcd-io/bbolt" diff --git a/sessions/sessiondb/redis/database.go b/sessions/sessiondb/redis/database.go index 7345cc05..f38d258c 100644 --- a/sessions/sessiondb/redis/database.go +++ b/sessions/sessiondb/redis/database.go @@ -1,9 +1,9 @@ package redis import ( + "errors" "time" - "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" "github.com/kataras/golog" @@ -252,8 +252,13 @@ func closeDB(db *Database) error { } var ( - // ErrRedisClosed an error with message 'Redis is already closed' - ErrRedisClosed = errors.New("Redis is already closed") - // ErrKeyNotFound an error with message 'Key $thekey doesn't found' - ErrKeyNotFound = errors.New("Key '%s' doesn't found") + // ErrRedisClosed an error with message 'redis: already closed' + ErrRedisClosed = errors.New("redis: already closed") + // ErrKeyNotFound a type of error of non-existing redis keys. + // The producers(the library) of this error will dynamically wrap this error(fmt.Errorf) with the key name. + // Usage: + // if err != nil && errors.Is(err, ErrKeyNotFound) { + // [...] + // } + ErrKeyNotFound = errors.New("key not found") ) diff --git a/sessions/sessiondb/redis/driver_radix.go b/sessions/sessiondb/redis/driver_radix.go index 426c3542..be0b3680 100644 --- a/sessions/sessiondb/redis/driver_radix.go +++ b/sessions/sessiondb/redis/driver_radix.go @@ -145,7 +145,7 @@ func (r *RadixDriver) Get(key string) (interface{}, error) { return nil, err } if mn.Nil { - return nil, ErrKeyNotFound.Format(key) + return nil, fmt.Errorf("%s: %w", key, ErrKeyNotFound) } return redisVal, nil } diff --git a/sessions/sessiondb/redis/driver_redigo.go b/sessions/sessiondb/redis/driver_redigo.go index 63880724..e247ea25 100644 --- a/sessions/sessiondb/redis/driver_redigo.go +++ b/sessions/sessiondb/redis/driver_redigo.go @@ -83,7 +83,7 @@ func (r *RedigoDriver) Get(key string) (interface{}, error) { return nil, err } if redisVal == nil { - return nil, ErrKeyNotFound.Format(key) + return nil, fmt.Errorf("%s: %w", key, ErrKeyNotFound) } return redisVal, nil } @@ -256,7 +256,7 @@ func (r *RedigoDriver) GetBytes(key string) ([]byte, error) { return nil, err } if redisVal == nil { - return nil, ErrKeyNotFound.Format(key) + return nil, fmt.Errorf("%s: %w", key, ErrKeyNotFound) } return redis.Bytes(redisVal, err) diff --git a/view/amber.go b/view/amber.go index c295a1d0..8d4f63a5 100644 --- a/view/amber.go +++ b/view/amber.go @@ -4,6 +4,7 @@ import ( "fmt" "html/template" "io" + "os" "path/filepath" "strings" "sync" @@ -91,6 +92,11 @@ func (s *AmberEngine) Load() error { if err != nil { return err } + + if _, err := os.Stat(dir); os.IsNotExist(err) { + return err + } + // change the directory field configuration, load happens after directory has been set, so we will not have any problems here. s.directory = dir return s.loadDirectory() diff --git a/view/django.go b/view/django.go index c814444f..776dda64 100644 --- a/view/django.go +++ b/view/django.go @@ -209,6 +209,11 @@ func (s *DjangoEngine) Load() error { if err != nil { return err } + + if _, err := os.Stat(dir); os.IsNotExist(err) { + return err + } + // change the directory field configuration, load happens after directory has been set, so we will not have any problems here. s.directory = dir return s.loadDirectory() diff --git a/view/handlebars.go b/view/handlebars.go index 829f7835..ab60d555 100644 --- a/view/handlebars.go +++ b/view/handlebars.go @@ -111,6 +111,10 @@ func (s *HandlebarsEngine) Load() error { if err != nil { return err } + if _, err := os.Stat(dir); os.IsNotExist(err) { + return err + } + // change the directory field configuration, load happens after directory has been set, so we will not have any problems here. s.directory = dir return s.loadDirectory() diff --git a/view/html.go b/view/html.go index eefacb3d..43702fb4 100644 --- a/view/html.go +++ b/view/html.go @@ -226,6 +226,11 @@ func (s *HTMLEngine) Load() error { if err != nil { return err } + + if _, err := os.Stat(dir); os.IsNotExist(err) { + return err + } + // change the directory field configuration, load happens after directory has been set, so we will not have any problems here. s.directory = dir return s.loadDirectory() diff --git a/view/jet.go b/view/jet.go index 95514b57..c98dac2f 100644 --- a/view/jet.go +++ b/view/jet.go @@ -47,6 +47,10 @@ var jetExtensions = [...]string{ // Jet creates and returns a new jet view engine. func Jet(directory, extension string) *JetEngine { + // if _, err := os.Stat(directory); os.IsNotExist(err) { + // panic(err) + // } + extOK := false for _, ext := range jetExtensions { if ext == extension { diff --git a/view/view.go b/view/view.go index 22ea3689..9d2d1775 100644 --- a/view/view.go +++ b/view/view.go @@ -1,11 +1,10 @@ package view import ( + "fmt" "io" "path/filepath" "strings" - - "github.com/kataras/iris/core/errors" ) // View is responsible to @@ -37,8 +36,6 @@ func (v *View) Len() int { return len(v.engines) } -var errNoViewEngineForExt = errors.New("no view engine found for '%s'") - // ExecuteWriter calls the correct view Engine's ExecuteWriter func func (v *View) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error { if len(filename) > 2 { @@ -49,7 +46,7 @@ func (v *View) ExecuteWriter(w io.Writer, filename string, layout string, bindin e := v.Find(filename) if e == nil { - return errNoViewEngineForExt.Format(filepath.Ext(filename)) + return fmt.Errorf("no view engine found for '%s'", filepath.Ext(filename)) } return e.ExecuteWriter(w, filename, layout, bindingData)