use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, ProcessingError>;
#[derive(Error, Debug)]
pub enum ProcessingError {
#[error("Failed to process content: {details}")]
ContentProcessing {
details: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("File operation failed for '{path}': {details}")]
FileOperation {
path: PathBuf,
details: String,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("File not found: {path}")]
FileNotFound {
path: PathBuf,
details: String,
},
#[error("Template error in '{template_name}': {details}")]
TemplateProcessing {
template_name: String,
details: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Configuration error: {details}")]
Configuration {
details: String,
path: Option<PathBuf>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Validation failed: {details}")]
Validation {
details: String,
context: Option<String>,
},
#[error("Output generation failed for '{path}': {details}")]
OutputGeneration {
path: PathBuf,
details: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Serialization error: {details}")]
Serialization {
details: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Plugin error for '{plugin_name}': {details}")]
Plugin {
plugin_name: String,
details: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("Internal error: {details}")]
Internal {
details: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("File IO error at `{path:?}`: {source}")]
IOError {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("Internal error: {0}")]
InternalError(String),
}
impl ProcessingError {
pub fn content_processing<S: Into<String>>(
details: S,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::ContentProcessing {
details: details.into(),
source,
}
}
pub fn file_operation<P: Into<PathBuf>, S: Into<String>>(
path: P,
details: S,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::FileOperation {
path: path.into(),
details: details.into(),
source,
}
}
pub fn template_processing<S1: Into<String>, S2: Into<String>>(
template_name: S1,
details: S2,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::TemplateProcessing {
template_name: template_name.into(),
details: details.into(),
source,
}
}
pub fn configuration<S: Into<String>>(
details: S,
path: Option<PathBuf>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Configuration {
details: details.into(),
path,
source,
}
}
pub fn validation<S1: Into<String>, S2: Into<String>>(
details: S1,
context: Option<S2>,
) -> Self {
Self::Validation {
details: details.into(),
context: context.map(|s| s.into()),
}
}
pub fn output_generation<P: Into<PathBuf>, S: Into<String>>(
path: P,
details: S,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::OutputGeneration {
path: path.into(),
details: details.into(),
source,
}
}
pub fn serialization<S: Into<String>>(
details: S,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Serialization {
details: details.into(),
source,
}
}
pub fn plugin<S1: Into<String>, S2: Into<String>>(
plugin_name: S1,
details: S2,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Plugin {
plugin_name: plugin_name.into(),
details: details.into(),
source,
}
}
pub fn internal<S: Into<String>>(
details: S,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::Internal {
details: details.into(),
source,
}
}
pub fn io_error(path: PathBuf, source: std::io::Error) -> Self {
ProcessingError::IOError { path, source }
}
pub fn internal_error<S: Into<String>>(message: S) -> Self {
ProcessingError::InternalError(message.into())
}
}
impl From<std::io::Error> for ProcessingError {
fn from(error: std::io::Error) -> Self {
ProcessingError::FileOperation {
path: PathBuf::new(),
details: error.to_string(),
source: Some(Box::new(error)),
}
}
}
impl From<serde_json::Error> for ProcessingError {
fn from(error: serde_json::Error) -> Self {
ProcessingError::Serialization {
details: error.to_string(),
source: Some(Box::new(error)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error, ErrorKind};
#[test]
fn test_content_processing() {
let error = ProcessingError::content_processing(
"Failed to process",
None,
);
assert!(matches!(
error,
ProcessingError::ContentProcessing { .. }
));
assert!(error.to_string().contains("Failed to process"));
}
#[test]
fn test_file_not_found_error() {
let path = PathBuf::from("/test/path");
let io_error =
Error::new(ErrorKind::NotFound, "file not found");
let error = ProcessingError::file_operation(
path,
"Operation failed",
Some(Box::new(io_error)),
);
assert!(matches!(error, ProcessingError::FileOperation { .. }));
assert!(error.to_string().contains("/test/path"));
}
#[test]
fn test_template_processing_error() {
let error = ProcessingError::template_processing(
"main.hbs",
"Template syntax error",
None,
);
assert!(matches!(
error,
ProcessingError::TemplateProcessing { .. }
));
assert!(error.to_string().contains("main.hbs"));
}
#[test]
fn test_configuration_error() {
let path = PathBuf::from("/config/file.toml");
let error = ProcessingError::configuration(
"Invalid configuration",
Some(path),
None,
);
assert!(matches!(error, ProcessingError::Configuration { .. }));
assert!(error.to_string().contains("Invalid configuration"));
}
#[test]
fn test_io_error_conversion() {
let io_error =
Error::new(ErrorKind::NotFound, "file not found");
let error: ProcessingError = io_error.into();
assert!(matches!(error, ProcessingError::FileOperation { .. }));
}
#[test]
fn test_validation_error() {
let error = ProcessingError::validation(
"Invalid input",
Some("Expected positive number"),
);
assert!(matches!(error, ProcessingError::Validation { .. }));
assert!(error.to_string().contains("Invalid input"));
}
}