cocoanut 0.2.3

A minimal, declarative macOS GUI framework for Rust
use thiserror::Error;

/// Error types for Cocoanut
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum CocoanutError {
    /// Application not initialized - NSApplication.sharedApplication failed
    #[error("Application not initialized")]
    NotInitialized,

    /// No root view specified - call .root() before .run()
    #[error("No root view specified")]
    NoRootView,

    /// Invalid view configuration with detailed context
    #[error("Invalid view configuration: {0}")]
    InvalidView(String),

    /// AppKit error with detailed message
    #[error("AppKit error: {0}")]
    AppKit(String),

    /// String conversion error (typically from CString::new)
    #[error("String conversion error: {0}")]
    StringConversion(#[from] std::ffi::NulError),

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

    /// Rendering error with context
    #[error("Rendering error: {0}")]
    Rendering(String),

    /// Layout constraint error
    #[error("Layout error: {0}")]
    Layout(String),

    /// Delegate registration error
    #[error("Delegate error: {0}")]
    Delegate(String),

    /// Invalid view tag
    #[error("Invalid view tag: {0}")]
    InvalidTag(isize),

    /// Window creation or management error
    #[error("Window error: {0}")]
    Window(String),

    /// Event registration error
    #[error("Event error: {0}")]
    Event(String),

    /// Resource not found (image, color, etc.)
    #[error("Resource not found: {0}")]
    ResourceNotFound(String),

    /// Thread error during async operations
    #[error("Thread error: {0}")]
    Thread(String),

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

/// Result type alias for Cocoanut
pub type Result<T> = std::result::Result<T, CocoanutError>;

impl From<&str> for CocoanutError {
    fn from(s: &str) -> Self {
        CocoanutError::AppKit(s.to_string())
    }
}

impl From<String> for CocoanutError {
    fn from(s: String) -> Self {
        CocoanutError::AppKit(s)
    }
}

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

    #[test]
    fn test_error_display() {
        let e = CocoanutError::NotInitialized;
        assert_eq!(e.to_string(), "Application not initialized");
    }

    #[test]
    fn test_error_from_str() {
        let e: CocoanutError = "test error".into();
        assert_eq!(e.to_string(), "AppKit error: test error");
    }

    #[test]
    fn test_error_from_string() {
        let e: CocoanutError = String::from("test").into();
        assert_eq!(e.to_string(), "AppKit error: test");
    }

    #[test]
    fn all_variants_display() {
        assert_eq!(
            CocoanutError::InvalidView("bad".into()).to_string(),
            "Invalid view configuration: bad"
        );
        assert_eq!(
            CocoanutError::Rendering("r".into()).to_string(),
            "Rendering error: r"
        );
        assert_eq!(
            CocoanutError::NoRootView.to_string(),
            "No root view specified"
        );
    }

    #[test]
    fn string_conversion_from_nul_error() {
        let err = std::ffi::CString::new("a\0b").unwrap_err();
        let e: CocoanutError = err.into();
        assert!(e.to_string().contains("String conversion"));
    }

    #[test]
    fn serialization_from_json_error() {
        let err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
        let e: CocoanutError = err.into();
        assert!(e.to_string().contains("Serialization"));
    }
}