New errors API!!
This commit is contained in:
parent
5f76ba2022
commit
e94247f9ad
133
context.go
133
context.go
@ -126,6 +126,11 @@ func (c *Context) AbortWithStatus(code int) {
|
|||||||
c.Abort()
|
c.Abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) AbortWithError(code int, err error) *errorMsg {
|
||||||
|
c.AbortWithStatus(code)
|
||||||
|
return c.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) IsAborted() bool {
|
func (c *Context) IsAborted() bool {
|
||||||
return c.index == AbortIndex
|
return c.index == AbortIndex
|
||||||
}
|
}
|
||||||
@ -134,30 +139,16 @@ func (c *Context) IsAborted() bool {
|
|||||||
/********* ERROR MANAGEMENT *********/
|
/********* ERROR MANAGEMENT *********/
|
||||||
/************************************/
|
/************************************/
|
||||||
|
|
||||||
// Fail is the same as Abort plus an error message.
|
|
||||||
// Calling `context.Fail(500, err)` is equivalent to:
|
|
||||||
// ```
|
|
||||||
// context.Error("Operation aborted", err)
|
|
||||||
// context.AbortWithStatus(500)
|
|
||||||
// ```
|
|
||||||
func (c *Context) Fail(code int, err error) {
|
|
||||||
c.Error(err, "Operation aborted")
|
|
||||||
c.AbortWithStatus(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) ErrorTyped(err error, typ int, meta interface{}) {
|
|
||||||
c.Errors = append(c.Errors, errorMsg{
|
|
||||||
Error: err,
|
|
||||||
Flags: typ,
|
|
||||||
Meta: meta,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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, meta interface{}) {
|
func (c *Context) Error(err error) *errorMsg {
|
||||||
c.ErrorTyped(err, ErrorTypeExternal, meta)
|
newError := &errorMsg{
|
||||||
|
Error: err,
|
||||||
|
Flags: ErrorTypePrivate,
|
||||||
|
}
|
||||||
|
c.Errors = append(c.Errors, newError)
|
||||||
|
return newError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) LastError() error {
|
func (c *Context) LastError() error {
|
||||||
@ -168,6 +159,35 @@ func (c *Context) LastError() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/******** METADATA MANAGEMENT********/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
|
// Sets a new pair key/value just for the specified context.
|
||||||
|
// It also lazy initializes the hashmap.
|
||||||
|
func (c *Context) Set(key string, value interface{}) {
|
||||||
|
if c.Keys == nil {
|
||||||
|
c.Keys = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
c.Keys[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the value for the given key or an error if the key does not exist.
|
||||||
|
func (c *Context) Get(key string) (value interface{}, exists bool) {
|
||||||
|
if c.Keys != nil {
|
||||||
|
value, exists = c.Keys[key]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGet returns the value for the given key or panics if the value doesn't exist.
|
||||||
|
func (c *Context) MustGet(key string) interface{} {
|
||||||
|
if value, exists := c.Get(key); exists {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
panic("Key \"" + key + "\" does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
/************ INPUT DATA ************/
|
/************ INPUT DATA ************/
|
||||||
/************************************/
|
/************************************/
|
||||||
@ -233,40 +253,29 @@ func (c *Context) postFormValue(key string) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************/
|
// This function checks the Content-Type to select a binding engine automatically,
|
||||||
/******** METADATA MANAGEMENT********/
|
// Depending the "Content-Type" header different bindings are used:
|
||||||
/************************************/
|
// "application/json" --> JSON binding
|
||||||
|
// "application/xml" --> XML binding
|
||||||
// Sets a new pair key/value just for the specified context.
|
// else --> returns an error
|
||||||
// It also lazy initializes the hashmap.
|
// if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid.
|
||||||
func (c *Context) Set(key string, value interface{}) {
|
func (c *Context) Bind(obj interface{}) error {
|
||||||
if c.Keys == nil {
|
b := binding.Default(c.Request.Method, c.ContentType())
|
||||||
c.Keys = make(map[string]interface{})
|
return c.BindWith(obj, b)
|
||||||
}
|
|
||||||
c.Keys[key] = value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value for the given key or an error if the key does not exist.
|
func (c *Context) BindJSON(obj interface{}) error {
|
||||||
func (c *Context) Get(key string) (value interface{}, exists bool) {
|
return c.BindWith(obj, binding.JSON)
|
||||||
if c.Keys != nil {
|
|
||||||
value, exists = c.Keys[key]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustGet returns the value for the given key or panics if the value doesn't exist.
|
func (c *Context) BindWith(obj interface{}, b binding.Binding) error {
|
||||||
func (c *Context) MustGet(key string) interface{} {
|
if err := b.Bind(c.Request, obj); err != nil {
|
||||||
if value, exists := c.Get(key); exists {
|
c.AbortWithError(400, err).Type(ErrorTypeBind)
|
||||||
return value
|
return err
|
||||||
} else {
|
|
||||||
panic("Key \"" + key + "\" does not exist")
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/************************************/
|
|
||||||
/********* PARSING REQUEST **********/
|
|
||||||
/************************************/
|
|
||||||
|
|
||||||
func (c *Context) ClientIP() string {
|
func (c *Context) ClientIP() string {
|
||||||
clientIP := c.Request.Header.Get("X-Real-IP")
|
clientIP := c.Request.Header.Get("X-Real-IP")
|
||||||
if len(clientIP) > 0 {
|
if len(clientIP) > 0 {
|
||||||
@ -284,25 +293,6 @@ func (c *Context) ContentType() string {
|
|||||||
return filterFlags(c.Request.Header.Get("Content-Type"))
|
return filterFlags(c.Request.Header.Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function checks the Content-Type to select a binding engine automatically,
|
|
||||||
// Depending the "Content-Type" header different bindings are used:
|
|
||||||
// "application/json" --> JSON binding
|
|
||||||
// "application/xml" --> XML binding
|
|
||||||
// else --> returns an error
|
|
||||||
// if Parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. It decodes the json payload into the struct specified as a pointer.Like ParseBody() but this method also writes a 400 error if the json is not valid.
|
|
||||||
func (c *Context) Bind(obj interface{}) bool {
|
|
||||||
b := binding.Default(c.Request.Method, c.ContentType())
|
|
||||||
return c.BindWith(obj, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Context) BindWith(obj interface{}, b binding.Binding) bool {
|
|
||||||
if err := b.Bind(c.Request, obj); err != nil {
|
|
||||||
c.Fail(400, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/************************************/
|
/************************************/
|
||||||
/******** RESPONSE RENDERING ********/
|
/******** RESPONSE RENDERING ********/
|
||||||
/************************************/
|
/************************************/
|
||||||
@ -319,8 +309,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.ErrorTyped(err, ErrorTypeInternal, nil)
|
c.AbortWithError(500, err).Type(ErrorTypeRender)
|
||||||
c.AbortWithStatus(500)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +419,7 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
|||||||
c.XML(code, data)
|
c.XML(code, data)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
c.Fail(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))
|
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,6 +447,10 @@ func (c *Context) SetAccepted(formats ...string) {
|
|||||||
c.Accepted = formats
|
c.Accepted = formats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************************/
|
||||||
|
/******** CONTENT NEGOTIATION *******/
|
||||||
|
/************************************/
|
||||||
|
|
||||||
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func TestContextReset(t *testing.T) {
|
|||||||
c.index = 2
|
c.index = 2
|
||||||
c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()}
|
c.Writer = &responseWriter{ResponseWriter: httptest.NewRecorder()}
|
||||||
c.Params = Params{Param{}}
|
c.Params = Params{Param{}}
|
||||||
c.Error(errors.New("test"), nil)
|
c.Error(errors.New("test"))
|
||||||
c.Set("foo", "bar")
|
c.Set("foo", "bar")
|
||||||
c.reset()
|
c.reset()
|
||||||
|
|
||||||
@ -352,41 +352,41 @@ func TestContextError(t *testing.T) {
|
|||||||
assert.Nil(t, c.LastError())
|
assert.Nil(t, c.LastError())
|
||||||
assert.Empty(t, c.Errors.String())
|
assert.Empty(t, c.Errors.String())
|
||||||
|
|
||||||
c.Error(errors.New("first error"), "some data")
|
c.Error(errors.New("first error")).Meta("some data")
|
||||||
assert.Equal(t, c.LastError().Error(), "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 Meta: some data\n")
|
||||||
|
|
||||||
c.Error(errors.New("second error"), "some data 2")
|
c.Error(errors.New("second error")).Meta("some data 2")
|
||||||
assert.Equal(t, c.LastError().Error(), "second error")
|
assert.Equal(t, c.LastError().Error(), "second error")
|
||||||
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"+
|
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")
|
"Error #02: second error\n Meta: some data 2\n")
|
||||||
|
|
||||||
assert.Equal(t, c.Errors[0].Error, errors.New("first error"))
|
assert.Equal(t, c.Errors[0].Error, errors.New("first error"))
|
||||||
assert.Equal(t, c.Errors[0].Meta, "some data")
|
assert.Equal(t, c.Errors[0].Metadata, "some data")
|
||||||
assert.Equal(t, c.Errors[0].Flags, ErrorTypeExternal)
|
assert.Equal(t, c.Errors[0].Flags, ErrorTypePrivate)
|
||||||
|
|
||||||
assert.Equal(t, c.Errors[1].Error, errors.New("second error"))
|
assert.Equal(t, c.Errors[1].Error, errors.New("second error"))
|
||||||
assert.Equal(t, c.Errors[1].Meta, "some data 2")
|
assert.Equal(t, c.Errors[1].Metadata, "some data 2")
|
||||||
assert.Equal(t, c.Errors[1].Flags, ErrorTypeExternal)
|
assert.Equal(t, c.Errors[1].Flags, ErrorTypePrivate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextTypedError(t *testing.T) {
|
func TestContextTypedError(t *testing.T) {
|
||||||
c, _, _ := createTestContext()
|
c, _, _ := createTestContext()
|
||||||
c.ErrorTyped(errors.New("externo 0"), ErrorTypeExternal, nil)
|
c.Error(errors.New("externo 0")).Type(ErrorTypePublic)
|
||||||
c.ErrorTyped(errors.New("externo 1"), ErrorTypeExternal, nil)
|
c.Error(errors.New("externo 1")).Type(ErrorTypePublic)
|
||||||
c.ErrorTyped(errors.New("interno 0"), ErrorTypeInternal, nil)
|
c.Error(errors.New("interno 0")).Type(ErrorTypePrivate)
|
||||||
c.ErrorTyped(errors.New("externo 2"), ErrorTypeExternal, nil)
|
c.Error(errors.New("externo 2")).Type(ErrorTypePublic)
|
||||||
c.ErrorTyped(errors.New("interno 1"), ErrorTypeInternal, nil)
|
c.Error(errors.New("interno 1")).Type(ErrorTypePrivate)
|
||||||
c.ErrorTyped(errors.New("interno 2"), ErrorTypeInternal, nil)
|
c.Error(errors.New("interno 2")).Type(ErrorTypePrivate)
|
||||||
|
|
||||||
for _, err := range c.Errors.ByType(ErrorTypeExternal) {
|
for _, err := range c.Errors.ByType(ErrorTypePublic) {
|
||||||
assert.Equal(t, err.Flags, ErrorTypeExternal)
|
assert.Equal(t, err.Flags, ErrorTypePublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, err := range c.Errors.ByType(ErrorTypeInternal) {
|
for _, err := range c.Errors.ByType(ErrorTypePrivate) {
|
||||||
assert.Equal(t, err.Flags, ErrorTypeInternal)
|
assert.Equal(t, err.Flags, 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", "externo 1", "interno 0", "externo 2", "interno 1", "interno 2"})
|
||||||
@ -394,7 +394,7 @@ func TestContextTypedError(t *testing.T) {
|
|||||||
|
|
||||||
func TestContextFail(t *testing.T) {
|
func TestContextFail(t *testing.T) {
|
||||||
c, w, _ := createTestContext()
|
c, w, _ := createTestContext()
|
||||||
c.Fail(401, errors.New("bad input"))
|
c.AbortWithError(401, errors.New("bad input"))
|
||||||
c.Writer.WriteHeaderNow()
|
c.Writer.WriteHeaderNow()
|
||||||
|
|
||||||
assert.Equal(t, w.Code, 401)
|
assert.Equal(t, w.Code, 401)
|
||||||
@ -434,7 +434,7 @@ func TestContextAutoBind(t *testing.T) {
|
|||||||
Foo string `json:"foo"`
|
Foo string `json:"foo"`
|
||||||
Bar string `json:"bar"`
|
Bar string `json:"bar"`
|
||||||
}
|
}
|
||||||
assert.True(t, c.Bind(&obj))
|
assert.NoError(t, c.Bind(&obj))
|
||||||
assert.Equal(t, obj.Bar, "foo")
|
assert.Equal(t, obj.Bar, "foo")
|
||||||
assert.Equal(t, obj.Foo, "bar")
|
assert.Equal(t, obj.Foo, "bar")
|
||||||
assert.Equal(t, w.Body.Len(), 0)
|
assert.Equal(t, w.Body.Len(), 0)
|
||||||
@ -450,7 +450,7 @@ func TestContextBadAutoBind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert.False(t, c.IsAborted())
|
assert.False(t, c.IsAborted())
|
||||||
assert.False(t, c.Bind(&obj))
|
assert.Error(t, c.Bind(&obj))
|
||||||
c.Writer.WriteHeaderNow()
|
c.Writer.WriteHeaderNow()
|
||||||
|
|
||||||
assert.Empty(t, obj.Bar)
|
assert.Empty(t, obj.Bar)
|
||||||
@ -467,7 +467,7 @@ func TestContextBindWith(t *testing.T) {
|
|||||||
Foo string `json:"foo"`
|
Foo string `json:"foo"`
|
||||||
Bar string `json:"bar"`
|
Bar string `json:"bar"`
|
||||||
}
|
}
|
||||||
assert.True(t, c.BindWith(&obj, binding.JSON))
|
assert.NoError(t, c.BindWith(&obj, binding.JSON))
|
||||||
assert.Equal(t, obj.Bar, "foo")
|
assert.Equal(t, obj.Bar, "foo")
|
||||||
assert.Equal(t, obj.Foo, "bar")
|
assert.Equal(t, obj.Foo, "bar")
|
||||||
assert.Equal(t, w.Body.Len(), 0)
|
assert.Equal(t, w.Body.Len(), 0)
|
||||||
|
24
errors.go
24
errors.go
@ -10,19 +10,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ErrorTypeInternal = 1 << iota
|
ErrorTypeBind = 1 << 31
|
||||||
ErrorTypeExternal = 1 << iota
|
ErrorTypeRender = 1 << 30
|
||||||
|
ErrorTypePrivate = 1 << 0
|
||||||
|
ErrorTypePublic = 1 << 1
|
||||||
|
|
||||||
ErrorTypeAny = 0xffffffff
|
ErrorTypeAny = 0xffffffff
|
||||||
|
ErrorTypeNu = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 errorMsg struct {
|
||||||
Error error `json:"error"`
|
Error error `json:"error"`
|
||||||
Flags int `json:"-"`
|
Flags int `json:"-"`
|
||||||
Meta interface{} `json:"meta"`
|
Metadata interface{} `json:"meta"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type errorMsgs []errorMsg
|
func (msg *errorMsg) Type(flags int) *errorMsg {
|
||||||
|
msg.Flags = flags
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msg *errorMsg) Meta(data interface{}) *errorMsg {
|
||||||
|
msg.Metadata = data
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorMsgs []*errorMsg
|
||||||
|
|
||||||
func (a errorMsgs) ByType(typ int) errorMsgs {
|
func (a errorMsgs) ByType(typ int) errorMsgs {
|
||||||
if len(a) == 0 {
|
if len(a) == 0 {
|
||||||
@ -54,7 +68,7 @@ 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.Error, msg.Meta)
|
fmt.Fprintf(&buffer, "Error #%02d: %s\n Meta: %v\n", (i + 1), msg.Error, msg.Metadata)
|
||||||
}
|
}
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ func TestMiddlewareFailHandlersChain(t *testing.T) {
|
|||||||
router := New()
|
router := New()
|
||||||
router.Use(func(context *Context) {
|
router.Use(func(context *Context) {
|
||||||
signature += "A"
|
signature += "A"
|
||||||
context.Fail(500, errors.New("foo"))
|
context.AbortWithError(500, errors.New("foo"))
|
||||||
})
|
})
|
||||||
router.Use(func(context *Context) {
|
router.Use(func(context *Context) {
|
||||||
signature += "B"
|
signature += "B"
|
||||||
|
Loading…
Reference in New Issue
Block a user