Files
node/errors/errors.go
T

237 lines
6.4 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package errors provides common error types and utilities for the Lux node.
package errors
import (
"errors"
"fmt"
)
// Common sentinel errors used throughout the codebase
var (
// Database errors
ErrNotFound = errors.New("not found")
ErrClosed = errors.New("closed")
ErrCorrupted = errors.New("corrupted")
ErrReadOnly = errors.New("read only")
ErrDiskFull = errors.New("disk full")
ErrInvalidKey = errors.New("invalid key")
ErrInvalidValue = errors.New("invalid value")
// Network errors
ErrTimeout = errors.New("timeout")
ErrConnectionClosed = errors.New("connection closed")
ErrConnectionReset = errors.New("connection reset")
ErrNoRoute = errors.New("no route to host")
ErrRefused = errors.New("connection refused")
ErrNetworkDown = errors.New("network down")
// Validation errors
ErrInvalidInput = errors.New("invalid input")
ErrInvalidSignature = errors.New("invalid signature")
ErrInvalidChecksum = errors.New("invalid checksum")
ErrInvalidFormat = errors.New("invalid format")
ErrMissingField = errors.New("missing required field")
ErrFieldTooLarge = errors.New("field exceeds maximum size")
// State errors
ErrNotInitialized = errors.New("not initialized")
ErrAlreadyExists = errors.New("already exists")
ErrNotSupported = errors.New("not supported")
ErrDeprecated = errors.New("deprecated")
ErrConflict = errors.New("conflict")
ErrCanceled = errors.New("canceled")
// Resource errors
ErrResourceExhausted = errors.New("resource exhausted")
ErrQuotaExceeded = errors.New("quota exceeded")
ErrRateLimited = errors.New("rate limited")
ErrOutOfMemory = errors.New("out of memory")
// Permission errors
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
ErrAccessDenied = errors.New("access denied")
)
// Error categories for grouping related errors
type Category string
const (
CategoryDatabase Category = "database"
CategoryNetwork Category = "network"
CategoryValidation Category = "validation"
CategoryState Category = "state"
CategoryResource Category = "resource"
CategoryPermission Category = "permission"
CategoryInternal Category = "internal"
CategoryUnknown Category = "unknown"
)
// WrappedError provides context around an error
type WrappedError struct {
Err error
Category Category
Message string
Context map[string]interface{}
}
// Error implements the error interface
func (e *WrappedError) Error() string {
if e.Message != "" {
return fmt.Sprintf("[%s] %s: %v", e.Category, e.Message, e.Err)
}
return fmt.Sprintf("[%s] %v", e.Category, e.Err)
}
// Unwrap returns the underlying error
func (e *WrappedError) Unwrap() error {
return e.Err
}
// Is checks if the error matches a target error
func (e *WrappedError) Is(target error) bool {
return errors.Is(e.Err, target)
}
// Wrap creates a new WrappedError with the given category and message
func Wrap(err error, category Category, message string) error {
if err == nil {
return nil
}
return &WrappedError{
Err: err,
Category: category,
Message: message,
Context: make(map[string]interface{}),
}
}
// WrapWithContext creates a new WrappedError with context information
func WrapWithContext(err error, category Category, message string, context map[string]interface{}) error {
if err == nil {
return nil
}
return &WrappedError{
Err: err,
Category: category,
Message: message,
Context: context,
}
}
// IsNotFound checks if an error is a "not found" error
func IsNotFound(err error) bool {
return errors.Is(err, ErrNotFound)
}
// IsClosed checks if an error indicates a closed resource
func IsClosed(err error) bool {
return errors.Is(err, ErrClosed)
}
// IsTimeout checks if an error is a timeout
func IsTimeout(err error) bool {
return errors.Is(err, ErrTimeout)
}
// IsTemporary checks if an error is temporary and can be retried
func IsTemporary(err error) bool {
// Check for common temporary errors
if errors.Is(err, ErrTimeout) ||
errors.Is(err, ErrRateLimited) ||
errors.Is(err, ErrResourceExhausted) {
return true
}
// Check if error implements Temporary() method
type temporary interface {
Temporary() bool
}
if temp, ok := err.(temporary); ok {
return temp.Temporary()
}
return false
}
// IsPermanent checks if an error is permanent and should not be retried
func IsPermanent(err error) bool {
// Check for common permanent errors
return errors.Is(err, ErrNotSupported) ||
errors.Is(err, ErrDeprecated) ||
errors.Is(err, ErrInvalidInput) ||
errors.Is(err, ErrInvalidSignature) ||
errors.Is(err, ErrInvalidFormat) ||
errors.Is(err, ErrForbidden) ||
errors.Is(err, ErrUnauthorized)
}
// GetCategory returns the category of an error
func GetCategory(err error) Category {
var wrapped *WrappedError
if errors.As(err, &wrapped) {
return wrapped.Category
}
// Try to infer category from error type
switch {
case errors.Is(err, ErrNotFound) || errors.Is(err, ErrClosed):
return CategoryDatabase
case errors.Is(err, ErrTimeout) || errors.Is(err, ErrConnectionClosed):
return CategoryNetwork
case errors.Is(err, ErrInvalidInput) || errors.Is(err, ErrInvalidSignature):
return CategoryValidation
case errors.Is(err, ErrNotInitialized) || errors.Is(err, ErrAlreadyExists):
return CategoryState
case errors.Is(err, ErrResourceExhausted) || errors.Is(err, ErrOutOfMemory):
return CategoryResource
case errors.Is(err, ErrUnauthorized) || errors.Is(err, ErrForbidden):
return CategoryPermission
default:
return CategoryUnknown
}
}
// Multi combines multiple errors into a single error
type Multi struct {
Errors []error
}
// Error implements the error interface
func (m *Multi) Error() string {
if len(m.Errors) == 0 {
return "no errors"
}
if len(m.Errors) == 1 {
return m.Errors[0].Error()
}
return fmt.Sprintf("multiple errors: %v", m.Errors)
}
// Add adds an error to the multi-error
func (m *Multi) Add(err error) {
if err != nil {
m.Errors = append(m.Errors, err)
}
}
// Err returns nil if there are no errors, otherwise returns the Multi error
func (m *Multi) Err() error {
if len(m.Errors) == 0 {
return nil
}
return m
}
// Join combines multiple errors into a single error
func Join(errs ...error) error {
multi := &Multi{}
for _, err := range errs {
multi.Add(err)
}
return multi.Err()
}