use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("Template error: {0}")]
Template(#[from] TemplateError),
#[cfg(feature = "quality-proxy")]
#[error("Quality validation error: {0}")]
Quality(#[from] QualityError),
#[cfg(feature = "mcp-tools")]
#[error("MCP error: {0}")]
Mcp(#[from] McpError),
#[error("Validation error: {0}")]
Validation(#[from] ValidationError),
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("Internal error: {0}")]
Internal(String),
}
#[derive(Error, Debug)]
pub enum TemplateError {
#[error("Template '{id}' not found")]
NotFound {
id: String,
},
#[error("Template compilation failed: {message}")]
CompilationFailed {
message: String,
},
#[error("Template rendering failed: {message}")]
RenderingFailed {
message: String,
},
#[error("Invalid template definition: {reason}")]
InvalidDefinition {
reason: String,
},
#[error("Template inheritance error: {message}")]
InheritanceError {
message: String,
},
#[error("Schema validation failed: {errors:?}")]
SchemaValidation {
errors: Vec<String>,
},
#[error("Template size {size} exceeds limit {limit}")]
SizeLimit {
size: usize,
limit: usize,
},
}
#[cfg(feature = "quality-proxy")]
#[derive(Error, Debug)]
pub enum QualityError {
#[error("Quality gate failed: {violations:?}")]
QualityGateFailed {
violations: Vec<QualityViolation>,
suggestions: Vec<String>,
},
#[error("Quality proxy unavailable: {reason}")]
ProxyUnavailable {
reason: String,
},
#[error("Quality validation timeout after {duration:?}")]
Timeout {
duration: std::time::Duration,
},
#[error("Invalid quality configuration: {reason}")]
InvalidConfig {
reason: String,
},
#[error("Unknown quality response status: {status}")]
UnknownResponse {
status: String,
},
}
#[cfg(feature = "mcp-tools")]
#[derive(Error, Debug)]
pub enum McpError {
#[error("Invalid MCP request: {message}")]
InvalidRequest {
message: String,
},
#[error("MCP tool '{name}' not found")]
ToolNotFound {
name: String,
},
#[error("MCP protocol error: {message}")]
Protocol {
message: String,
},
#[error("MCP transport error: {message}")]
Transport {
message: String,
},
#[error("MCP operation timeout after {duration:?}")]
Timeout {
duration: std::time::Duration,
},
}
#[derive(Error, Debug)]
pub enum ValidationError {
#[error("Required field '{field}' is missing")]
MissingField {
field: String,
},
#[error("Invalid value for field '{field}': {reason}")]
InvalidValue {
field: String,
reason: String,
},
#[error("Structure validation failed: {reason}")]
StructureError {
reason: String,
},
#[cfg(feature = "todo-validation")]
#[error("Todo validation error: {0}")]
Todo(#[from] TodoValidationError),
#[error("Cross-reference validation failed: {message}")]
CrossReference {
message: String,
},
#[error("Constraint violated: {constraint} - {details}")]
Constraint {
constraint: String,
details: String,
},
}
#[cfg(feature = "todo-validation")]
#[derive(Error, Debug)]
pub enum TodoValidationError {
#[error("Todo '{content}' is not actionable")]
NotActionable {
content: String,
},
#[error("Todo '{content}' is too vague (min {min_chars} chars)")]
TooVague {
content: String,
min_chars: usize,
},
#[error("Todo '{id}' missing time estimate")]
MissingEstimate {
id: String,
},
#[error("Circular dependency detected: {cycle:?}")]
CircularDependency {
cycle: Vec<String>,
},
#[error("Invalid priority '{priority}' for todo '{id}'")]
InvalidPriority {
id: String,
priority: String,
},
#[error("Todo count {count} exceeds limit {limit}")]
CountLimit {
count: usize,
limit: usize,
},
#[error("Dependency '{dependency}' not found for todo '{id}'")]
DependencyNotFound {
id: String,
dependency: String,
},
}
#[cfg(feature = "quality-proxy")]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityViolation {
pub violation_type: String,
pub severity: Severity,
pub location: Option<String>,
pub message: String,
pub suggestion: Option<String>,
}
#[cfg(feature = "quality-proxy")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Severity {
Error,
Warning,
Info,
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<serde_yaml::Error> for Error {
fn from(err: serde_yaml::Error) -> Self {
Self::Serialization(err.to_string())
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Self::Serialization(err.to_string())
}
}
impl From<handlebars::RenderError> for TemplateError {
fn from(err: handlebars::RenderError) -> Self {
Self::RenderingFailed {
message: err.to_string(),
}
}
}
impl From<handlebars::TemplateError> for TemplateError {
fn from(err: handlebars::TemplateError) -> Self {
Self::CompilationFailed {
message: err.to_string(),
}
}
}
#[cfg(feature = "quality-proxy")]
impl From<reqwest::Error> for QualityError {
fn from(err: reqwest::Error) -> Self {
if err.is_timeout() {
Self::Timeout {
duration: std::time::Duration::from_secs(30), }
} else {
Self::ProxyUnavailable {
reason: err.to_string(),
}
}
}
}
impl Error {
pub fn invalid_input<S: Into<String>>(message: S) -> Self {
Self::InvalidInput(message.into())
}
pub fn config<S: Into<String>>(message: S) -> Self {
Self::Config(message.into())
}
pub fn internal<S: Into<String>>(message: S) -> Self {
Self::Internal(message.into())
}
}
impl TemplateError {
pub fn not_found<S: Into<String>>(id: S) -> Self {
Self::NotFound { id: id.into() }
}
pub fn invalid_definition<S: Into<String>>(reason: S) -> Self {
Self::InvalidDefinition {
reason: reason.into(),
}
}
#[must_use]
pub const fn size_limit(size: usize, limit: usize) -> Self {
Self::SizeLimit { size, limit }
}
}
impl ValidationError {
pub fn missing_field<S: Into<String>>(field: S) -> Self {
ValidationError::MissingField {
field: field.into(),
}
}
pub fn invalid_value<S: Into<String>>(field: S, reason: S) -> Self {
ValidationError::InvalidValue {
field: field.into(),
reason: reason.into(),
}
}
pub fn structure<S: Into<String>>(reason: S) -> Self {
ValidationError::StructureError {
reason: reason.into(),
}
}
}
#[cfg(feature = "quality-proxy")]
impl QualityViolation {
pub fn new<S: Into<String>>(violation_type: S, severity: Severity, message: S) -> Self {
Self {
violation_type: violation_type.into(),
severity,
location: None,
message: message.into(),
suggestion: None,
}
}
#[must_use]
pub fn with_location<S: Into<String>>(mut self, location: S) -> Self {
self.location = Some(location.into());
self
}
#[must_use]
pub fn with_suggestion<S: Into<String>>(mut self, suggestion: S) -> Self {
self.suggestion = Some(suggestion.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let err = Error::invalid_input("test message");
assert!(matches!(err, Error::InvalidInput(_)));
let err = TemplateError::not_found("test_template");
assert!(matches!(err, TemplateError::NotFound { .. }));
}
#[test]
fn test_error_display() {
let err = Error::invalid_input("test input error");
assert!(err.to_string().contains("test input error"));
let err = TemplateError::not_found("missing_template");
assert!(err.to_string().contains("missing_template"));
}
#[cfg(feature = "quality-proxy")]
#[test]
fn test_quality_violation() {
let violation = QualityViolation::new("complexity", Severity::Error, "Too complex")
.with_location("file.rs:10:5")
.with_suggestion("Split into smaller functions");
assert_eq!(violation.violation_type, "complexity");
assert_eq!(violation.severity, Severity::Error);
assert_eq!(violation.location, Some("file.rs:10:5".to_string()));
}
}