package model import ( "errors" "fmt" "net/http" ) var ( ErrNotFound = errors.New("resource not found") ErrDuplicate = errors.New("resource already exists") ErrUnauthorized = errors.New("unauthorized") ErrForbidden = errors.New("forbidden") ErrUploadTooLarge = errors.New("upload exceeds maximum size") ) // AppError is a service-layer error that carries an HTTP status, a user-safe // message, and an optional internal cause for logging. Handlers unpack it // transparently via api.RespondError. type AppError struct { Status int // HTTP status code Message string // safe for API response Err error // internal cause (nil = user-caused, skip logging) } func (e *AppError) Error() string { return e.Message } func (e *AppError) Unwrap() error { return e.Err } // NewBadRequestError creates a 400 AppError. func NewBadRequestError(message string) *AppError { return &AppError{Status: http.StatusBadRequest, Message: message} } // NewConflictError creates a 409 AppError. func NewConflictError(message string) *AppError { return &AppError{Status: http.StatusConflict, Message: message} } // NewNotFoundError creates a 404 AppError. func NewNotFoundError(message string, cause error) *AppError { return &AppError{Status: http.StatusNotFound, Message: message, Err: cause} } // NewForbiddenError creates a 403 AppError. func NewForbiddenError(message string, cause error) *AppError { return &AppError{Status: http.StatusForbidden, Message: message, Err: cause} } // NewPayloadTooLargeError creates a 413 AppError. func NewPayloadTooLargeError(message string, cause error) *AppError { return &AppError{Status: http.StatusRequestEntityTooLarge, Message: message, Err: cause} } // NewInternalError creates a 500 AppError with a wrapped internal cause. // msg describes the operation that failed; cause is the underlying error. func NewInternalError(msg string, cause error) *AppError { return &AppError{ Status: http.StatusInternalServerError, Message: "internal server error", Err: fmt.Errorf("%s: %w", msg, cause), } }