From f7b4b643ebc52a4d72d90d9adbdddc9aa0721e4a Mon Sep 17 00:00:00 2001 From: Filip Wandzio Date: Wed, 25 Feb 2026 16:10:23 +0100 Subject: 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 --- src/errors.rs | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 306 insertions(+), 14 deletions(-) (limited to 'src/errors.rs') 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 @@ use std::{fmt, io}; use fmt::{Display, Formatter, Result}; +use io::Error; use zip::result::ZipError; -/// Represents all possible errors that can occur in the DML launcher. +/// Central error type for the DML launcher. /// -/// This enum centralizes error handling for the entire application, -/// wrapping various underlying error types from I/O, HTTP requests, -/// JSON parsing, ZIP extraction, configuration issues, and runtime errors. +/// # Overview +/// +/// `McError` provides a unified error abstraction for the entire +/// application. Instead of exposing multiple unrelated error types +/// across modules, all recoverable failures are represented through +/// this single enum. +/// +/// This design offers several advantages: +/// +/// - Simplified function signatures (`Result`) +/// - Seamless propagation using the `?` operator +/// - Centralized error categorization +/// - Clear separation between infrastructure errors and logical failures +/// +/// # Error Categories +/// +/// The variants fall into two broad categories: +/// +/// 1. Wrapped external errors These variants encapsulate errors originating +/// from third-party libraries or the standard library. +/// +/// 2. Application-defined errors These variants represent logical or +/// domain-specific failures unique to the launcher. +/// +/// All variants are intentionally explicit to preserve failure context +/// and avoid opaque error strings. +/// +/// # Example +/// +/// ```ignore +/// fn load_config(path: &str) -> Result { +/// let data = read_to_string(path)?; +/// let config = from_str(&data)?; +/// Ok(config) +/// } +/// ``` +/// +/// In this example, both `io::Error` and `serde_json::Error` +/// automatically convert into `McError` via the provided `From` +/// implementations. #[allow(dead_code)] #[derive(Debug)] pub enum McError { - Io(io::Error), + /// Errors originating from the standard I/O subsystem. + /// + /// Typical causes: + /// - File system access failures + /// - Permission errors + /// - Missing files or directories + /// - Broken pipes + /// + /// Wraps: `std::io::Error` + Io(Error), + + /// Errors occurring during HTTP communication. + /// + /// Typical causes: + /// - Network connectivity failures + /// - Timeouts + /// - TLS negotiation errors + /// - Invalid HTTP responses + /// + /// Wraps: `reqwest::Error` Http(reqwest::Error), + + /// Errors encountered while parsing or serializing JSON. + /// + /// Typical causes: + /// - Malformed configuration files + /// - Invalid API responses + /// - Schema mismatches + /// + /// Wraps: `serde_json::Error` Json(serde_json::Error), + + /// Errors related to ZIP archive handling. + /// + /// Typical causes: + /// - Corrupted archives + /// - Unsupported compression methods + /// - Extraction failures + /// + /// Wraps: `zip::result::ZipError` Zip(ZipError), + + /// Configuration-related failures detected by the application. + /// + /// This variant is used when configuration data is syntactically + /// valid but semantically incorrect or incomplete. + /// + /// The contained string should describe the specific issue. Config(String), + + /// Indicates a checksum verification failure. + /// + /// This is typically used when validating downloaded files + /// against an expected SHA hash. A mismatch suggests corruption, + /// incomplete transfer, or tampering. + /// + /// The contained string should identify the failing resource. ShaMismatch(String), + + /// Represents a failure when spawning or interacting with + /// an external process. + /// + /// Typical causes: + /// - Executable not found + /// - Non-zero exit status + /// - Failed argument construction + /// + /// The contained string provides additional diagnostic detail. Process(String), + + /// A general-purpose runtime error. + /// + /// This variant is intended for unexpected logical failures + /// that do not fall into other specific categories. + /// + /// It should not be used as a catch-all replacement for + /// more precise error variants. Runtime(String), } +/// User-facing formatting implementation. +/// +/// This implementation of the `Display` trait delegates to the `Debug` representation of the enum, +/// providing a full structural view of the error. This includes all internal details such as the +/// variant type and wrapped error information, which is particularly useful during development and debugging. +/// +/// The `Debug` representation might be too verbose for production use, where more concise, user-friendly +/// error messages would be beneficial. A future enhancement may be to implement a custom `Display` formatting +/// for each error variant, providing a cleaner, more readable message for end users./// User-facing formatting implementation. +/// +/// Currently delegates to the `Debug` representation of the enum. +/// This preserves full structural information and wrapped error +/// details, which is useful during development and debugging. +/// +/// For production environments, a more refined implementation +/// may format each variant explicitly to produce cleaner, +/// user-oriented error messages. impl Display for McError { - /// Formats the error for user-friendly display. - /// - /// Currently, it uses the `Debug` format for simplicity. - fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{:?}", self) } + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{:?}", self) + } } -impl From for McError { - fn from(e: io::Error) -> Self { Self::Io(e) } +/// Enables conversion from `std::io::Error` to `McError`. +/// +/// This implementation allows any `std::io::Error` (e.g., file I/O errors) to be automatically converted into +/// the `McError::Io` variant. This conversion is crucial when using the `?` operator within functions returning +/// `Result`, as it ensures that I/O errors are properly handled and propagated as part of the unified +/// `McError` error type. +/// +/// This reduces the need for manual conversions and simplifies error handling in functions that involve I/O operations. +impl From for McError { + fn from(e: Error) -> Self { + Self::Io(e) + } } +/// Enables conversion from `reqwest::Error` to `McError`. +/// +/// This implementation allows `reqwest::Error` (such as errors from HTTP requests) to be seamlessly converted +/// into the `McError::Http` variant. This is particularly useful for error handling in functions that involve HTTP +/// requests, as it allows HTTP-related failures to propagate naturally into the `McError` type and be handled +/// consistently with other error types. +/// +/// This conversion makes it easy to work with `reqwest` errors without worrying about mixing error types in the +/// function signature. impl From for McError { - fn from(e: reqwest::Error) -> Self { Self::Http(e) } + fn from(e: reqwest::Error) -> Self { + Self::Http(e) + } } +/// Enables conversion from `serde_json::Error` to `McError`. +/// +/// This implementation simplifies handling JSON parsing and serialization errors by converting any `serde_json::Error` +/// (e.g., invalid JSON format, deserialization failure) into the `McError::Json` variant. This allows JSON errors +/// to be handled in the same way as other error types, enabling uniform error management across the application. +/// +/// By using this conversion, developers don't have to manually map JSON-related errors to a custom error type, +/// reducing boilerplate and improving readability. impl From for McError { - fn from(e: serde_json::Error) -> Self { Self::Json(e) } + fn from(e: serde_json::Error) -> Self { + Self::Json(e) + } } +/// Enables conversion from `ZipError` to `McError`. +/// +/// This implementation allows errors from ZIP archive operations (such as invalid or corrupted archives) to be +/// converted into the `McError::Zip` variant. This makes it easy to integrate ZIP archive operations into the +/// `McError` error handling model, ensuring consistency in how errors are propagated across the application. +/// +/// This conversion allows the use of the `?` operator for ZIP-related errors, making error handling in ZIP operations +/// as seamless as with other error types. impl From for McError { - fn from(e: ZipError) -> Self { Self::Zip(e) } + fn from(e: ZipError) -> Self { + Self::Zip(e) + } +} + +/// Tests the `Display` trait implementation for each `McError` variant. +/// +/// This test ensures that the `Display` formatting for all `McError` variants results in a non-empty string. +/// It verifies that each variant can be correctly converted to a string, even though the current implementation +/// delegates to `Debug`. This test is crucial for confirming that the `Display` implementation is functioning +/// as expected for all error variants. +#[cfg(test)] +mod tests { + use super::*; + use io::ErrorKind::Other; + use reqwest::Client; + use serde_json::{from_str, Value}; + use std::io; + use std::io::Error; + use McError::{Config, Io, Json, Process, Runtime, ShaMismatch, Zip}; + use ZipError::InvalidArchive; + + fn create_error_variants() -> Vec { + vec![ + Io(Error::new(Other, "io")), + Json(from_str::("not json").unwrap_err()), + Zip(InvalidArchive("zip".into())), + Config("config".into()), + ShaMismatch("sha".into()), + Process("process".into()), + Runtime("runtime".into()), + ] + } + + #[test] + fn display_formats_error_variants() { + let error_variants = create_error_variants(); + + for error in error_variants { + let display_str = format!("{}", error); + assert!(!display_str.is_empty()); + } + } + + /// Tests the conversion from `std::io::Error` to `McError::Io`. + /// + /// This test ensures that an I/O error of type `std::io::Error` is correctly converted to the `McError::Io` + /// variant. The test checks that the string representation of the I/O error matches the expected value and + /// verifies that the conversion process is seamless. This test is essential for confirming the correct behavior + /// of the `From` implementation for I/O errors. + #[test] + fn from_io_error() { + let io_error: Error = Error::new(Other, "oops"); + let mc_error: McError = io_error.into(); + + match mc_error { + | Io(e) => assert_eq!(e.to_string(), "oops"), + | _ => panic!("Expected McError::Io, but got: {:?}", mc_error), + } + } + + /// Tests the conversion from `serde_json::Error` to `McError::Json`. + /// + /// This test ensures that a `serde_json::Error`, which occurs when JSON parsing or serialization fails, + /// is correctly converted into the `McError::Json` variant. The test checks that the error is appropriately + /// transformed and can be matched against the `McError::Json` variant. + #[test] + fn from_json_error() { + let json_error: serde_json::Error = + from_str::("not json").unwrap_err(); + let mc_error: McError = json_error.into(); + + assert!(matches!(mc_error, Json(_))); + } + + /// Tests the conversion from `ZipError` to `McError::Zip`. + /// + /// This test ensures that a `ZipError`, typically arising from invalid ZIP archive operations, + /// is converted to the `McError::Zip` variant. This ensures that ZIP-related errors are handled + /// in the same way as other error types, maintaining consistency in error propagation across the application. + #[test] + fn from_zip_error() { + let zip_error = InvalidArchive("bad zip".into()); + let mc_error: McError = zip_error.into(); + + assert!(matches!(mc_error, Zip(_))); + } + + /// Tests the conversion from `reqwest::Error` to `McError::Http`. + /// + /// This test ensures that HTTP-related errors from `reqwest` (e.g., failed requests, connection issues) + /// are correctly converted into the `McError::Http` variant. It confirms that HTTP-related failures + /// are properly integrated into the unified `McError` error type and handled in the same way as other errors. + #[tokio::test] + async fn from_reqwest_error() { + let client: Client = Client::new(); + let request_error: reqwest::Error = client + .get("http://127.0.0.1:0") + .send() + .await + .unwrap_err(); + + let mc_error: McError = request_error.into(); + + assert!( + matches!(mc_error, McError::Http(_)), + "Expected McError::Http, got: {:?}", + mc_error + ); + } + + /// Tests that `Config`, `ShaMismatch`, `Process`, and `Runtime` variants are formatted consistently. + /// + /// This test checks that the `Display` and `Debug` formats are the same for the `Config`, `ShaMismatch`, + /// `Process`, and `Runtime` variants of `McError`. The test ensures that these variants are correctly + /// formatted both for user-facing display (`Display`) and for debugging purposes (`Debug`), + /// confirming consistency between the two formats. + #[test] + fn config_sha_process_runtime_variants() { + let error_variants = vec![ + Config("bad config".into()), + ShaMismatch("bad sha".into()), + Process("failed process".into()), + Runtime("runtime error".into()), + ]; + + for error in error_variants { + assert_eq!(format!("{}", error), format!("{:?}", error)); + } + } } -- cgit v1.2.3