error: add MultiError for batch error aggregation

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I468289d7a5c3956fc410b6af1a8d070d6a6a6964
This commit is contained in:
raf 2026-02-12 22:01:23 +03:00
commit 4b353733ff
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF

View file

@ -2,6 +2,76 @@ use thiserror::Error;
pub type Result<T> = std::result::Result<T, PakkerError>;
/// Severity level for errors
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ErrorSeverity {
/// Fatal error - operation cannot continue
#[default]
Error,
/// Warning - operation can continue but may have issues
Warning,
/// Info - informational message
Info,
}
/// Container for multiple errors that occurred during an operation
#[derive(Debug)]
pub struct MultiError {
errors: Vec<PakkerError>,
}
impl MultiError {
pub const fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn push(&mut self, error: PakkerError) {
self.errors.push(error);
}
pub fn extend(&mut self, errors: impl IntoIterator<Item = PakkerError>) {
self.errors.extend(errors);
}
pub const fn is_empty(&self) -> bool {
self.errors.is_empty()
}
pub const fn len(&self) -> usize {
self.errors.len()
}
pub fn into_result<T>(self, success_value: T) -> Result<T> {
if self.is_empty() {
Ok(success_value)
} else {
Err(PakkerError::Multiple(self.errors))
}
}
pub fn errors(&self) -> &[PakkerError] {
&self.errors
}
pub fn into_errors(self) -> Vec<PakkerError> {
self.errors
}
}
impl Default for MultiError {
fn default() -> Self {
Self::new()
}
}
impl FromIterator<PakkerError> for MultiError {
fn from_iter<I: IntoIterator<Item = PakkerError>>(iter: I) -> Self {
Self {
errors: iter.into_iter().collect(),
}
}
}
#[derive(Error, Debug)]
pub enum PakkerError {
// Network errors
@ -95,6 +165,21 @@ pub enum PakkerError {
#[error("IPC error: {0}")]
IpcError(String),
#[error("{}", format_multiple_errors(.0))]
Multiple(Vec<Self>),
}
fn format_multiple_errors(errors: &[PakkerError]) -> String {
if errors.len() == 1 {
return errors[0].to_string();
}
let mut msg = format!("{} errors occurred:\n", errors.len());
for (idx, error) in errors.iter().enumerate() {
msg.push_str(&format!(" {}. {}\n", idx + 1, error));
}
msg
}
impl From<git2::Error> for PakkerError {
@ -108,3 +193,96 @@ impl From<crate::ipc::IpcError> for PakkerError {
Self::IpcError(err.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multi_error_empty() {
let multi = MultiError::new();
assert!(multi.is_empty());
assert_eq!(multi.len(), 0);
}
#[test]
fn test_multi_error_push() {
let mut multi = MultiError::new();
multi.push(PakkerError::ProjectNotFound("mod1".to_string()));
multi.push(PakkerError::ProjectNotFound("mod2".to_string()));
assert!(!multi.is_empty());
assert_eq!(multi.len(), 2);
}
#[test]
fn test_multi_error_into_result_empty() {
let multi = MultiError::new();
let result: Result<i32> = multi.into_result(42);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}
#[test]
fn test_multi_error_into_result_with_errors() {
let mut multi = MultiError::new();
multi.push(PakkerError::ProjectNotFound("mod1".to_string()));
let result: Result<i32> = multi.into_result(42);
assert!(result.is_err());
}
#[test]
fn test_multi_error_from_iterator() {
let errors = vec![
PakkerError::ProjectNotFound("mod1".to_string()),
PakkerError::ProjectNotFound("mod2".to_string()),
];
let multi: MultiError = errors.into_iter().collect();
assert_eq!(multi.len(), 2);
}
#[test]
fn test_multi_error_extend() {
let mut multi = MultiError::new();
multi.push(PakkerError::ProjectNotFound("mod1".to_string()));
let more_errors = vec![
PakkerError::ProjectNotFound("mod2".to_string()),
PakkerError::ProjectNotFound("mod3".to_string()),
];
multi.extend(more_errors);
assert_eq!(multi.len(), 3);
}
#[test]
fn test_multiple_errors_formatting() {
let errors = vec![
PakkerError::ProjectNotFound("mod1".to_string()),
PakkerError::ProjectNotFound("mod2".to_string()),
];
let error = PakkerError::Multiple(errors);
let msg = error.to_string();
assert!(msg.contains("2 errors occurred"));
assert!(msg.contains("mod1"));
assert!(msg.contains("mod2"));
}
#[test]
fn test_single_multiple_error_formatting() {
let errors = vec![PakkerError::ProjectNotFound("mod1".to_string())];
let error = PakkerError::Multiple(errors);
let msg = error.to_string();
// Single error should just display the error itself
assert!(msg.contains("mod1"));
assert!(!msg.contains("errors occurred"));
}
#[test]
fn test_error_severity_default() {
assert_eq!(ErrorSeverity::default(), ErrorSeverity::Error);
}
}