Fixing new errors API

This commit is contained in:
Manu Mtz-Almeida 2015-05-22 16:39:15 +02:00
parent 306da81aaf
commit 37b6f6c179
4 changed files with 150 additions and 61 deletions

View File

@ -126,7 +126,7 @@ func (c *Context) AbortWithStatus(code int) {
c.Abort() c.Abort()
} }
func (c *Context) AbortWithError(code int, err error) *errorMsg { func (c *Context) AbortWithError(code int, err error) *Error {
c.AbortWithStatus(code) c.AbortWithStatus(code)
return c.Error(err) 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. // 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. // 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. // 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 { func (c *Context) Error(err error) *Error {
newError := &errorMsg{ var parsedError *Error
Err: err, switch err.(type) {
Flags: ErrorTypePrivate, case *Error:
parsedError = err.(*Error)
default:
parsedError = &Error{
Err: err,
Type: ErrorTypePrivate,
}
} }
c.Errors = append(c.Errors, newError) c.Errors = append(c.Errors, parsedError)
return newError return parsedError
}
func (c *Context) LastError() error {
nuErrors := len(c.Errors)
if nuErrors > 0 {
return c.Errors[nuErrors-1].Err
}
return nil
} }
/************************************/ /************************************/
@ -270,7 +268,7 @@ func (c *Context) BindJSON(obj interface{}) error {
func (c *Context) BindWith(obj interface{}, b binding.Binding) error { func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
if err := b.Bind(c.Request, obj); err != nil { if err := b.Bind(c.Request, obj); err != nil {
c.AbortWithError(400, err).Type(ErrorTypeBind) c.AbortWithError(400, err).SetType(ErrorTypeBind)
return err return err
} }
return nil return nil
@ -309,7 +307,7 @@ func (c *Context) Render(code int, r render.Render) {
c.Writer.WriteHeader(code) c.Writer.WriteHeader(code)
if err := r.Write(c.Writer); err != nil { if err := r.Write(c.Writer); err != nil {
debugPrintError(err) debugPrintError(err)
c.AbortWithError(500, err).Type(ErrorTypeRender) c.AbortWithError(500, err).SetType(ErrorTypeRender)
} }
} }

View File

@ -349,56 +349,50 @@ func TestContextAbortWithStatus(t *testing.T) {
func TestContextError(t *testing.T) { func TestContextError(t *testing.T) {
c, _, _ := createTestContext() c, _, _ := createTestContext()
assert.Nil(t, c.LastError()) assert.Empty(t, c.Errors)
assert.Empty(t, c.Errors.String())
c.Error(errors.New("first error")).Meta("some data") c.Error(errors.New("first error"))
assert.Equal(t, c.LastError().Error(), "first error")
assert.Len(t, c.Errors, 1) 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") c.Error(&Error{
assert.Equal(t, c.LastError().Error(), "second error") Err: errors.New("second error"),
Meta: "some data 2",
Type: ErrorTypePublic,
})
assert.Len(t, c.Errors, 2) 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].Err, errors.New("first error"))
assert.Equal(t, c.Errors[0].Metadata, "some data") assert.Nil(t, c.Errors[0].Meta)
assert.Equal(t, c.Errors[0].Flags, ErrorTypePrivate) 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].Err, errors.New("second error"))
assert.Equal(t, c.Errors[1].Metadata, "some data 2") assert.Equal(t, c.Errors[1].Meta, "some data 2")
assert.Equal(t, c.Errors[1].Flags, ErrorTypePrivate) assert.Equal(t, c.Errors[1].Type, ErrorTypePublic)
assert.Equal(t, c.Errors.Last(), c.Errors[1])
} }
func TestContextTypedError(t *testing.T) { func TestContextTypedError(t *testing.T) {
c, _, _ := createTestContext() c, _, _ := createTestContext()
c.Error(errors.New("externo 0")).Type(ErrorTypePublic) c.Error(errors.New("externo 0")).SetType(ErrorTypePublic)
c.Error(errors.New("externo 1")).Type(ErrorTypePublic) c.Error(errors.New("interno 0")).SetType(ErrorTypePrivate)
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)
for _, err := range c.Errors.ByType(ErrorTypePublic) { 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) { 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", "interno 0"})
assert.Equal(t, c.Errors.Errors(), []string{"externo 0", "externo 1", "interno 0", "externo 2", "interno 1", "interno 2"})
} }
func TestContextFail(t *testing.T) { func TestContextAbortWithError(t *testing.T) {
c, w, _ := createTestContext() c, w, _ := createTestContext()
c.AbortWithError(401, errors.New("bad input")) c.AbortWithError(401, errors.New("bad input")).SetMeta("some input")
c.Writer.WriteHeaderNow() c.Writer.WriteHeaderNow()
assert.Equal(t, w.Code, 401) assert.Equal(t, w.Code, 401)
assert.Equal(t, c.LastError().Error(), "bad input")
assert.Equal(t, c.index, AbortIndex) assert.Equal(t, c.index, AbortIndex)
assert.True(t, c.IsAborted()) assert.True(t, c.IsAborted())
} }

View File

@ -21,33 +21,37 @@ const (
) )
// Used internally to collect errors that occurred during an http request. // Used internally to collect errors that occurred during an http request.
type errorMsg struct { type Error struct {
Err error `json:"error"` Err error `json:"error"`
Flags int `json:"-"` Type int `json:"-"`
Metadata interface{} `json:"meta"` Meta interface{} `json:"meta"`
} }
func (msg *errorMsg) Type(flags int) *errorMsg { var _ error = &Error{}
msg.Flags = flags
func (msg *Error) SetType(flags int) *Error {
msg.Type = flags
return msg return msg
} }
func (msg *errorMsg) Meta(data interface{}) *errorMsg { func (msg *Error) SetMeta(data interface{}) *Error {
msg.Metadata = data msg.Meta = data
return msg return msg
} }
func (msg *errorMsg) JSON() interface{} { func (msg *Error) JSON() interface{} {
json := H{} json := H{}
if msg.Metadata != nil { if msg.Meta != nil {
value := reflect.ValueOf(msg.Metadata) value := reflect.ValueOf(msg.Meta)
switch value.Kind() { switch value.Kind() {
case reflect.Struct: case reflect.Struct:
return msg.Metadata return msg.Meta
case reflect.Map: case reflect.Map:
for _, key := range value.MapKeys() { for _, key := range value.MapKeys() {
json[key.String()] = value.MapIndex(key).Interface() json[key.String()] = value.MapIndex(key).Interface()
} }
default:
json["meta"] = msg.Meta
} }
} }
if _, ok := json["error"]; !ok { if _, ok := json["error"]; !ok {
@ -56,11 +60,11 @@ func (msg *errorMsg) JSON() interface{} {
return json return json
} }
func (msg *errorMsg) Error() string { func (msg *Error) Error() string {
return msg.Err.Error() return msg.Err.Error()
} }
type errorMsgs []*errorMsg type errorMsgs []*Error
func (a errorMsgs) ByType(typ int) errorMsgs { func (a errorMsgs) ByType(typ int) errorMsgs {
if len(a) == 0 { if len(a) == 0 {
@ -68,14 +72,14 @@ func (a errorMsgs) ByType(typ int) errorMsgs {
} }
result := make(errorMsgs, 0, len(a)) result := make(errorMsgs, 0, len(a))
for _, msg := range a { for _, msg := range a {
if msg.Flags&typ > 0 { if msg.Type&typ > 0 {
result = append(result, msg) result = append(result, msg)
} }
} }
return result return result
} }
func (a errorMsgs) Last() *errorMsg { func (a errorMsgs) Last() *Error {
length := len(a) length := len(a)
if length == 0 { if length == 0 {
return nil return nil
@ -115,7 +119,10 @@ func (a errorMsgs) String() string {
} }
var buffer bytes.Buffer var buffer bytes.Buffer
for i, msg := range a { 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() return buffer.String()
} }

90
errors_test.go Normal file
View File

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