gdelt 0.1.0

CLI for GDELT Project - optimized for agentic usage with local data caching
//! Error types and exit codes for the GDELT CLI.

pub mod exit_codes;

pub use exit_codes::{all_exit_codes, ExitStatus};

use thiserror::Error;

/// Main error type for the GDELT CLI
#[derive(Error, Debug)]
#[allow(dead_code)]
pub enum GdeltError {
    #[error("Network error: {0}")]
    Network(#[from] reqwest::Error),

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

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

    #[error("CSV error: {0}")]
    Csv(#[from] csv::Error),

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

    #[error("DuckDB error: {0}")]
    DuckDb(#[from] duckdb::Error),

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

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

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

    #[error("API error: {message}")]
    Api { message: String, status_code: Option<u16> },

    #[error("Rate limited: retry after {retry_after_secs} seconds")]
    RateLimited { retry_after_secs: u64 },

    #[error("Resource not found: {0}")]
    NotFound(String),

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

    #[error("Cache miss")]
    CacheMiss,

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

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

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

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

    #[error("Timeout after {0} seconds")]
    Timeout(u64),

    #[error("Operation interrupted")]
    Interrupted,

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

impl GdeltError {
    /// Get the appropriate exit status for this error
    pub fn exit_status(&self) -> ExitStatus {
        match self {
            Self::Network(_) => ExitStatus::NetworkError,
            Self::Io(_) => ExitStatus::GeneralError,
            Self::Json(_) => ExitStatus::ValidationError,
            Self::Csv(_) => ExitStatus::GeneralError,
            Self::Database(_) | Self::DuckDb(_) | Self::Sqlite(_) => ExitStatus::DatabaseError,
            Self::Config(_) => ExitStatus::ConfigError,
            Self::Validation(_) => ExitStatus::ValidationError,
            Self::Api { .. } => ExitStatus::ApiError,
            Self::RateLimited { .. } => ExitStatus::RateLimited,
            Self::NotFound(_) => ExitStatus::NotFound,
            Self::Cache(_) => ExitStatus::CacheError,
            Self::CacheMiss => ExitStatus::CacheMiss,
            Self::Analytics(_) => ExitStatus::AnalyticsError,
            Self::Download(_) => ExitStatus::DownloadError,
            Self::Import(_) => ExitStatus::ImportError,
            Self::Daemon(_) => ExitStatus::GeneralError,
            Self::Timeout(_) => ExitStatus::Timeout,
            Self::Interrupted => ExitStatus::Interrupted,
            Self::Other(_) => ExitStatus::GeneralError,
        }
    }

    /// Check if this error is retryable
    #[allow(dead_code)]
    pub fn is_retryable(&self) -> bool {
        self.exit_status().is_retryable()
    }

    /// Get a user-friendly message with guidance on how to resolve the error
    pub fn user_message(&self) -> String {
        match self {
            Self::RateLimited { retry_after_secs } => {
                format!(
                    "Rate limited. Wait {} seconds or use --wait-on-rate-limit to auto-retry.\n\
                     Tip: Enable caching to reduce API calls, or use local database for repeated queries.",
                    retry_after_secs
                )
            }
            Self::NotFound(msg) => {
                if msg.contains("Event") || msg.contains("event") {
                    format!(
                        "{}.\n\
                         Have you synced data? Try: gdelt data sync",
                        msg
                    )
                } else {
                    msg.clone()
                }
            }
            Self::Api { message, status_code: Some(404) } => {
                format!(
                    "API endpoint not found. This may be a temporary issue or the endpoint may be deprecated.\n\
                     Original error: {}\n\
                     Check GDELT status: https://blog.gdeltproject.org/",
                    message
                )
            }
            Self::Api { message, .. } => {
                if message.contains("HTML instead of JSON") {
                    format!(
                        "{}\n\
                         The API may be temporarily unavailable. Try again later or check GDELT status.",
                        message
                    )
                } else {
                    message.clone()
                }
            }
            Self::Database(msg) => {
                format!(
                    "Database error: {}\n\
                     Try: gdelt db vacuum --analyze\n\
                     If the problem persists, you may need to rebuild: rm ~/.local/share/gdelt/gdelt.duckdb && gdelt data sync",
                    msg
                )
            }
            Self::DuckDb(e) => {
                format!(
                    "Database error: {}\n\
                     Try: gdelt db vacuum --analyze\n\
                     If the problem persists, you may need to rebuild: rm ~/.local/share/gdelt/gdelt.duckdb && gdelt data sync",
                    e
                )
            }
            Self::Network(e) => {
                format!(
                    "Network error: {}\n\
                     Check your internet connection and try again.\n\
                     If behind a proxy, configure it in ~/.config/gdelt/config.toml",
                    e
                )
            }
            Self::Validation(msg) => {
                format!(
                    "Validation error: {}\n\
                     Check your command syntax with: gdelt --help",
                    msg
                )
            }
            _ => self.to_string(),
        }
    }
}

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