mirror of
https://github.com/gin-gonic/gin.git
synced 2026-04-29 23:23:18 +08:00
fix: unwrap errors.Join() created joined errors in Context.Error() (#4237)
Automatically unwrap errors created by errors.Join() when passed to Context.Error(), so they are added as separate error entries instead of a single combined error. This improves error log readability and structure. Before: Error #01: gin error Error #02: service error store error Error #03: other error After: Error #01: gin error Error #02: service error Error #03: store error Error #04: other error Closes #4237
This commit is contained in:
parent
d3ffc99852
commit
010dea5bd5
16
context.go
16
context.go
@ -254,6 +254,22 @@ func (c *Context) Error(err error) *Error {
|
||||
panic("err is nil")
|
||||
}
|
||||
|
||||
// Unwrap errors.Join() created joinErr
|
||||
type unwrapper interface {
|
||||
Unwrap() []error
|
||||
}
|
||||
if joinErr, ok := err.(unwrapper); ok {
|
||||
errs := joinErr.Unwrap()
|
||||
if len(errs) > 0 {
|
||||
// Recursively add each error from the joined errors
|
||||
for _, e := range errs {
|
||||
c.Error(e)
|
||||
}
|
||||
// Return the last added error
|
||||
return c.Errors.Last()
|
||||
}
|
||||
}
|
||||
|
||||
var parsedError *Error
|
||||
ok := errors.As(err, &parsedError)
|
||||
if !ok {
|
||||
|
||||
@ -7,6 +7,7 @@ package gin
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin/codec/json"
|
||||
@ -138,3 +139,54 @@ func TestErrorUnwrap(t *testing.T) {
|
||||
var testErrNonPointer TestErr
|
||||
require.ErrorAs(t, wrappedErr, &testErrNonPointer)
|
||||
}
|
||||
|
||||
// TestErrorJoinUnwrap tests that gin.Error() automatically unwraps errors.Join() created joined errors.
|
||||
func TestErrorJoinUnwrap(t *testing.T) {
|
||||
c, _ := CreateTestContext(httptest.NewRecorder())
|
||||
|
||||
// Test with errors.Join()
|
||||
err1 := errors.New("service error")
|
||||
err2 := errors.New("store error")
|
||||
joinedErr := errors.Join(err1, err2)
|
||||
|
||||
c.Error(joinedErr)
|
||||
|
||||
// Should be unwrapped into 2 separate errors
|
||||
assert.Len(t, c.Errors, 2)
|
||||
assert.Equal(t, "service error", c.Errors[0].Error())
|
||||
assert.Equal(t, "store error", c.Errors[1].Error())
|
||||
|
||||
// Test mixed usage
|
||||
c2, _ := CreateTestContext(httptest.NewRecorder())
|
||||
c2.Error(errors.New("gin error"))
|
||||
c2.Error(errors.Join(err1, err2))
|
||||
c2.Error(errors.New("other error"))
|
||||
|
||||
assert.Len(t, c2.Errors, 4)
|
||||
expected := `Error #01: gin error
|
||||
Error #02: service error
|
||||
Error #03: store error
|
||||
Error #04: other error
|
||||
`
|
||||
assert.Equal(t, expected, c2.Errors.String())
|
||||
|
||||
// Test empty join (edge case)
|
||||
c3, _ := CreateTestContext(httptest.NewRecorder())
|
||||
emptyJoin := errors.Join() // Creates nil error
|
||||
if emptyJoin != nil {
|
||||
c3.Error(emptyJoin)
|
||||
// errors.Join() with no arguments returns nil, so this shouldn't panic
|
||||
}
|
||||
|
||||
// Test nested joins
|
||||
c4, _ := CreateTestContext(httptest.NewRecorder())
|
||||
err3 := errors.New("nested1")
|
||||
err4 := errors.New("nested2")
|
||||
nestedJoin := errors.Join(errors.Join(err3, err4), errors.New("outer"))
|
||||
c4.Error(nestedJoin)
|
||||
assert.Len(t, c4.Errors, 3)
|
||||
assert.Equal(t, "nested1", c4.Errors[0].Error())
|
||||
assert.Equal(t, "nested2", c4.Errors[1].Error())
|
||||
assert.Equal(t, "outer", c4.Errors[2].Error())
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user