80 lines
2.1 KiB
Go
80 lines
2.1 KiB
Go
// Package apperr defines AppError, the single error type returned by every
|
|
// service/repo function. Handlers translate AppError into HTTP responses.
|
|
package apperr
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
type AppError struct {
|
|
Code int // HTTP status to return
|
|
Message string // user-facing message (safe to expose)
|
|
Field string // optional: which field caused the error
|
|
RetryAfter int // seconds, for 429
|
|
Err error // original error for logging (never exposed)
|
|
}
|
|
|
|
func (e *AppError) Error() string {
|
|
if e.Err != nil {
|
|
return fmt.Sprintf("%s: %v", e.Message, e.Err)
|
|
}
|
|
return e.Message
|
|
}
|
|
|
|
func (e *AppError) Unwrap() error { return e.Err }
|
|
|
|
// As reports whether err is or wraps an *AppError.
|
|
func As(err error) (*AppError, bool) {
|
|
var ae *AppError
|
|
if errors.As(err, &ae) {
|
|
return ae, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Constructors
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func BadRequest(msg, field string, err error) *AppError {
|
|
return &AppError{Code: http.StatusBadRequest, Message: msg, Field: field, Err: err}
|
|
}
|
|
|
|
func Unauthorized(msg string) *AppError {
|
|
return &AppError{Code: http.StatusUnauthorized, Message: msg}
|
|
}
|
|
|
|
func Forbidden(msg string) *AppError {
|
|
return &AppError{Code: http.StatusForbidden, Message: msg}
|
|
}
|
|
|
|
func NotFound(msg string) *AppError {
|
|
return &AppError{Code: http.StatusNotFound, Message: msg}
|
|
}
|
|
|
|
func Conflict(msg string, err error) *AppError {
|
|
return &AppError{Code: http.StatusConflict, Message: msg, Err: err}
|
|
}
|
|
|
|
func PayloadTooLarge(msg string) *AppError {
|
|
return &AppError{Code: http.StatusRequestEntityTooLarge, Message: msg}
|
|
}
|
|
|
|
func UnprocessableEntity(msg string) *AppError {
|
|
return &AppError{Code: http.StatusUnprocessableEntity, Message: msg}
|
|
}
|
|
|
|
func TooManyRequests(retryAfterSeconds int) *AppError {
|
|
return &AppError{
|
|
Code: http.StatusTooManyRequests,
|
|
Message: "rate limit exceeded",
|
|
RetryAfter: retryAfterSeconds,
|
|
}
|
|
}
|
|
|
|
func Internal(err error) *AppError {
|
|
return &AppError{Code: http.StatusInternalServerError, Message: "internal server error", Err: err}
|
|
}
|