mangofetch_core/core/
errors.rs1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum MangoError {
5 #[error("IO error: {0}")]
6 Io(#[from] std::io::Error),
7
8 #[error("Network error: {0}")]
9 Network(#[from] reqwest::Error),
10
11 #[error("Serialization error: {0}")]
12 Serialization(#[from] serde_json::Error),
13
14 #[error("Dependency missing: {0}")]
15 DependencyMissing(String),
16
17 #[error("Process error: {0}")]
18 Process(String),
19
20 #[error("Download error: {0}")]
21 Download(String),
22
23 #[error("Authentication required: {0}")]
24 AuthRequired(String),
25
26 #[error("Rate limited: {0}")]
27 RateLimited(String),
28
29 #[error("Content restricted: {0}")]
30 Restricted(String),
31
32 #[error("Content not found: {0}")]
33 NotFound(String),
34
35 #[error("FFmpeg error: {0}")]
36 FFmpeg(String),
37
38 #[error("Internal error: {0}")]
39 Internal(String),
40
41 #[error("{0}")]
42 Custom(String),
43}
44
45pub type MangoResult<T> = Result<T, MangoError>;
46
47pub fn classify_download_error(error: &str) -> (&str, &str) {
48 let lower = error.to_lowercase();
49
50 if lower.contains("cookie")
51 || lower.contains("login")
52 || lower.contains("sign in")
53 || lower.contains("authentication")
54 || lower.contains("403")
55 {
56 return ("auth_required", "This content requires login. Install the browser extension and visit the site while logged in.");
57 }
58
59 if lower.contains("captcha")
60 || lower.contains("blocking")
61 || lower.contains("rate limit")
62 || lower.contains("429")
63 || lower.contains("too many")
64 {
65 return (
66 "rate_limited",
67 "Too many requests. Try again in a few minutes.",
68 );
69 }
70
71 if lower.contains("private") || lower.contains("restricted") || lower.contains("age") {
72 return ("restricted", "This content is private or age-restricted.");
73 }
74
75 if lower.contains("downloaded file") && lower.contains("not found") {
76 return (
77 "file_missing",
78 "Downloaded file could not be located in the output folder.",
79 );
80 }
81
82 if lower.contains("not found")
83 || lower.contains("404")
84 || lower.contains("unavailable")
85 || lower.contains("deleted")
86 {
87 return ("not_found", "Content not found or has been deleted.");
88 }
89
90 if lower.contains("ffmpeg") || lower.contains("mux") || lower.contains("merge") {
91 return (
92 "ffmpeg_needed",
93 "FFmpeg is required for this download. Install it from Settings.",
94 );
95 }
96
97 if lower.contains("yt-dlp") || lower.contains("ytdlp") || lower.contains("no downloader") {
98 return (
99 "ytdlp_needed",
100 "yt-dlp is required. Install it from Settings.",
101 );
102 }
103
104 if lower.contains("nsig") || lower.contains("signature") || lower.contains("cipher") {
105 return (
106 "ytdlp_outdated",
107 "yt-dlp needs updating. Restart the app to auto-update.",
108 );
109 }
110
111 ("unknown", error)
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn test_classify_download_error_auth_required() {
120 let (code, msg) = classify_download_error("Sign in to confirm you're not a bot");
121 assert_eq!(code, "auth_required");
122 assert!(msg.contains("requires login"));
123
124 let (code, _) = classify_download_error("ERROR: 403 Forbidden");
125 assert_eq!(code, "auth_required");
126 }
127
128 #[test]
129 fn test_classify_download_error_rate_limited() {
130 let (code, msg) = classify_download_error("HTTP Error 429: Too Many Requests");
131 assert_eq!(code, "rate_limited");
132 assert!(msg.contains("Too many requests"));
133
134 let (code, _) = classify_download_error("captcha challenge failed");
135 assert_eq!(code, "rate_limited");
136 }
137
138 #[test]
139 fn test_classify_download_error_restricted() {
140 let (code, msg) = classify_download_error("Video is private");
141 assert_eq!(code, "restricted");
142 assert!(msg.contains("private or age-restricted"));
143
144 let (code, _) = classify_download_error("age-restricted content");
145 assert_eq!(code, "restricted");
146 }
147
148 #[test]
149 fn test_classify_download_error_file_missing() {
150 let (code, msg) = classify_download_error("Downloaded file not found");
151 assert_eq!(code, "file_missing");
152 assert!(msg.contains("could not be located"));
153 }
154
155 #[test]
156 fn test_classify_download_error_not_found() {
157 let (code, msg) = classify_download_error("Video not found");
158 assert_eq!(code, "not_found");
159 assert!(msg.contains("Content not found"));
160
161 let (code, _) = classify_download_error("ERROR: 404 Not Found");
162 assert_eq!(code, "not_found");
163
164 let (code, _) = classify_download_error("Video unavailable");
165 assert_eq!(code, "not_found");
166 }
167
168 #[test]
169 fn test_classify_download_error_ffmpeg_needed() {
170 let (code, msg) = classify_download_error("ffmpeg is not installed");
171 assert_eq!(code, "ffmpeg_needed");
172 assert!(msg.contains("FFmpeg is required"));
173
174 let (code, _) = classify_download_error("Failed to merge formats");
175 assert_eq!(code, "ffmpeg_needed");
176 }
177
178 #[test]
179 fn test_classify_download_error_ytdlp_needed() {
180 let (code, msg) = classify_download_error("yt-dlp missing");
181 assert_eq!(code, "ytdlp_needed");
182 assert!(msg.contains("yt-dlp is required"));
183 }
184
185 #[test]
186 fn test_classify_download_error_ytdlp_outdated() {
187 let (code, msg) = classify_download_error("Cannot extract nsig");
188 assert_eq!(code, "ytdlp_outdated");
189 assert!(msg.contains("needs updating"));
190
191 let (code, _) = classify_download_error("Unable to extract signature");
192 assert_eq!(code, "ytdlp_outdated");
193 }
194
195 #[test]
196 fn test_classify_download_error_unknown() {
197 let original_error = "Something completely different went wrong";
198 let (code, msg) = classify_download_error(original_error);
199 assert_eq!(code, "unknown");
200 assert_eq!(msg, original_error);
201 }
202}