use thiserror::Error;
#[derive(Error, Debug)]
pub enum GgenError {
#[error("Template not found: {path}")]
TemplateNotFound {
path: String,
context: String,
suggestion: Option<String>,
},
#[error("Invalid template syntax in {file}:{line}:{column}\n{snippet}\n{reason}")]
InvalidTemplateSyntax {
file: String,
line: usize,
column: usize,
reason: String,
snippet: String,
fix_suggestion: Option<String>,
},
#[error("Template validation failed: {0}")]
TemplateValidationFailed(#[from] ValidationError),
#[error("Template rendering failed: {reason}\nTemplate: {template}\nContext: {context}")]
RenderingFailed {
template: String,
context: String,
reason: String,
debug_info: Option<String>,
},
#[error("Invalid command: '{command}'\n{suggestion}")]
InvalidCommand {
command: String,
suggestion: String,
valid_commands: Vec<String>,
},
#[error("Missing required argument: {arg}\nUsage: {usage}")]
MissingArgument { arg: String, usage: String },
#[error("Invalid argument value: {arg} = '{value}'\nExpected: {expected}\nGot: {actual}")]
InvalidArgumentValue {
arg: String,
value: String,
expected: String,
actual: String,
allowed_values: Vec<String>,
},
#[error("API incompatibility: {message}\nExpected version: {expected_version}\nActual version: {actual_version}\nSuggestion: {suggestion}")]
ApiIncompatible {
message: String,
expected_version: String,
actual_version: String,
suggestion: String,
},
#[error("Contract violation: {contract} failed\nReason: {reason}\nFix: {fix_suggestion}")]
ContractViolation {
contract: String,
reason: String,
fix_suggestion: String,
affected_components: Vec<String>,
},
#[error("Integration test failed: {test_name}\nExpected: {expected}\nActual: {actual}")]
IntegrationTestFailed {
test_name: String,
expected: String,
actual: String,
diff: Option<String>,
},
#[error("IO error: {operation} failed for {path}\nReason: {source}\nSuggestion: {suggestion}")]
Io {
operation: String,
path: String,
#[source]
source: std::io::Error,
suggestion: String,
},
#[error("Configuration error: {0}")]
Config(#[from] ConfigError),
#[error("Permission denied: {operation} on {resource}\nRequired permissions: {required}\nSuggestion: {suggestion}")]
PermissionDenied {
operation: String,
resource: String,
required: String,
suggestion: String,
},
#[error("Multiple errors occurred:\nPrimary: {primary}\nAdditional errors: {count}")]
Multiple {
primary: Box<GgenError>,
errors: Vec<GgenError>,
count: usize,
},
}
#[derive(Error, Debug)]
pub enum ValidationError {
#[error("Missing required field: '{field}' in {location}")]
MissingField {
field: String,
location: String,
expected_type: String,
},
#[error("Type mismatch: expected {expected}, got {actual} at {location}")]
TypeMismatch {
expected: String,
actual: String,
location: String,
value: String,
},
#[error("Constraint violation: {constraint}\nValue: {value}\nAllowed: {allowed_values:?}")]
ConstraintViolation {
constraint: String,
value: String,
allowed_values: Vec<String>,
suggestion: Option<String>,
},
#[error("Schema validation failed: {reason}\nSchema: {schema}\nData: {data}")]
SchemaValidationFailed {
reason: String,
schema: String,
data: String,
errors: Vec<String>,
},
}
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("Configuration file not found: {path}\nSearched locations: {searched:?}")]
FileNotFound {
path: String,
searched: Vec<String>,
},
#[error("Invalid configuration: {reason}\nFile: {file}\nLine: {line}")]
InvalidConfig {
reason: String,
file: String,
line: usize,
},
#[error("Configuration parse error: {0}")]
ParseError(String),
#[error("Environment variable not set: {var}\nRequired for: {reason}\nSuggestion: export {var}={example}")]
MissingEnvVar {
var: String,
reason: String,
example: String,
},
}
pub type Result<T> = std::result::Result<T, GgenError>;
pub trait ErrorContext<T> {
fn context(self, msg: impl Into<String>) -> Result<T>;
fn with_context<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> String;
}
impl<T, E: Into<GgenError>> ErrorContext<T> for std::result::Result<T, E> {
fn context(self, msg: impl Into<String>) -> Result<T> {
self.map_err(|e| {
let error: GgenError = e.into();
error
})
}
fn with_context<F>(self, f: F) -> Result<T>
where
F: FnOnce() -> String,
{
self.map_err(|e| {
let error: GgenError = e.into();
let _context = f();
error
})
}
}
pub struct ErrorBuilder {
error_type: ErrorType,
context: Option<String>,
suggestion: Option<String>,
}
enum ErrorType {
TemplateNotFound { path: String },
InvalidSyntax { file: String, line: usize },
}
impl ErrorBuilder {
pub fn template_not_found(path: impl Into<String>) -> Self {
Self {
error_type: ErrorType::TemplateNotFound { path: path.into() },
context: None,
suggestion: None,
}
}
pub fn context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
pub fn suggestion(mut self, suggestion: impl Into<String>) -> Self {
self.suggestion = Some(suggestion.into());
self
}
pub fn build(self) -> GgenError {
match self.error_type {
ErrorType::TemplateNotFound { path } => GgenError::TemplateNotFound {
path,
context: self.context.unwrap_or_default(),
suggestion: self.suggestion,
},
ErrorType::InvalidSyntax { file, line } => GgenError::InvalidTemplateSyntax {
file,
line,
column: 0,
reason: self.context.unwrap_or_default(),
snippet: String::new(),
fix_suggestion: self.suggestion,
},
}
}
}
pub fn format_error(error: &GgenError) -> String {
format!("ERROR: {}\n\nFor more information, run with RUST_LOG=debug", error)
}
pub fn report_error(error: &GgenError) {
eprintln!("{}", format_error(error));
let mut current_error: Option<&dyn std::error::Error> = Some(error);
let mut level = 0;
while let Some(err) = current_error {
if level > 0 {
eprintln!(" Caused by ({}): {}", level, err);
}
current_error = err.source();
level += 1;
}
}
impl From<std::io::Error> for GgenError {
fn from(source: std::io::Error) -> Self {
GgenError::Io {
operation: "IO operation".to_string(),
path: "unknown".to_string(),
source,
suggestion: "Check file permissions and disk space".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_builder() {
let error = ErrorBuilder::template_not_found("test.tmpl")
.context("Loading template")
.suggestion("Create the template file")
.build();
match error {
GgenError::TemplateNotFound {
path,
context,
suggestion,
} => {
assert_eq!(path, "test.tmpl");
assert_eq!(context, "Loading template");
assert_eq!(suggestion, Some("Create the template file".to_string()));
}
_ => panic!("Expected TemplateNotFound error"),
}
}
#[test]
fn test_error_context() {
fn failing_operation() -> std::result::Result<(), std::io::Error> {
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"file not found",
))
}
let result: Result<()> = failing_operation().context("Custom context");
assert!(result.is_err());
}
#[test]
fn test_format_error() {
let error = GgenError::TemplateNotFound {
path: "test.tmpl".to_string(),
context: "test context".to_string(),
suggestion: Some("try this".to_string()),
};
let formatted = format_error(&error);
assert!(formatted.contains("Template not found"));
assert!(formatted.contains("test.tmpl"));
}
}