aboutsummaryrefslogtreecommitdiffstats
path: root/src/errors.rs
diff options
context:
space:
mode:
authorFilip Wandzio <contact@philw.dev>2026-02-25 16:10:23 +0100
committerFilip Wandzio <contact@philw.dev>2026-02-25 16:10:23 +0100
commitf7b4b643ebc52a4d72d90d9adbdddc9aa0721e4a (patch)
treec96432be342b02bc0409e5b78b6b5d54afcc7cd6 /src/errors.rs
parent2e10b0713f5369f489d2ababd70108cc359c5d2d (diff)
downloaddml-f7b4b643ebc52a4d72d90d9adbdddc9aa0721e4a.tar.gz
dml-f7b4b643ebc52a4d72d90d9adbdddc9aa0721e4a.zip
Feat: Refactor core download logic with concurrency and async features
Implement basic unit testing Implement automatic java executable switching based on game version Split loader module into smaller modules Implement basic documentation
Diffstat (limited to '')
-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}