mecha10-core 0.6.2

Mecha10 core runtime and foundational types
Documentation
// Mecha10 Error Types
//
// Provides structured error handling with clear error messages and actionable suggestions.

use std::path::PathBuf;
use thiserror::Error;

/// Main error type for the Mecha10 framework
#[derive(Error, Debug)]
pub enum Mecha10Error {
    // ========================================================================
    // Node Errors
    // ========================================================================
    #[error("Node initialization failed: {0}")]
    NodeInit(String),

    #[error("Node '{node_name}' failed: {message}")]
    NodeFailed { node_name: String, message: String },

    #[error("Node '{0}' not found")]
    NodeNotFound(String),

    // ========================================================================
    // Configuration Errors
    // ========================================================================
    #[error("Configuration error in field '{field}': {message}\n  Expected: {expected}\n  Got: {got}\n  Suggestion: {suggestion}")]
    ConfigValidation {
        field: String,
        message: String,
        expected: String,
        got: String,
        suggestion: String,
    },

    #[error(
        "Configuration file not found: {path}\n  Suggestion: Create the file or set environment variable {env_var}"
    )]
    ConfigNotFound { path: PathBuf, env_var: String },

    #[error("Invalid configuration format: {0}\n  Suggestion: Validate against schema at {1}")]
    ConfigInvalid(String, String),

    #[error("Configuration error: {0}")]
    Configuration(String),

    // ========================================================================
    // Messaging Errors
    // ========================================================================
    #[error("{message}\n  Suggestion: {suggestion}")]
    MessagingError { message: String, suggestion: String },
    #[error("Failed to connect to message broker at {url}\n  → Tried: {url}\n  → Suggestion: {suggestion}")]
    MessagingConnectionFailed { url: String, suggestion: String },

    #[error("Failed to publish message to topic '{topic}': {reason}")]
    PublishFailed { topic: String, reason: String },

    #[error("Failed to subscribe to topic '{topic}': {reason}")]
    SubscribeFailed { topic: String, reason: String },

    #[error("Message deserialization failed for topic '{topic}'\n  Expected type: {expected_type}\n  Error: {error}")]
    DeserializationFailed {
        topic: String,
        expected_type: String,
        error: String,
    },

    #[error("Message serialization failed: {0}")]
    SerializationFailed(String),

    #[error("Serialization error: {message}\n  Suggestion: {suggestion}")]
    SerializationError { message: String, suggestion: String },

    // ========================================================================
    // Circuit Breaker Errors
    // ========================================================================
    #[error("Circuit breaker open for service '{service}': {message}")]
    CircuitBreakerOpen { service: String, message: String },

    // ========================================================================
    // Model Errors
    // ========================================================================
    #[error("Failed to load model from '{uri}': {reason}\n  Suggestion: {suggestion}")]
    ModelLoadFailed {
        uri: String,
        reason: String,
        suggestion: String,
    },

    #[error("Model inference failed: {0}")]
    InferenceFailed(String),

    #[error(
        "Model not found in MLflow registry: {model_name}/{version}\n  Suggestion: Check MLflow UI at {mlflow_url}"
    )]
    ModelNotFound {
        model_name: String,
        version: String,
        mlflow_url: String,
    },

    #[error("Model checksum mismatch\n  Expected: {expected}\n  Got: {got}\n  Suggestion: Re-download the model")]
    ModelChecksumMismatch { expected: String, got: String },

    // ========================================================================
    // Build Errors
    // ========================================================================
    #[error("Build failed for node '{node_name}':\n{stderr}\n  Suggestion: Check the error messages above")]
    BuildFailed { node_name: String, stderr: String },

    #[error("Hot-reload failed: {0}")]
    HotReloadFailed(String),

    // ========================================================================
    // Runtime Errors
    // ========================================================================
    #[error("Runtime error: {0}")]
    Runtime(String),

    #[error("Timeout after {timeout_ms}ms waiting for: {operation}")]
    Timeout { timeout_ms: u64, operation: String },

    #[error("Shutdown error: {0}")]
    Shutdown(String),

    // ========================================================================
    // IO Errors
    // ========================================================================
    #[error("File not found: {path}\n  Suggestion: {suggestion}")]
    FileNotFound { path: PathBuf, suggestion: String },

    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    #[error("IO error: {message}\n  Suggestion: {suggestion}")]
    IoError { message: String, suggestion: String },

    // ========================================================================
    // External Errors
    // ========================================================================
    #[error("JSON error: {0}")]
    Json(#[from] serde_json::Error),

    #[error("YAML error: {0}")]
    Yaml(String),

    #[error("Redis error: {0}")]
    Redis(String),

    #[error("Database error: {0}")]
    Database(String),

    #[error("HTTP error: {0}")]
    Http(String),

    // ========================================================================
    // Generic Errors
    // ========================================================================
    #[error("{0}")]
    Other(String),
}

/// Result type using Mecha10Error
pub type Result<T> = std::result::Result<T, Mecha10Error>;

// ============================================================================
// Helper methods for creating errors with context
// ============================================================================

impl Mecha10Error {
    /// Create a connection error with helpful suggestions
    pub fn connection_failed(url: impl Into<String>) -> Self {
        let url = url.into();

        let suggestion = if url.contains("localhost") || url.contains("127.0.0.1") {
            "Start Redis with `redis-server`\n  → Or set REDIS_URL environment variable".to_string()
        } else {
            format!("Check network connection and ensure Redis is running at {}", url)
        };

        Self::MessagingConnectionFailed { url, suggestion }
    }

    /// Create a config validation error with clear expectations
    pub fn config_validation(
        field: impl Into<String>,
        message: impl Into<String>,
        expected: impl Into<String>,
        got: impl Into<String>,
        suggestion: impl Into<String>,
    ) -> Self {
        Self::ConfigValidation {
            field: field.into(),
            message: message.into(),
            expected: expected.into(),
            got: got.into(),
            suggestion: suggestion.into(),
        }
    }

    /// Create a model load error with helpful suggestions
    pub fn model_load_failed(uri: impl Into<String>, reason: impl Into<String>) -> Self {
        let uri_str = uri.into();
        let reason = reason.into();

        let suggestion = if uri_str.starts_with("mlflow://") {
            "Check MLflow server is running with `mecha10 mlflow start`\n  → Or verify MLFLOW_TRACKING_URI environment variable".to_string()
        } else if uri_str.starts_with("http://") || uri_str.starts_with("https://") {
            "Check network connection and model URL".to_string()
        } else {
            format!("Check file exists: {}", uri_str)
        };

        Self::ModelLoadFailed {
            uri: uri_str,
            reason,
            suggestion,
        }
    }

    /// Create a messaging error with a helpful suggestion
    pub fn messaging_error(message: impl Into<String>, suggestion: impl Into<String>) -> Self {
        Self::MessagingError {
            message: message.into(),
            suggestion: suggestion.into(),
        }
    }

    /// Create a configuration error with context
    pub fn configuration(message: impl Into<String>) -> Self {
        Self::Configuration(message.into())
    }

    /// Create a file not found error with a helpful suggestion
    pub fn file_not_found(path: impl Into<PathBuf>, suggestion: impl Into<String>) -> Self {
        Self::FileNotFound {
            path: path.into(),
            suggestion: suggestion.into(),
        }
    }

    /// Create a config not found error
    pub fn config_not_found(path: impl Into<PathBuf>, env_var: impl Into<String>) -> Self {
        Self::ConfigNotFound {
            path: path.into(),
            env_var: env_var.into(),
        }
    }
}

// ============================================================================
// Conversion from anyhow::Error for compatibility
// ============================================================================

impl From<anyhow::Error> for Mecha10Error {
    fn from(err: anyhow::Error) -> Self {
        Self::Other(err.to_string())
    }
}

// ============================================================================
// Display formatting for better error messages
// ============================================================================