use std::fmt;
#[derive(Debug, Clone)]
pub struct BuildError {
pub kind: BuildErrorKind,
pub context: Option<String>,
}
#[derive(Debug, Clone)]
pub enum BuildErrorKind {
ValidationFailed(String),
CheckFailed(CheckReport),
TemplateLoadFailed(String),
DatabaseMissing,
ComponentNotReady(String),
}
#[derive(Debug, Clone)]
pub struct CheckReport {
pub errors: Vec<CheckError>,
}
#[derive(Debug, Clone)]
pub struct CheckError {
pub component: String,
pub message: String,
pub suggestion: Option<String>,
}
impl BuildError {
pub fn validation(msg: impl Into<String>) -> Self {
Self {
kind: BuildErrorKind::ValidationFailed(msg.into()),
context: None,
}
}
pub fn check(report: CheckReport) -> Self {
Self {
kind: BuildErrorKind::CheckFailed(report),
context: None,
}
}
pub fn template(err: impl Into<String>) -> Self {
Self {
kind: BuildErrorKind::TemplateLoadFailed(err.into()),
context: None,
}
}
pub fn database_missing() -> Self {
Self {
kind: BuildErrorKind::DatabaseMissing,
context: None,
}
}
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = Some(context.into());
self
}
}
impl CheckReport {
pub fn new() -> Self {
Self { errors: Vec::new() }
}
pub fn add(&mut self, error: CheckError) {
self.errors.push(error);
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn count(&self) -> usize {
self.errors.len()
}
}
impl Default for CheckReport {
fn default() -> Self {
Self::new()
}
}
impl CheckError {
pub fn new(component: impl Into<String>, message: impl Into<String>) -> Self {
Self {
component: component.into(),
message: message.into(),
suggestion: None,
}
}
pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
self.suggestion = Some(suggestion.into());
self
}
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.kind {
BuildErrorKind::ValidationFailed(msg) => {
write!(f, "Build validation failed: {}", msg)?;
}
BuildErrorKind::CheckFailed(report) => {
writeln!(f, "Build failed with {} check error(s):", report.count())?;
writeln!(f)?;
for (i, err) in report.errors.iter().enumerate() {
write!(f, " {}. {} - {}", i + 1, err.component, err.message)?;
if let Some(suggestion) = &err.suggestion {
write!(f, "\n → {}", suggestion)?;
}
if i < report.errors.len() - 1 {
writeln!(f)?;
}
}
}
BuildErrorKind::TemplateLoadFailed(msg) => {
write!(f, "Template loading failed: {}", msg)?;
}
BuildErrorKind::DatabaseMissing => {
write!(
f,
"Database connection required when 'orm' feature is enabled"
)?;
}
BuildErrorKind::ComponentNotReady(component) => {
write!(f, "Component '{}' is not ready", component)?;
}
}
if let Some(ctx) = &self.context {
write!(f, "\nContext: {}", ctx)?;
}
Ok(())
}
}
impl fmt::Display for CheckError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.component, self.message)?;
if let Some(suggestion) = &self.suggestion {
write!(f, " (Suggestion: {})", suggestion)?;
}
Ok(())
}
}
impl std::error::Error for BuildError {}
impl std::error::Error for CheckError {}
impl From<tera::Error> for BuildError {
fn from(err: tera::Error) -> Self {
Self::template(err.to_string())
}
}