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)