1use miette::Diagnostic;
2use thiserror::Error;
3
4#[derive(Error, Diagnostic, Debug)]
5pub enum DownloadError {
6 #[error("Invalid URL: {url}")]
7 #[diagnostic(code(soar_dl::invalid_url))]
8 InvalidUrl {
9 url: String,
10 #[source]
11 source: url::ParseError,
12 },
13
14 #[error(transparent)]
15 #[diagnostic(code(soar_dl::extract_error))]
16 ExtractError(#[from] compak::error::ArchiveError),
17
18 #[error(transparent)]
19 #[diagnostic(
20 code(soar_dl::network),
21 help("Check your internet connection or try again later")
22 )]
23 Network(#[from] Box<ureq::Error>),
24
25 #[error("HTTP {status}: {url}")]
26 #[diagnostic(code(soar_dl::http_error))]
27 HttpError { status: u16, url: String },
28
29 #[error(transparent)]
30 #[diagnostic(code(soar_dl::io))]
31 Io(#[from] std::io::Error),
32
33 #[error("No matching assets found")]
34 #[diagnostic(
35 code(soar_dl::no_match),
36 help("Available assets:\n{}", .available.join("\n"))
37 )]
38 NoMatch { available: Vec<String> },
39
40 #[error("Layer not found")]
41 #[diagnostic(code(soar_dl::layer_not_found))]
42 LayerNotFound,
43
44 #[error("Invalid response from server")]
45 #[diagnostic(code(soar_dl::invalid_response))]
46 InvalidResponse,
47
48 #[error("File name could not be determined")]
49 #[diagnostic(
50 code(soar_dl::no_filename),
51 help("Try specifying an output path explicitly")
52 )]
53 NoFilename,
54
55 #[error("Resume metadata mismatch")]
56 #[diagnostic(code(soar_dl::resume_mismatch))]
57 ResumeMismatch,
58
59 #[error("Multiple download errors occurred")]
60 #[diagnostic(code(soar_dl::multiple_errors))]
61 Multiple { errors: Vec<String> },
62}
63
64pub type Result<T> = miette::Result<T>;
65
66impl From<ureq::Error> for DownloadError {
67 fn from(e: ureq::Error) -> Self {
83 Self::Network(Box::new(e))
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_download_error_invalid_url() {
93 let err = DownloadError::InvalidUrl {
94 url: "invalid".to_string(),
95 source: url::ParseError::RelativeUrlWithoutBase,
96 };
97 let msg = format!("{}", err);
98 assert!(msg.contains("Invalid URL"));
99 assert!(msg.contains("invalid"));
100 }
101
102 #[test]
103 fn test_download_error_http_error() {
104 let err = DownloadError::HttpError {
105 status: 404,
106 url: "https://example.com/notfound".to_string(),
107 };
108 let msg = format!("{}", err);
109 assert!(msg.contains("HTTP 404"));
110 assert!(msg.contains("https://example.com/notfound"));
111 }
112
113 #[test]
114 fn test_download_error_no_match() {
115 let err = DownloadError::NoMatch {
116 available: vec!["file1.zip".to_string(), "file2.tar.gz".to_string()],
117 };
118 let msg = format!("{}", err);
119 assert!(msg.contains("No matching assets found"));
120 }
121
122 #[test]
123 fn test_download_error_layer_not_found() {
124 let err = DownloadError::LayerNotFound;
125 let msg = format!("{}", err);
126 assert_eq!(msg, "Layer not found");
127 }
128
129 #[test]
130 fn test_download_error_invalid_response() {
131 let err = DownloadError::InvalidResponse;
132 let msg = format!("{}", err);
133 assert_eq!(msg, "Invalid response from server");
134 }
135
136 #[test]
137 fn test_download_error_no_filename() {
138 let err = DownloadError::NoFilename;
139 let msg = format!("{}", err);
140 assert_eq!(msg, "File name could not be determined");
141 }
142
143 #[test]
144 fn test_download_error_resume_mismatch() {
145 let err = DownloadError::ResumeMismatch;
146 let msg = format!("{}", err);
147 assert_eq!(msg, "Resume metadata mismatch");
148 }
149
150 #[test]
151 fn test_download_error_multiple() {
152 let err = DownloadError::Multiple {
153 errors: vec!["Error 1".to_string(), "Error 2".to_string()],
154 };
155 let msg = format!("{}", err);
156 assert_eq!(msg, "Multiple download errors occurred");
157 }
158
159 #[test]
160 fn test_download_error_io() {
161 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
162 let err = DownloadError::Io(io_err);
163 let msg = format!("{}", err);
164 assert!(msg.contains("file not found"));
165 }
166
167 #[test]
168 fn test_download_error_debug() {
169 let err = DownloadError::LayerNotFound;
170 let debug = format!("{:?}", err);
171 assert!(debug.contains("LayerNotFound"));
172 }
173
174 #[test]
175 fn test_from_ureq_error() {
176 let ureq_err = ureq::Error::ConnectionFailed;
177 let download_err: DownloadError = ureq_err.into();
178
179 match download_err {
180 DownloadError::Network(_) => (),
181 _ => panic!("Expected Network error variant"),
182 }
183 }
184
185 #[test]
186 fn test_error_source_chain() {
187 let err = DownloadError::InvalidUrl {
188 url: "invalid".to_string(),
189 source: url::ParseError::RelativeUrlWithoutBase,
190 };
191
192 assert!(std::error::Error::source(&err).is_some());
194 }
195}