aboutsummaryrefslogtreecommitdiffstats
path: root/src/errors.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/errors.rs')
-rw-r--r--src/errors.rs320
1 files changed, 306 insertions, 14 deletions
diff --git a/src/errors.rs b/src/errors.rs
index b98ae2d..bb9c985 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -1,45 +1,337 @@
1use std::{fmt, io}; 1use std::{fmt, io};
2 2
3use fmt::{Display, Formatter, Result}; 3use fmt::{Display, Formatter, Result};
4use io::Error;
4use zip::result::ZipError; 5use zip::result::ZipError;
5 6
6/// Represents all possible errors that can occur in the DML launcher. 7/// Central error type for the DML launcher.
7/// 8///
8/// This enum centralizes error handling for the entire application, 9/// # Overview
9/// wrapping various underlying error types from I/O, HTTP requests, 10///
10/// JSON parsing, ZIP extraction, configuration issues, and runtime errors. 11/// `McError` provides a unified error abstraction for the entire
12/// application. Instead of exposing multiple unrelated error types
13/// across modules, all recoverable failures are represented through
14/// this single enum.
15///
16/// This design offers several advantages:
17///
18/// - Simplified function signatures (`Result<T, McError>`)
19/// - Seamless propagation using the `?` operator
20/// - Centralized error categorization
21/// - Clear separation between infrastructure errors and logical failures
22///
23/// # Error Categories
24///
25/// The variants fall into two broad categories:
26///
27/// 1. Wrapped external errors These variants encapsulate errors originating
28/// from third-party libraries or the standard library.
29///
30/// 2. Application-defined errors These variants represent logical or
31/// domain-specific failures unique to the launcher.
32///
33/// All variants are intentionally explicit to preserve failure context
34/// and avoid opaque error strings.
35///
36/// # Example
37///
38/// ```ignore
39/// fn load_config(path: &str) -> Result<Config, McError> {
40/// let data = read_to_string(path)?;
41/// let config = from_str(&data)?;
42/// Ok(config)
43/// }
44/// ```
45///
46/// In this example, both `io::Error` and `serde_json::Error`
47/// automatically convert into `McError` via the provided `From`
48/// implementations.
11#[allow(dead_code)] 49#[allow(dead_code)]
12#[derive(Debug)] 50#[derive(Debug)]
13pub enum McError { 51pub enum McError {
14 Io(io::Error), 52 /// Errors originating from the standard I/O subsystem.
53 ///
54 /// Typical causes:
55 /// - File system access failures
56 /// - Permission errors
57 /// - Missing files or directories
58 /// - Broken pipes
59 ///
60 /// Wraps: `std::io::Error`
61 Io(Error),
62
63 /// Errors occurring during HTTP communication.
64 ///
65 /// Typical causes:
66 /// - Network connectivity failures
67 /// - Timeouts
68 /// - TLS negotiation errors
69 /// - Invalid HTTP responses
70 ///
71 /// Wraps: `reqwest::Error`
15 Http(reqwest::Error), 72 Http(reqwest::Error),
73
74 /// Errors encountered while parsing or serializing JSON.
75 ///
76 /// Typical causes:
77 /// - Malformed configuration files
78 /// - Invalid API responses
79 /// - Schema mismatches
80 ///
81 /// Wraps: `serde_json::Error`
16 Json(serde_json::Error), 82 Json(serde_json::Error),
83
84 /// Errors related to ZIP archive handling.
85 ///
86 /// Typical causes:
87 /// - Corrupted archives
88 /// - Unsupported compression methods
89 /// - Extraction failures
90 ///
91 /// Wraps: `zip::result::ZipError`
17 Zip(ZipError), 92 Zip(ZipError),
93
94 /// Configuration-related failures detected by the application.
95 ///
96 /// This variant is used when configuration data is syntactically
97 /// valid but semantically incorrect or incomplete.
98 ///
99 /// The contained string should describe the specific issue.
18 Config(String), 100 Config(String),
101
102 /// Indicates a checksum verification failure.
103 ///
104 /// This is typically used when validating downloaded files
105 /// against an expected SHA hash. A mismatch suggests corruption,
106 /// incomplete transfer, or tampering.
107 ///
108 /// The contained string should identify the failing resource.
19 ShaMismatch(String), 109 ShaMismatch(String),
110
111 /// Represents a failure when spawning or interacting with
112 /// an external process.
113 ///
114 /// Typical causes:
115 /// - Executable not found
116 /// - Non-zero exit status
117 /// - Failed argument construction
118 ///
119 /// The contained string provides additional diagnostic detail.
20 Process(String), 120 Process(String),
121
122 /// A general-purpose runtime error.
123 ///
124 /// This variant is intended for unexpected logical failures
125 /// that do not fall into other specific categories.
126 ///
127 /// It should not be used as a catch-all replacement for
128 /// more precise error variants.
21 Runtime(String), 129 Runtime(String),
22} 130}
23 131
132/// User-facing formatting implementation.
133///
134/// This implementation of the `Display` trait delegates to the `Debug` representation of the enum,
135/// providing a full structural view of the error. This includes all internal details such as the
136/// variant type and wrapped error information, which is particularly useful during development and debugging.
137///
138/// The `Debug` representation might be too verbose for production use, where more concise, user-friendly
139/// error messages would be beneficial. A future enhancement may be to implement a custom `Display` formatting
140/// for each error variant, providing a cleaner, more readable message for end users./// User-facing formatting implementation.
141///
142/// Currently delegates to the `Debug` representation of the enum.
143/// This preserves full structural information and wrapped error
144/// details, which is useful during development and debugging.
145///
146/// For production environments, a more refined implementation
147/// may format each variant explicitly to produce cleaner,
148/// user-oriented error messages.
24impl Display for McError { 149impl Display for McError {
25 /// Formats the error for user-friendly display. 150 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
26 /// 151 write!(f, "{:?}", self)
27 /// Currently, it uses the `Debug` format for simplicity. 152 }
28 fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{:?}", self) }
29} 153}
30 154
31impl From<io::Error> for McError { 155/// Enables conversion from `std::io::Error` to `McError`.
32 fn from(e: io::Error) -> Self { Self::Io(e) } 156///
157/// This implementation allows any `std::io::Error` (e.g., file I/O errors) to be automatically converted into
158/// the `McError::Io` variant. This conversion is crucial when using the `?` operator within functions returning
159/// `Result<T, McError>`, as it ensures that I/O errors are properly handled and propagated as part of the unified
160/// `McError` error type.
161///
162/// This reduces the need for manual conversions and simplifies error handling in functions that involve I/O operations.
163impl From<Error> for McError {
164 fn from(e: Error) -> Self {
165 Self::Io(e)
166 }
33} 167}
34 168
169/// Enables conversion from `reqwest::Error` to `McError`.
170///
171/// This implementation allows `reqwest::Error` (such as errors from HTTP requests) to be seamlessly converted
172/// into the `McError::Http` variant. This is particularly useful for error handling in functions that involve HTTP
173/// requests, as it allows HTTP-related failures to propagate naturally into the `McError` type and be handled
174/// consistently with other error types.
175///
176/// This conversion makes it easy to work with `reqwest` errors without worrying about mixing error types in the
177/// function signature.
35impl From<reqwest::Error> for McError { 178impl From<reqwest::Error> for McError {
36 fn from(e: reqwest::Error) -> Self { Self::Http(e) } 179 fn from(e: reqwest::Error) -> Self {
180 Self::Http(e)
181 }
37} 182}
38 183
184/// Enables conversion from `serde_json::Error` to `McError`.
185///
186/// This implementation simplifies handling JSON parsing and serialization errors by converting any `serde_json::Error`
187/// (e.g., invalid JSON format, deserialization failure) into the `McError::Json` variant. This allows JSON errors
188/// to be handled in the same way as other error types, enabling uniform error management across the application.
189///
190/// By using this conversion, developers don't have to manually map JSON-related errors to a custom error type,
191/// reducing boilerplate and improving readability.
39impl From<serde_json::Error> for McError { 192impl From<serde_json::Error> for McError {
40 fn from(e: serde_json::Error) -> Self { Self::Json(e) } 193 fn from(e: serde_json::Error) -> Self {
194 Self::Json(e)
195 }
41} 196}
42 197
198/// Enables conversion from `ZipError` to `McError`.
199///
200/// This implementation allows errors from ZIP archive operations (such as invalid or corrupted archives) to be
201/// converted into the `McError::Zip` variant. This makes it easy to integrate ZIP archive operations into the
202/// `McError` error handling model, ensuring consistency in how errors are propagated across the application.
203///
204/// This conversion allows the use of the `?` operator for ZIP-related errors, making error handling in ZIP operations
205/// as seamless as with other error types.
43impl From<ZipError> for McError { 206impl From<ZipError> for McError {
44 fn from(e: ZipError) -> Self { Self::Zip(e) } 207 fn from(e: ZipError) -> Self {
208 Self::Zip(e)
209 }
210}
211
212/// Tests the `Display` trait implementation for each `McError` variant.
213///
214/// This test ensures that the `Display` formatting for all `McError` variants results in a non-empty string.
215/// It verifies that each variant can be correctly converted to a string, even though the current implementation
216/// delegates to `Debug`. This test is crucial for confirming that the `Display` implementation is functioning
217/// as expected for all error variants.
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use io::ErrorKind::Other;
222 use reqwest::Client;
223 use serde_json::{from_str, Value};
224 use std::io;
225 use std::io::Error;
226 use McError::{Config, Io, Json, Process, Runtime, ShaMismatch, Zip};
227 use ZipError::InvalidArchive;
228
229 fn create_error_variants() -> Vec<McError> {
230 vec![
231 Io(Error::new(Other, "io")),
232 Json(from_str::<Value>("not json").unwrap_err()),
233 Zip(InvalidArchive("zip".into())),
234 Config("config".into()),
235 ShaMismatch("sha".into()),
236 Process("process".into()),
237 Runtime("runtime".into()),
238 ]
239 }
240
241 #[test]
242 fn display_formats_error_variants() {
243 let error_variants = create_error_variants();
244
245 for error in error_variants {
246 let display_str = format!("{}", error);
247 assert!(!display_str.is_empty());
248 }
249 }
250
251 /// Tests the conversion from `std::io::Error` to `McError::Io`.
252 ///
253 /// This test ensures that an I/O error of type `std::io::Error` is correctly converted to the `McError::Io`
254 /// variant. The test checks that the string representation of the I/O error matches the expected value and
255 /// verifies that the conversion process is seamless. This test is essential for confirming the correct behavior
256 /// of the `From<Error>` implementation for I/O errors.
257 #[test]
258 fn from_io_error() {
259 let io_error: Error = Error::new(Other, "oops");
260 let mc_error: McError = io_error.into();
261
262 match mc_error {
263 | Io(e) => assert_eq!(e.to_string(), "oops"),
264 | _ => panic!("Expected McError::Io, but got: {:?}", mc_error),
265 }
266 }
267
268 /// Tests the conversion from `serde_json::Error` to `McError::Json`.
269 ///
270 /// This test ensures that a `serde_json::Error`, which occurs when JSON parsing or serialization fails,
271 /// is correctly converted into the `McError::Json` variant. The test checks that the error is appropriately
272 /// transformed and can be matched against the `McError::Json` variant.
273 #[test]
274 fn from_json_error() {
275 let json_error: serde_json::Error =
276 from_str::<Value>("not json").unwrap_err();
277 let mc_error: McError = json_error.into();
278
279 assert!(matches!(mc_error, Json(_)));
280 }
281
282 /// Tests the conversion from `ZipError` to `McError::Zip`.
283 ///
284 /// This test ensures that a `ZipError`, typically arising from invalid ZIP archive operations,
285 /// is converted to the `McError::Zip` variant. This ensures that ZIP-related errors are handled
286 /// in the same way as other error types, maintaining consistency in error propagation across the application.
287 #[test]
288 fn from_zip_error() {
289 let zip_error = InvalidArchive("bad zip".into());
290 let mc_error: McError = zip_error.into();
291
292 assert!(matches!(mc_error, Zip(_)));
293 }
294
295 /// Tests the conversion from `reqwest::Error` to `McError::Http`.
296 ///
297 /// This test ensures that HTTP-related errors from `reqwest` (e.g., failed requests, connection issues)
298 /// are correctly converted into the `McError::Http` variant. It confirms that HTTP-related failures
299 /// are properly integrated into the unified `McError` error type and handled in the same way as other errors.
300 #[tokio::test]
301 async fn from_reqwest_error() {
302 let client: Client = Client::new();
303 let request_error: reqwest::Error = client
304 .get("http://127.0.0.1:0")
305 .send()
306 .await
307 .unwrap_err();
308
309 let mc_error: McError = request_error.into();
310
311 assert!(
312 matches!(mc_error, McError::Http(_)),
313 "Expected McError::Http, got: {:?}",
314 mc_error
315 );
316 }
317
318 /// Tests that `Config`, `ShaMismatch`, `Process`, and `Runtime` variants are formatted consistently.
319 ///
320 /// This test checks that the `Display` and `Debug` formats are the same for the `Config`, `ShaMismatch`,
321 /// `Process`, and `Runtime` variants of `McError`. The test ensures that these variants are correctly
322 /// formatted both for user-facing display (`Display`) and for debugging purposes (`Debug`),
323 /// confirming consistency between the two formats.
324 #[test]
325 fn config_sha_process_runtime_variants() {
326 let error_variants = vec![
327 Config("bad config".into()),
328 ShaMismatch("bad sha".into()),
329 Process("failed process".into()),
330 Runtime("runtime error".into()),
331 ];
332
333 for error in error_variants {
334 assert_eq!(format!("{}", error), format!("{:?}", error));
335 }
336 }
45} 337}