1
Fork 0
mirror of https://github.com/RGBCube/dix synced 2025-07-28 04:07:46 +00:00

error: improve error context in AppError

With nothing other than proper `From` implementations
This commit is contained in:
NotAShelf 2025-05-05 02:52:38 +03:00
parent f52281d691
commit 955a710812
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF

View file

@ -16,20 +16,125 @@ use yansi::Paint;
/// Application errors with thiserror /// Application errors with thiserror
#[derive(Debug, Error)] #[derive(Debug, Error)]
enum AppError { enum AppError {
#[error("Command failed: {0}")] #[error("Command failed: {command} {args:?} - {message}")]
CommandFailed(String), CommandFailed {
command: String,
args: Vec<String>,
message: String,
},
#[error("Failed to decode command output: {0}")] #[error("Failed to decode command output from {context}: {source}")]
CommandOutputError(#[from] std::str::Utf8Error), CommandOutputError {
source: std::str::Utf8Error,
context: String,
},
#[error("Failed to parse data: {0}")] #[error("Failed to parse data in {context}: {message}")]
ParseError(String), ParseError {
message: String,
context: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Regex error: {0}")] #[error("Regex error in {context}: {source}")]
RegexError(#[from] regex::Error), RegexError {
source: regex::Error,
context: String,
},
#[error("IO error: {0}")] #[error("IO error in {context}: {source}")]
IoError(#[from] std::io::Error), IoError {
source: std::io::Error,
context: String,
},
#[error("Nix store error: {message} for path: {store_path}")]
NixStoreError { message: String, store_path: String },
}
// Implement From traits to support the ? operator
impl From<std::io::Error> for AppError {
fn from(source: std::io::Error) -> Self {
Self::IoError {
source,
context: "unknown context".into(),
}
}
}
impl From<std::str::Utf8Error> for AppError {
fn from(source: std::str::Utf8Error) -> Self {
Self::CommandOutputError {
source,
context: "command output".into(),
}
}
}
impl From<regex::Error> for AppError {
fn from(source: regex::Error) -> Self {
Self::RegexError {
source,
context: "regex operation".into(),
}
}
}
impl AppError {
/// Create a command failure error with context
pub fn command_failed<S: Into<String>>(command: S, args: &[&str], message: S) -> Self {
Self::CommandFailed {
command: command.into(),
args: args.iter().map(|&s| s.to_string()).collect(),
message: message.into(),
}
}
/// Create a parse error with context
pub fn parse_error<S: Into<String>, C: Into<String>>(
message: S,
context: C,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::ParseError {
message: message.into(),
context: context.into(),
source,
}
}
/// Create an IO error with context
pub fn io_error<C: Into<String>>(source: std::io::Error, context: C) -> Self {
Self::IoError {
source,
context: context.into(),
}
}
/// Create a regex error with context
pub fn regex_error<C: Into<String>>(source: regex::Error, context: C) -> Self {
Self::RegexError {
source,
context: context.into(),
}
}
/// Create a command output error with context
pub fn command_output_error<C: Into<String>>(source: std::str::Utf8Error, context: C) -> Self {
Self::CommandOutputError {
source,
context: context.into(),
}
}
/// Create a Nix store error
pub fn nix_store_error<M: Into<String>, P: Into<String>>(message: M, store_path: P) -> Self {
Self::NixStoreError {
message: message.into(),
store_path: store_path.into(),
}
}
} }
// Use type alias for Result with our custom error type // Use type alias for Result with our custom error type
@ -264,9 +369,15 @@ fn get_packages(path: &std::path::Path) -> Result<Vec<String>> {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix-store command failed: {}", stderr); error!("nix-store command failed: {}", stderr);
return Err(AppError::CommandFailed(format!( return Err(AppError::CommandFailed {
"nix-store command failed: {stderr}" command: "nix-store".to_string(),
))); args: vec![
"--query".to_string(),
"--references".to_string(),
path.join("sw").to_string_lossy().to_string(),
],
message: stderr.to_string(),
});
} }
let list = str::from_utf8(&output.stdout)?; let list = str::from_utf8(&output.stdout)?;
@ -289,9 +400,15 @@ fn get_dependencies(path: &std::path::Path) -> Result<Vec<String>> {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix-store command failed: {}", stderr); error!("nix-store command failed: {}", stderr);
return Err(AppError::CommandFailed(format!( return Err(AppError::CommandFailed {
"nix-store command failed: {stderr}" command: "nix-store".to_string(),
))); args: vec![
"--query".to_string(),
"--requisites".to_string(),
path.join("sw").to_string_lossy().to_string(),
],
message: stderr.to_string(),
});
} }
let list = str::from_utf8(&output.stdout)?; let list = str::from_utf8(&output.stdout)?;
@ -321,17 +438,21 @@ fn get_version<'a>(pack: impl Into<&'a str>) -> Result<(&'a str, &'a str)> {
let version = cap.get(2).map_or("", |m| m.as_str()); let version = cap.get(2).map_or("", |m| m.as_str());
if name.is_empty() || version.is_empty() { if name.is_empty() || version.is_empty() {
return Err(AppError::ParseError(format!( return Err(AppError::ParseError {
"Failed to extract name or version from path: {path}" message: format!("Failed to extract name or version from path: {path}"),
))); context: "get_version".to_string(),
source: None,
});
} }
return Ok((name, version)); return Ok((name, version));
} }
Err(AppError::ParseError(format!( Err(AppError::ParseError {
"Path does not match expected nix store format: {path}" message: format!("Path does not match expected nix store format: {path}"),
))) context: "get_version".to_string(),
source: None,
})
} }
fn check_nix_available() -> bool { fn check_nix_available() -> bool {
@ -361,9 +482,15 @@ fn get_closure_size(path: &std::path::Path) -> Result<i64> {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix path-info command failed: {}", stderr); error!("nix path-info command failed: {}", stderr);
return Err(AppError::CommandFailed(format!( return Err(AppError::CommandFailed {
"nix path-info command failed: {stderr}" command: "nix".to_string(),
))); args: vec![
"path-info".to_string(),
"--closure-size".to_string(),
path.join("sw").to_string_lossy().to_string(),
],
message: stderr.to_string(),
});
} }
let stdout = str::from_utf8(&output.stdout)?; let stdout = str::from_utf8(&output.stdout)?;
@ -375,13 +502,21 @@ fn get_closure_size(path: &std::path::Path) -> Result<i64> {
.ok_or_else(|| { .ok_or_else(|| {
let err = "Unexpected output format from nix path-info"; let err = "Unexpected output format from nix path-info";
error!("{}", err); error!("{}", err);
AppError::ParseError(err.into()) AppError::ParseError {
message: err.into(),
context: "get_closure_size".to_string(),
source: None,
}
})? })?
.parse::<i64>() .parse::<i64>()
.map_err(|e| { .map_err(|e| {
let err = format!("Failed to parse size value: {e}"); let err = format!("Failed to parse size value: {e}");
error!("{}", err); error!("{}", err);
AppError::ParseError(err) AppError::ParseError {
message: err,
context: "get_closure_size".to_string(),
source: None,
}
})?; })?;
// Convert to MiB // Convert to MiB