Skip to main content

mangofetch_core/core/
errors.rs

1use 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}