diff --git a/context.go b/context.go index 9716c67b..d6a64fe5 100644 --- a/context.go +++ b/context.go @@ -126,7 +126,7 @@ func (c *Context) AbortWithStatus(code int) { c.Abort() } -func (c *Context) AbortWithError(code int, err error) *errorMsg { +func (c *Context) AbortWithError(code int, err error) *Error { c.AbortWithStatus(code) return c.Error(err) } @@ -142,21 +142,19 @@ func (c *Context) IsAborted() bool { // Attaches an error to the current context. The error is pushed to a list of errors. // It's a good idea to call Error for each error that occurred during the resolution of a request. // A middleware can be used to collect all the errors and push them to a database together, print a log, or append it in the HTTP response. -func (c *Context) Error(err error) *errorMsg { - newError := &errorMsg{ - Err: err, - Flags: ErrorTypePrivate, +func (c *Context) Error(err error) *Error { + var parsedError *Error + switch err.(type) { + case *Error: + parsedError = err.(*Error) + default: + parsedError = &Error{ + Err: err, + Type: ErrorTypePrivate, + } } - c.Errors = append(c.Errors, newError) - return newError -} - -func (c *Context) LastError() error { - nuErrors := len(c.Errors) - if nuErrors > 0 { - return c.Errors[nuErrors-1].Err - } - return nil + c.Errors = append(c.Errors, parsedError) + return parsedError } /************************************/ @@ -270,7 +268,7 @@ func (c *Context) BindJSON(obj interface{}) error { func (c *Context) BindWith(obj interface{}, b binding.Binding) error { if err := b.Bind(c.Request, obj); err != nil { - c.AbortWithError(400, err).Type(ErrorTypeBind) + c.AbortWithError(400, err).SetType(ErrorTypeBind) return err } return nil @@ -309,7 +307,7 @@ func (c *Context) Render(code int, r render.Render) { c.Writer.WriteHeader(code) if err := r.Write(c.Writer); err != nil { debugPrintError(err) - c.AbortWithError(500, err).Type(ErrorTypeRender) + c.AbortWithError(500, err).SetType(ErrorTypeRender) } } diff --git a/context_test.go b/context_test.go index 17c123e1..3cfde113 100644 --- a/context_test.go +++ b/context_test.go @@ -349,56 +349,50 @@ func TestContextAbortWithStatus(t *testing.T) { func TestContextError(t *testing.T) { c, _, _ := createTestContext() - assert.Nil(t, c.LastError()) - assert.Empty(t, c.Errors.String()) + assert.Empty(t, c.Errors) - c.Error(errors.New("first error")).Meta("some data") - assert.Equal(t, c.LastError().Error(), "first error") + c.Error(errors.New("first error")) assert.Len(t, c.Errors, 1) - assert.Equal(t, c.Errors.String(), "Error #01: first error\n Meta: some data\n") + assert.Equal(t, c.Errors.String(), "Error #01: first error\n") - c.Error(errors.New("second error")).Meta("some data 2") - assert.Equal(t, c.LastError().Error(), "second error") + c.Error(&Error{ + Err: errors.New("second error"), + Meta: "some data 2", + Type: ErrorTypePublic, + }) assert.Len(t, c.Errors, 2) - assert.Equal(t, c.Errors.String(), "Error #01: first error\n Meta: some data\n"+ - "Error #02: second error\n Meta: some data 2\n") assert.Equal(t, c.Errors[0].Err, errors.New("first error")) - assert.Equal(t, c.Errors[0].Metadata, "some data") - assert.Equal(t, c.Errors[0].Flags, ErrorTypePrivate) + assert.Nil(t, c.Errors[0].Meta) + assert.Equal(t, c.Errors[0].Type, ErrorTypePrivate) assert.Equal(t, c.Errors[1].Err, errors.New("second error")) - assert.Equal(t, c.Errors[1].Metadata, "some data 2") - assert.Equal(t, c.Errors[1].Flags, ErrorTypePrivate) + assert.Equal(t, c.Errors[1].Meta, "some data 2") + assert.Equal(t, c.Errors[1].Type, ErrorTypePublic) + + assert.Equal(t, c.Errors.Last(), c.Errors[1]) } func TestContextTypedError(t *testing.T) { c, _, _ := createTestContext() - c.Error(errors.New("externo 0")).Type(ErrorTypePublic) - c.Error(errors.New("externo 1")).Type(ErrorTypePublic) - c.Error(errors.New("interno 0")).Type(ErrorTypePrivate) - c.Error(errors.New("externo 2")).Type(ErrorTypePublic) - c.Error(errors.New("interno 1")).Type(ErrorTypePrivate) - c.Error(errors.New("interno 2")).Type(ErrorTypePrivate) + c.Error(errors.New("externo 0")).SetType(ErrorTypePublic) + c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate) for _, err := range c.Errors.ByType(ErrorTypePublic) { - assert.Equal(t, err.Flags, ErrorTypePublic) + assert.Equal(t, err.Type, ErrorTypePublic) } - for _, err := range c.Errors.ByType(ErrorTypePrivate) { - assert.Equal(t, err.Flags, ErrorTypePrivate) + assert.Equal(t, err.Type, ErrorTypePrivate) } - - assert.Equal(t, c.Errors.Errors(), []string{"externo 0", "externo 1", "interno 0", "externo 2", "interno 1", "interno 2"}) + assert.Equal(t, c.Errors.Errors(), []string{"externo 0", "interno 0"}) } -func TestContextFail(t *testing.T) { +func TestContextAbortWithError(t *testing.T) { c, w, _ := createTestContext() - c.AbortWithError(401, errors.New("bad input")) + c.AbortWithError(401, errors.New("bad input")).SetMeta("some input") c.Writer.WriteHeaderNow() assert.Equal(t, w.Code, 401) - assert.Equal(t, c.LastError().Error(), "bad input") assert.Equal(t, c.index, AbortIndex) assert.True(t, c.IsAborted()) } diff --git a/errors.go b/errors.go index af6b04a1..ff03262f 100644 --- a/errors.go +++ b/errors.go @@ -21,33 +21,37 @@ const ( ) // Used internally to collect errors that occurred during an http request. -type errorMsg struct { - Err error `json:"error"` - Flags int `json:"-"` - Metadata interface{} `json:"meta"` +type Error struct { + Err error `json:"error"` + Type int `json:"-"` + Meta interface{} `json:"meta"` } -func (msg *errorMsg) Type(flags int) *errorMsg { - msg.Flags = flags +var _ error = &Error{} + +func (msg *Error) SetType(flags int) *Error { + msg.Type = flags return msg } -func (msg *errorMsg) Meta(data interface{}) *errorMsg { - msg.Metadata = data +func (msg *Error) SetMeta(data interface{}) *Error { + msg.Meta = data return msg } -func (msg *errorMsg) JSON() interface{} { +func (msg *Error) JSON() interface{} { json := H{} - if msg.Metadata != nil { - value := reflect.ValueOf(msg.Metadata) + if msg.Meta != nil { + value := reflect.ValueOf(msg.Meta) switch value.Kind() { case reflect.Struct: - return msg.Metadata + return msg.Meta case reflect.Map: for _, key := range value.MapKeys() { json[key.String()] = value.MapIndex(key).Interface() } + default: + json["meta"] = msg.Meta } } if _, ok := json["error"]; !ok { @@ -56,11 +60,11 @@ func (msg *errorMsg) JSON() interface{} { return json } -func (msg *errorMsg) Error() string { +func (msg *Error) Error() string { return msg.Err.Error() } -type errorMsgs []*errorMsg +type errorMsgs []*Error func (a errorMsgs) ByType(typ int) errorMsgs { if len(a) == 0 { @@ -68,14 +72,14 @@ func (a errorMsgs) ByType(typ int) errorMsgs { } result := make(errorMsgs, 0, len(a)) for _, msg := range a { - if msg.Flags&typ > 0 { + if msg.Type&typ > 0 { result = append(result, msg) } } return result } -func (a errorMsgs) Last() *errorMsg { +func (a errorMsgs) Last() *Error { length := len(a) if length == 0 { return nil @@ -115,7 +119,10 @@ func (a errorMsgs) String() string { } var buffer bytes.Buffer for i, msg := range a { - fmt.Fprintf(&buffer, "Error #%02d: %s\n Meta: %v\n", (i + 1), msg.Err, msg.Metadata) + fmt.Fprintf(&buffer, "Error #%02d: %s\n", (i + 1), msg.Err) + if msg.Meta != nil { + fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta) + } } return buffer.String() } diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 00000000..2e3e3939 --- /dev/null +++ b/errors_test.go @@ -0,0 +1,90 @@ +// Copyright 2014 Manu Martinez-Almeida. All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. + +package gin + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestError(t *testing.T) { + baseError := errors.New("test error") + err := &Error{ + Err: baseError, + Type: ErrorTypePrivate, + } + assert.Equal(t, err.Error(), baseError.Error()) + assert.Equal(t, err.JSON(), H{"error": baseError.Error()}) + + assert.Equal(t, err.SetType(ErrorTypePublic), err) + assert.Equal(t, err.Type, ErrorTypePublic) + + assert.Equal(t, err.SetMeta("some data"), err) + assert.Equal(t, err.Meta, "some data") + assert.Equal(t, err.JSON(), H{ + "error": baseError.Error(), + "meta": "some data", + }) + + err.SetMeta(H{ + "status": "200", + "data": "some data", + }) + assert.Equal(t, err.JSON(), H{ + "error": baseError.Error(), + "status": "200", + "data": "some data", + }) + + err.SetMeta(H{ + "error": "custom error", + "status": "200", + "data": "some data", + }) + assert.Equal(t, err.JSON(), H{ + "error": "custom error", + "status": "200", + "data": "some data", + }) +} + +func TestErrorSlice(t *testing.T) { + errs := errorMsgs{ + {Err: errors.New("first"), Type: ErrorTypePrivate}, + {Err: errors.New("second"), Type: ErrorTypePrivate, Meta: "some data"}, + {Err: errors.New("third"), Type: ErrorTypePublic, Meta: H{"status": "400"}}, + } + + assert.Equal(t, errs.Last().Error(), "third") + assert.Equal(t, errs.Errors(), []string{"first", "second", "third"}) + assert.Equal(t, errs.ByType(ErrorTypePublic).Errors(), []string{"third"}) + assert.Equal(t, errs.ByType(ErrorTypePrivate).Errors(), []string{"first", "second"}) + assert.Equal(t, errs.ByType(ErrorTypePublic|ErrorTypePrivate).Errors(), []string{"first", "second", "third"}) + assert.Empty(t, errs.ByType(ErrorTypeBind)) + + assert.Equal(t, errs.String(), `Error #01: first +Error #02: second + Meta: some data +Error #03: third + Meta: map[status:400] +`) + assert.Equal(t, errs.JSON(), []interface{}{ + H{"error": "first"}, + H{"error": "second", "meta": "some data"}, + H{"error": "third", "status": "400"}, + }) + + errs = errorMsgs{ + {Err: errors.New("first"), Type: ErrorTypePrivate}, + } + assert.Equal(t, errs.JSON(), H{"error": "first"}) + + errs = errorMsgs{} + assert.Nil(t, errs.Last()) + assert.Nil(t, errs.JSON()) + assert.Empty(t, errs.String()) +}