Upgrade to new go errors and some minor fixes and improvements including easier debugging of invalid routes and e.t.c.

Former-commit-id: 5809157b952ccc61a67a9861470774b3a6fee024
This commit is contained in:
Gerasimos (Makis) Maropoulos 2019-10-24 18:57:05 +03:00
parent c8236a8d3e
commit 221978e41a
40 changed files with 941 additions and 834 deletions

View File

@ -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)
<details>
<summary>Third-party benchmark</summary>
[![](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).
</details>
## Learning Iris
<details>

26
cache/cache_test.go vendored
View File

@ -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})
}
}

View File

@ -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

View File

@ -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*

View File

@ -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

356
core/errgroup/errgroup.go Normal file
View File

@ -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
}

View File

@ -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
})
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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))
}
}

View File

@ -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("", "") })

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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.
}

View File

@ -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) {

View File

@ -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

View File

@ -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)
}

View File

@ -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".

2
go.mod
View File

@ -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

3
go.sum
View File

@ -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=

34
iris.go
View File

@ -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...)

View File

@ -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.

View File

@ -1,10 +1,10 @@
package sessions
import (
"errors"
"sync"
"time"
"github.com/kataras/iris/core/errors"
"github.com/kataras/iris/core/memstore"
)

View File

@ -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.

View File

@ -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,

View File

@ -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]

View File

@ -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"

View File

@ -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")
)

View File

@ -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
}

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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 {

View File

@ -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)