use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("file not found: {}", path.display())]
NotFound { path: PathBuf },
#[error("failed to parse {}: {message}", path.display())]
Parse { path: PathBuf, message: String },
#[error("failed to parse content: {message}")]
ContentParse { message: String },
#[error("validation error in {}: {message}", path.display())]
Validation { path: PathBuf, message: String },
#[error("missing required field '{field}' in {}", path.display())]
MissingField { path: PathBuf, field: &'static str },
#[error("missing required field '{field}'")]
ContentMissingField { field: &'static str },
#[error("invalid value for '{field}' in {}: {message}", path.display())]
InvalidField {
path: PathBuf,
field: &'static str,
message: String,
},
#[error("invalid value for '{field}': {message}")]
ContentInvalidField {
field: &'static str,
message: String,
},
#[error("unresolved reference: {reference}")]
UnresolvedReference { reference: String },
#[error("cannot delete {}: {reason}", path.display())]
DeleteBlocked { path: PathBuf, reason: String },
#[error("directory not found: {}", path.display())]
DirectoryNotFound { path: PathBuf },
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub struct BatchResult<T> {
pub succeeded: Vec<T>,
pub failed: Vec<(PathBuf, Error)>,
}
impl<T> BatchResult<T> {
#[must_use]
pub fn new() -> Self {
Self {
succeeded: Vec::new(),
failed: Vec::new(),
}
}
#[must_use]
pub fn is_complete_success(&self) -> bool {
self.failed.is_empty()
}
#[must_use]
pub fn success_count(&self) -> usize {
self.succeeded.len()
}
#[must_use]
pub fn failure_count(&self) -> usize {
self.failed.len()
}
pub fn into_result(self) -> std::result::Result<Vec<T>, Vec<(PathBuf, Error)>> {
if self.failed.is_empty() {
Ok(self.succeeded)
} else {
Err(self.failed)
}
}
}
impl<T> Default for BatchResult<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn batch_result_empty_is_success() {
let result: BatchResult<PathBuf> = BatchResult::new();
assert!(result.is_complete_success());
assert_eq!(result.success_count(), 0);
assert_eq!(result.failure_count(), 0);
}
#[test]
fn batch_result_with_successes() {
let mut result = BatchResult::new();
result.succeeded.push(PathBuf::from("/test/a.md"));
result.succeeded.push(PathBuf::from("/test/b.md"));
assert!(result.is_complete_success());
assert_eq!(result.success_count(), 2);
assert_eq!(result.failure_count(), 0);
}
#[test]
fn batch_result_with_failures() {
let mut result: BatchResult<PathBuf> = BatchResult::new();
result.succeeded.push(PathBuf::from("/test/a.md"));
result.failed.push((
PathBuf::from("/test/b.md"),
Error::NotFound {
path: PathBuf::from("/test/b.md"),
},
));
assert!(!result.is_complete_success());
assert_eq!(result.success_count(), 1);
assert_eq!(result.failure_count(), 1);
}
#[test]
fn batch_result_into_result_success() {
let mut result = BatchResult::new();
result.succeeded.push(PathBuf::from("/test/a.md"));
let converted = result.into_result();
assert!(converted.is_ok());
assert_eq!(converted.unwrap().len(), 1);
}
#[test]
fn batch_result_into_result_failure() {
let mut result: BatchResult<PathBuf> = BatchResult::new();
result.failed.push((
PathBuf::from("/test/b.md"),
Error::NotFound {
path: PathBuf::from("/test/b.md"),
},
));
let converted = result.into_result();
assert!(converted.is_err());
}
}