Compare commits
	
		
			No commits in common. "eea72ac9bccea328bc1c48575dae9f783eaee19d" and "e736978b93a8764cd998e1d7c615a2f07b7cf789" have entirely different histories.
		
	
	
		
			
				eea72ac9bc
			
			...
			
				e736978b93
			
		
	
		
					 7 changed files with 7 additions and 163 deletions
				
			
		
							
								
								
									
										24
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
					@ -1,24 +0,0 @@
 | 
				
			||||||
# Changelog
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## [0.2.0] - 2024-02-07
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Fixed
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- [Using HandlerWithError as Middleware allows response to be sent](https://codeberg.org/danjones000/gin-error-handler/issues/1)
 | 
					 | 
				
			||||||
- Error response still sent if other response is already sent, resulting in duplicate body
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## [0.1.0] - 2024-01-22
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
🎉 Initial release
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Added
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- HandlerWithError
 | 
					 | 
				
			||||||
  + HandlerWithErrorWrapper
 | 
					 | 
				
			||||||
- ErrorMiddleware
 | 
					 | 
				
			||||||
  + Option
 | 
					 | 
				
			||||||
    * Transformer
 | 
					 | 
				
			||||||
      - WithTransformer
 | 
					 | 
				
			||||||
      - WithDefaultTransformer
 | 
					 | 
				
			||||||
    * LoggerFunc
 | 
					 | 
				
			||||||
      - WithLogger
 | 
					 | 
				
			||||||
							
								
								
									
										84
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										84
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
					@ -1,87 +1,3 @@
 | 
				
			||||||
# Gin Error Handler
 | 
					# Gin Error Handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A Gin middleware and wrapper functions to make handling errors easier.
 | 
					A Gin middleware and wrapper functions to make handling errors easier.
 | 
				
			||||||
 | 
					 | 
				
			||||||
## Installation
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Use the module in the usual way: `codeberg.org/danjones000/gin-error-handler`.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## Usage
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```go
 | 
					 | 
				
			||||||
package main
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
    "net/http"
 | 
					 | 
				
			||||||
    handler "codeberg.org/danjones000/gin-error-handler"
 | 
					 | 
				
			||||||
    rErrors "codeberg.org/danjones000/responsable-errors"
 | 
					 | 
				
			||||||
    "github.com/go-playground/validator/v10"
 | 
					 | 
				
			||||||
    "github.com/gin-gonic/gin"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var _ rErrors.ResponsableError = new(vError)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type vError struct {
 | 
					 | 
				
			||||||
    validator.ValidationErrors
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (ve *vError) Msg() string {
 | 
					 | 
				
			||||||
    return "Validation Error"
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (ve *vError) Status() int {
 | 
					 | 
				
			||||||
    return http.StatusBadRequest
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (ve *vError) JSON() any {
 | 
					 | 
				
			||||||
    errs := make([]map[string]string, len(ve))
 | 
					 | 
				
			||||||
    for i, fe := range ve {
 | 
					 | 
				
			||||||
        errs[i] = map[string]string{"field":fe.Field(),"error":fe.Error()}
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return map[string][]map[string]string{"errors":errs}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (ve *vError) MarshalJSON() ([]byte, error) {
 | 
					 | 
				
			||||||
	return json.Marshal(e.JSON())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (ve *vError) Unwrap() error {
 | 
					 | 
				
			||||||
	return ve.ValidationErrors
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func main() {
 | 
					 | 
				
			||||||
    r := gin.New()
 | 
					 | 
				
			||||||
    r.Use(handler.ErrorMiddleware(
 | 
					 | 
				
			||||||
        handler.WithTransformer(func (err error) rErrors.ResponsableError {
 | 
					 | 
				
			||||||
            var ve validator.ValidationErrors
 | 
					 | 
				
			||||||
            if !errors.As(err, &ve) {
 | 
					 | 
				
			||||||
                return nil
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return &vError{ve}
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
        handler.WithDefaultTransformer(),
 | 
					 | 
				
			||||||
        handler.WithLogger(ctx context.Context, err rErrors.ResponsableError) {
 | 
					 | 
				
			||||||
            log.Print(err)
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    r.GET("/user", handler.HandlerWithErrorWrapper(func (c *gin.Context) error {
 | 
					 | 
				
			||||||
        var qu struct {
 | 
					 | 
				
			||||||
            id int `binding:"required"`
 | 
					 | 
				
			||||||
        }{}
 | 
					 | 
				
			||||||
        if err := c.ShouldBindQuery(&qu); err != nil {
 | 
					 | 
				
			||||||
            return err
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        user, err := user.Get(qu.id)
 | 
					 | 
				
			||||||
        if err != nil {
 | 
					 | 
				
			||||||
            return err
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if user == nil {
 | 
					 | 
				
			||||||
            return rErrors.NewNotFound("User not found")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        c.JSON(200, user)
 | 
					 | 
				
			||||||
    }))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
```
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
In our example, if `ShouldBindQuery` fails validation, we'll return a nicely formatted error with each validation error, due to our custom transformer. If no user is found, we'll return a 404, with an error message. And any errors are logged to stdout.
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										2
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
										
									
									
									
								
							| 
						 | 
					@ -3,7 +3,7 @@ module codeberg.org/danjones000/gin-error-handler
 | 
				
			||||||
go 1.21.5
 | 
					go 1.21.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	codeberg.org/danjones000/responsable-errors v0.2.1
 | 
						codeberg.org/danjones000/responsable-errors v0.2.0
 | 
				
			||||||
	github.com/gin-gonic/gin v1.9.1
 | 
						github.com/gin-gonic/gin v1.9.1
 | 
				
			||||||
	github.com/stretchr/testify v1.8.4
 | 
						github.com/stretchr/testify v1.8.4
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										6
									
								
								go.sum
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
										
									
									
									
								
							| 
						 | 
					@ -1,5 +1,7 @@
 | 
				
			||||||
codeberg.org/danjones000/responsable-errors v0.2.1 h1:Ur6t+QZWaAfBgjQWBqSCgS3sTySdkKQD/FQefeRrm90=
 | 
					codeberg.org/danjones000/responsable-errors v0.1.1 h1:WTWo0egPNsp+kEKRK8R+yc/bfdEDUbwEjMnqgA7IklA=
 | 
				
			||||||
codeberg.org/danjones000/responsable-errors v0.2.1/go.mod h1:susEj39A/bflyej4tRirtuVKkmfUdhR2Skljwd/1ndI=
 | 
					codeberg.org/danjones000/responsable-errors v0.1.1/go.mod h1:susEj39A/bflyej4tRirtuVKkmfUdhR2Skljwd/1ndI=
 | 
				
			||||||
 | 
					codeberg.org/danjones000/responsable-errors v0.2.0 h1:WUTBSaKQzkXHDYraHt3mxB4QHqVk78sKR/VeteXM1OY=
 | 
				
			||||||
 | 
					codeberg.org/danjones000/responsable-errors v0.2.0/go.mod h1:susEj39A/bflyej4tRirtuVKkmfUdhR2Skljwd/1ndI=
 | 
				
			||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 | 
					github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 | 
				
			||||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
 | 
					github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
 | 
				
			||||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
 | 
					github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,8 +25,6 @@ func HandlerWithErrorWrapper(h HandlerWithError) gin.HandlerFunc {
 | 
				
			||||||
	return func(c *gin.Context) {
 | 
						return func(c *gin.Context) {
 | 
				
			||||||
		err := h(c)
 | 
							err := h(c)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			// We shouldn't process more handlers if there was an error
 | 
					 | 
				
			||||||
			c.Abort()
 | 
					 | 
				
			||||||
			c.Error(err)
 | 
								c.Error(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										47
									
								
								int_test.go
									
										
									
									
									
								
							
							
						
						
									
										47
									
								
								int_test.go
									
										
									
									
									
								
							| 
						 | 
					@ -1,47 +0,0 @@
 | 
				
			||||||
package handler
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import (
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
	"net/http/httptest"
 | 
					 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	errors "codeberg.org/danjones000/responsable-errors"
 | 
					 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
					 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestErrorMiddleware(t *testing.T) {
 | 
					 | 
				
			||||||
	gin.SetMode(gin.TestMode)
 | 
					 | 
				
			||||||
	w := httptest.NewRecorder()
 | 
					 | 
				
			||||||
	_, r := gin.CreateTestContext(w)
 | 
					 | 
				
			||||||
	r.Use(ErrorMiddleware())
 | 
					 | 
				
			||||||
	r.Use(HandlerWithErrorWrapper(func(c *gin.Context) error {
 | 
					 | 
				
			||||||
		return errors.Errorf(400, "Oops")
 | 
					 | 
				
			||||||
	}))
 | 
					 | 
				
			||||||
	r.GET("/", func(c *gin.Context) {
 | 
					 | 
				
			||||||
		c.JSON(200, gin.H{"Hello": "World"})
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	req, _ := http.NewRequest("GET", "/", nil)
 | 
					 | 
				
			||||||
	r.ServeHTTP(w, req)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	assert.Equal(t, 400, w.Code)
 | 
					 | 
				
			||||||
	assert.Equal(t, `{"error":"Oops"}`, w.Body.String())
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func TestErrorNoResponseIfAlreadyWritten(t *testing.T) {
 | 
					 | 
				
			||||||
	gin.SetMode(gin.TestMode)
 | 
					 | 
				
			||||||
	w := httptest.NewRecorder()
 | 
					 | 
				
			||||||
	_, r := gin.CreateTestContext(w)
 | 
					 | 
				
			||||||
	r.Use(ErrorMiddleware())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	r.GET("/", HandlerWithErrorWrapper(func(c *gin.Context) error {
 | 
					 | 
				
			||||||
		c.JSON(200, gin.H{"Hello": "World"})
 | 
					 | 
				
			||||||
		return errors.Errorf(400, "Oops")
 | 
					 | 
				
			||||||
	}))
 | 
					 | 
				
			||||||
	req, _ := http.NewRequest("GET", "/", nil)
 | 
					 | 
				
			||||||
	r.ServeHTTP(w, req)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	assert.Equal(t, 200, w.Code)
 | 
					 | 
				
			||||||
	assert.Equal(t, `{"Hello":"World"}`, w.Body.String())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -55,8 +55,7 @@ func ErrorMiddleware(opts ...Option) gin.HandlerFunc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		c.Set("rendered_error", re)
 | 
							c.Set("rendered_error", re)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !c.Writer.Written() {
 | 
							// @todo check a response hasn't already been sent
 | 
				
			||||||
		c.JSON(re.Status(), re)
 | 
							c.JSON(re.Status(), re)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue