1
Fork 0
mirror of https://github.com/RGBCube/dix synced 2025-07-28 12:17:45 +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
#[derive(Debug, Error)]
enum AppError {
#[error("Command failed: {0}")]
CommandFailed(String),
#[error("Command failed: {command} {args:?} - {message}")]
CommandFailed {
command: String,
args: Vec<String>,
message: String,
},
#[error("Failed to decode command output: {0}")]
CommandOutputError(#[from] std::str::Utf8Error),
#[error("Failed to decode command output from {context}: {source}")]
CommandOutputError {
source: std::str::Utf8Error,
context: String,
},
#[error("Failed to parse data: {0}")]
ParseError(String),
#[error("Failed to parse data in {context}: {message}")]
ParseError {
message: String,
context: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Regex error: {0}")]
RegexError(#[from] regex::Error),
#[error("Regex error in {context}: {source}")]
RegexError {
source: regex::Error,
context: String,
},
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("IO error in {context}: {source}")]
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
@ -264,9 +369,15 @@ fn get_packages(path: &std::path::Path) -> Result<Vec<String>> {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix-store command failed: {}", stderr);
return Err(AppError::CommandFailed(format!(
"nix-store command failed: {stderr}"
)));
return Err(AppError::CommandFailed {
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)?;
@ -289,9 +400,15 @@ fn get_dependencies(path: &std::path::Path) -> Result<Vec<String>> {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix-store command failed: {}", stderr);
return Err(AppError::CommandFailed(format!(
"nix-store command failed: {stderr}"
)));
return Err(AppError::CommandFailed {
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)?;
@ -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());
if name.is_empty() || version.is_empty() {
return Err(AppError::ParseError(format!(
"Failed to extract name or version from path: {path}"
)));
return Err(AppError::ParseError {
message: format!("Failed to extract name or version from path: {path}"),
context: "get_version".to_string(),
source: None,
});
}
return Ok((name, version));
}
Err(AppError::ParseError(format!(
"Path does not match expected nix store format: {path}"
)))
Err(AppError::ParseError {
message: format!("Path does not match expected nix store format: {path}"),
context: "get_version".to_string(),
source: None,
})
}
fn check_nix_available() -> bool {
@ -361,9 +482,15 @@ fn get_closure_size(path: &std::path::Path) -> Result<i64> {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("nix path-info command failed: {}", stderr);
return Err(AppError::CommandFailed(format!(
"nix path-info command failed: {stderr}"
)));
return Err(AppError::CommandFailed {
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)?;
@ -375,13 +502,21 @@ fn get_closure_size(path: &std::path::Path) -> Result<i64> {
.ok_or_else(|| {
let err = "Unexpected output format from nix path-info";
error!("{}", err);
AppError::ParseError(err.into())
AppError::ParseError {
message: err.into(),
context: "get_closure_size".to_string(),
source: None,
}
})?
.parse::<i64>()
.map_err(|e| {
let err = format!("Failed to parse size value: {e}");
error!("{}", err);
AppError::ParseError(err)
AppError::ParseError {
message: err,
context: "get_closure_size".to_string(),
source: None,
}
})?;
// Convert to MiB