Skip to main content

ani_lib/
error.rs

1use std::error::Error as StdError;
2
3use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, AniError>;
6
7#[derive(Debug, Error)]
8pub enum AniError {
9    #[error("network request failed")]
10    Network(String),
11    #[error("malformed provider data")]
12    Provider(String),
13    #[error("catalog error")]
14    Catalog { provider: String, message: String },
15    #[error("provider rate limited")]
16    ProviderRateLimited {
17        provider: String,
18        retry_after_seconds: u64,
19    },
20    #[error("episode unavailable")]
21    Unavailable(String),
22    #[error("player failed")]
23    Player(String),
24    #[error("download failed")]
25    Download(String),
26    #[error("history operation failed")]
27    History(String),
28    #[error("update failed")]
29    Update(String),
30    #[error("invalid input")]
31    Input(String),
32    #[error("I/O error")]
33    Io(#[from] std::io::Error),
34    #[error("JSON error")]
35    Json(#[from] serde_json::Error),
36    #[error("URL error")]
37    Url(#[from] url::ParseError),
38    // Specific user-facing error variants
39    #[error("HLS downloads require yt-dlp or FFmpeg")]
40    DownloadNoDownloader,
41    #[error("HLS download failed")]
42    DownloadFailed,
43    #[error("could not determine state directory")]
44    HistoryStateDirectory,
45    #[error("player executable not found")]
46    PlayerNotFound,
47    #[error("player launch failed")]
48    PlayerLaunchFailed,
49    #[error("player exited with error")]
50    PlayerExitFailed,
51    #[error("Android playback requires interactive terminal")]
52    PlayerAndroidTerminalRequired,
53    #[error("selection out of range")]
54    InputSelectionOutOfRange,
55    #[error("empty search query")]
56    InputEmptyQuery,
57    #[error("episode selection invalid")]
58    InputInvalidEpisode,
59    #[error("command requires query")]
60    InputRequiresQuery,
61    #[error("no results found")]
62    UnavailableNoResults,
63    #[error("no streams available")]
64    UnavailableNoStreams,
65    #[error("no episodes available")]
66    UnavailableNoEpisodes,
67}
68
69impl AniError {
70    pub fn key(&self) -> &'static str {
71        match self {
72            Self::Network(_) => "errors.network",
73            Self::Provider(_) => "errors.provider",
74            Self::Catalog { .. } => "errors.catalog",
75            Self::ProviderRateLimited { .. } => "errors.provider.rate_limited",
76            Self::Unavailable(_) => "errors.unavailable",
77            Self::Player(_) => "errors.player",
78            Self::Download(_) => "errors.download",
79            Self::History(_) => "errors.history",
80            Self::Update(_) => "errors.update",
81            Self::Input(_) => "errors.input",
82            Self::Io(_) => "errors.io",
83            Self::Json(_) => "errors.json",
84            Self::Url(_) => "errors.url",
85            Self::DownloadNoDownloader => "errors.download.no_downloader",
86            Self::DownloadFailed => "errors.download.failed",
87            Self::HistoryStateDirectory => "errors.history.state_directory",
88            Self::PlayerNotFound => "errors.player.not_found",
89            Self::PlayerLaunchFailed => "errors.player.launch_failed",
90            Self::PlayerExitFailed => "errors.player.exit_failed",
91            Self::PlayerAndroidTerminalRequired => "errors.player.android_terminal_required",
92            Self::InputSelectionOutOfRange => "errors.input.selection_out_of_range",
93            Self::InputEmptyQuery => "errors.input.empty_query",
94            Self::InputInvalidEpisode => "errors.input.invalid_episode",
95            Self::InputRequiresQuery => "errors.input.requires_query",
96            Self::UnavailableNoResults => "errors.unavailable.no_results",
97            Self::UnavailableNoStreams => "errors.unavailable.no_streams",
98            Self::UnavailableNoEpisodes => "errors.unavailable.no_episodes",
99        }
100    }
101}
102
103impl From<reqwest::Error> for AniError {
104    fn from(error: reqwest::Error) -> Self {
105        let mut message = error.to_string();
106        let mut source = error.source();
107        while let Some(cause) = source {
108            let cause_message = cause.to_string();
109            if !message.ends_with(&cause_message) {
110                message.push_str(": ");
111                message.push_str(&cause_message);
112            }
113            source = cause.source();
114        }
115        Self::Network(message)
116    }
117}