use std::{fmt, io}; use fmt::{Display, Formatter, Result}; use io::Error; use zip::result::ZipError; /// Central error type for the DML launcher. /// /// # 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 { /// 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 { fn fmt(&self, f: &mut Formatter<'_>) -> Result { write!(f, "{:?}", self) } } /// 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) } } /// 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) } } /// 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) } } /// 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)); } } }