what-core 1.7.0

Core framework for What - an HTML-first web framework powered by Rust
Documentation
//! Error types for what-core

use thiserror::Error;

/// Result type alias for What operations
pub type Result<T> = std::result::Result<T, Error>;

/// Main error type for what-core
#[derive(Error, Debug)]
pub enum Error {
    #[error("Configuration error: {0}")]
    Config(String),

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

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

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

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

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

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

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

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

    #[error("HTTP error: {0}")]
    Http(#[from] reqwest::Error),

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

    #[error("TOML error: {0}")]
    Toml(#[from] toml::de::Error),

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

    #[error("SQLite error: {0}")]
    Sqlite(#[from] rusqlite::Error),

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

    #[error("JWT error: {0}")]
    Jwt(#[from] jsonwebtoken::errors::Error),

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

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

#[cfg(test)]
mod tests {
    use super::*;

    // --- Display formatting tests ---

    #[test]
    fn test_config_error_display() {
        let err = Error::Config("missing field".to_string());
        assert_eq!(err.to_string(), "Configuration error: missing field");
    }

    #[test]
    fn test_parse_error_display() {
        let err = Error::Parse("unexpected token".to_string());
        assert_eq!(err.to_string(), "Parse error: unexpected token");
    }

    #[test]
    fn test_component_error_display() {
        let err = Error::Component("slot not found".to_string());
        assert_eq!(err.to_string(), "Component error: slot not found");
    }

    #[test]
    fn test_template_error_display() {
        let err = Error::Template("invalid syntax".to_string());
        assert_eq!(err.to_string(), "Template error: invalid syntax");
    }

    #[test]
    fn test_data_error_display() {
        let err = Error::Data("source unavailable".to_string());
        assert_eq!(err.to_string(), "Data error: source unavailable");
    }

    #[test]
    fn test_cache_error_display() {
        let err = Error::Cache("connection lost".to_string());
        assert_eq!(err.to_string(), "Cache error: connection lost");
    }

    #[test]
    fn test_action_error_display() {
        let err = Error::Action("handler failed".to_string());
        assert_eq!(err.to_string(), "Action error: handler failed");
    }

    #[test]
    fn test_server_error_display() {
        let err = Error::Server("bind failed".to_string());
        assert_eq!(err.to_string(), "Server error: bind failed");
    }

    #[test]
    fn test_session_error_display() {
        let err = Error::Session("expired".to_string());
        assert_eq!(err.to_string(), "Session error: expired");
    }

    #[test]
    fn test_auth_error_display() {
        let err = Error::Auth("unauthorized".to_string());
        assert_eq!(err.to_string(), "Authentication error: unauthorized");
    }

    // --- From conversion tests ---

    #[test]
    fn test_from_io_error() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
        let err: Error = Error::from(io_err);
        match &err {
            Error::Io(_) => {}
            _ => panic!("Expected Error::Io variant, got: {:?}", err),
        }
        assert!(err.to_string().contains("file not found"));
    }

    #[test]
    fn test_from_json_error() {
        let json_result: std::result::Result<serde_json::Value, _> =
            serde_json::from_str("not json");
        let json_err = json_result.unwrap_err();
        let err: Error = Error::from(json_err);
        match &err {
            Error::Json(_) => {}
            _ => panic!("Expected Error::Json variant, got: {:?}", err),
        }
        assert!(err.to_string().starts_with("JSON error:"));
    }

    #[test]
    fn test_from_toml_error() {
        let toml_result: std::result::Result<toml::Value, _> = toml::from_str("not valid [[[toml");
        let toml_err = toml_result.unwrap_err();
        let err: Error = Error::from(toml_err);
        match &err {
            Error::Toml(_) => {}
            _ => panic!("Expected Error::Toml variant, got: {:?}", err),
        }
        assert!(err.to_string().starts_with("TOML error:"));
    }

    #[test]
    fn test_from_rusqlite_error() {
        let sqlite_err = rusqlite::Error::InvalidParameterName("bad_param".to_string());
        let err: Error = Error::from(sqlite_err);
        match &err {
            Error::Sqlite(_) => {}
            _ => panic!("Expected Error::Sqlite variant, got: {:?}", err),
        }
        assert!(err.to_string().starts_with("SQLite error:"));
    }

    #[test]
    fn test_from_jwt_error() {
        // Decode an invalid token to produce a JWT error
        let jwt_result = jsonwebtoken::decode::<serde_json::Value>(
            "not.a.token",
            &jsonwebtoken::DecodingKey::from_secret(b"secret"),
            &jsonwebtoken::Validation::default(),
        );
        let jwt_err = jwt_result.unwrap_err();
        let err: Error = Error::from(jwt_err);
        match &err {
            Error::Jwt(_) => {}
            _ => panic!("Expected Error::Jwt variant, got: {:?}", err),
        }
        assert!(err.to_string().starts_with("JWT error:"));
    }

    // --- Result type alias test ---

    #[test]
    fn test_result_type_ok() {
        let result: Result<i32> = Ok(42);
        assert_eq!(result.unwrap(), 42);
    }

    #[test]
    fn test_result_type_err() {
        let result: Result<i32> = Err(Error::Config("test".to_string()));
        assert!(result.is_err());
    }

    // --- Debug trait test ---

    #[test]
    fn test_error_is_debuggable() {
        let err = Error::Config("test error".to_string());
        let debug_str = format!("{:?}", err);
        assert!(debug_str.contains("Config"));
        assert!(debug_str.contains("test error"));
    }

    // --- Error with empty message ---

    #[test]
    fn test_error_with_empty_message() {
        let err = Error::Parse(String::new());
        assert_eq!(err.to_string(), "Parse error: ");
    }

    // --- Question mark operator with From conversions ---

    #[test]
    fn test_io_error_via_question_mark() {
        fn may_fail() -> Result<()> {
            let _file = std::fs::File::open("/nonexistent/file/path")?;
            Ok(())
        }
        let result = may_fail();
        assert!(result.is_err());
        match result.unwrap_err() {
            Error::Io(_) => {}
            other => panic!("Expected Error::Io, got: {:?}", other),
        }
    }
}