mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-05 16:02:48 +00:00
99 lines
2.8 KiB
Go
99 lines
2.8 KiB
Go
package errx
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// Error represents a business error with a code and message.
|
||
type Error struct {
|
||
Code Code `json:"code"`
|
||
Message string `json:"message"`
|
||
// Identifier optionally overrides the machine-readable `code` in the JSON
|
||
// response. Without it every error of the same HTTP class is indistinguishable
|
||
// to a client, so a caller that needs to branch on a specific condition has
|
||
// nothing stable to match on. Empty means "derive it from Code".
|
||
Identifier string `json:"-"`
|
||
}
|
||
|
||
// Error implements error interface.
|
||
func (e *Error) Error() string {
|
||
return fmt.Sprintf("%s (%d): %s", codeToString[e.Code], e.Code, e.Message)
|
||
}
|
||
|
||
// New creates a new business error.
|
||
func New(code Code, message string) *Error {
|
||
return &Error{Code: code, Message: message}
|
||
}
|
||
|
||
// NewWithIdentifier creates a business error carrying its own machine-readable
|
||
// identifier, for conditions a client is expected to detect and handle
|
||
// specifically rather than just display.
|
||
func NewWithIdentifier(code Code, identifier, message string) *Error {
|
||
return &Error{Code: code, Message: message, Identifier: identifier}
|
||
}
|
||
|
||
// identifier returns the response `code`: the error's own when set, otherwise
|
||
// the generic one for its HTTP class.
|
||
func (e *Error) identifier() string {
|
||
if e.Identifier != "" {
|
||
return e.Identifier
|
||
}
|
||
return codeToIdentifier[e.Code]
|
||
}
|
||
|
||
// --- Predefined errors (exported) ---
|
||
var (
|
||
ErrUnauthorized = New(Unauthorized, "Token not found.")
|
||
ErrForbidden = New(Forbidden, "You don't have access to this feature.")
|
||
ErrNotFound = New(NotFound, "Resource not found.")
|
||
ErrConflict = New(Conflict, "resource already exists")
|
||
ErrUnprocessable = New(Unprocessable, "validation failed")
|
||
ErrServiceDown = New(ServiceUnavailable, "service unavailable")
|
||
)
|
||
|
||
// --- Gin handler helper ---
|
||
type response struct {
|
||
Error string `json:"error"`
|
||
Message string `json:"message"`
|
||
Code string `json:"code"`
|
||
RequestID string `json:"request_id,omitempty"`
|
||
}
|
||
|
||
func InternalError() *Error {
|
||
return New(Internal, "Something went wrong.")
|
||
}
|
||
|
||
func Handle(c *gin.Context, err error) {
|
||
var bizErr *Error
|
||
if errors.As(err, &bizErr) {
|
||
// Business error – send clean JSON
|
||
httpCode := codeToHTTP[bizErr.Code]
|
||
httpError := codeToString[bizErr.Code]
|
||
c.JSON(httpCode, response{
|
||
Error: httpError,
|
||
Message: bizErr.Message,
|
||
Code: bizErr.identifier(),
|
||
RequestID: c.GetString("request_id"),
|
||
})
|
||
return
|
||
}
|
||
|
||
// Unexpected error → treat as internal
|
||
Handle(c, InternalError())
|
||
}
|
||
|
||
// JSON sends a business error as JSON response
|
||
func JSON(c *gin.Context, err *Error) {
|
||
httpCode := codeToHTTP[err.Code]
|
||
httpError := codeToString[err.Code]
|
||
c.JSON(httpCode, response{
|
||
Error: httpError,
|
||
Message: err.Message,
|
||
Code: err.identifier(),
|
||
RequestID: c.GetString("request_id"),
|
||
})
|
||
}
|