package middleware import ( "errors" "net" "net/http" "os" "strings" infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors" "github.com/Wei-Shaw/sub2api/internal/pkg/response" "github.com/gin-gonic/gin" ) // Recovery converts panics into the project's standard JSON error envelope. // // It preserves Gin's broken-pipe handling by not attempting to write a response // when the client connection is already gone. func Recovery() gin.HandlerFunc { return gin.CustomRecoveryWithWriter(gin.DefaultErrorWriter, func(c *gin.Context, recovered any) { recoveredErr, _ := recovered.(error) if isBrokenPipe(recoveredErr) { if recoveredErr != nil { _ = c.Error(recoveredErr) } c.Abort() return } if c.Writer.Written() { c.Abort() return } response.ErrorWithDetails( c, http.StatusInternalServerError, infraerrors.UnknownMessage, infraerrors.UnknownReason, nil, ) c.Abort() }) } func isBrokenPipe(err error) bool { if err == nil { return false } var opErr *net.OpError if !errors.As(err, &opErr) { return false } var syscallErr *os.SyscallError if !errors.As(opErr.Err, &syscallErr) { return false } msg := strings.ToLower(syscallErr.Error()) return strings.Contains(msg, "broken pipe") || strings.Contains(msg, "connection reset by peer") }