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 #[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}