// 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() }