pub mod exit_codes;
pub use exit_codes::{all_exit_codes, ExitStatus};
use thiserror::Error;
#[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 {
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,
}
}
#[allow(dead_code)]
pub fn is_retryable(&self) -> bool {
self.exit_status().is_retryable()
}
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(),
}
}
}
pub type Result<T> = std::result::Result<T, GdeltError>;