baobao_codegen/pipeline/
diagnostic.rs

1//! Diagnostic types for the compilation pipeline.
2//!
3//! This module provides types for collecting errors, warnings, and informational
4//! messages during compilation phases.
5
6use serde::Serialize;
7
8/// Severity level for a diagnostic message.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
10pub enum Severity {
11    /// A fatal error that prevents further processing.
12    Error,
13    /// A warning that doesn't prevent processing but should be addressed.
14    Warning,
15    /// Informational message about the compilation process.
16    Info,
17}
18
19impl Severity {
20    /// Returns true if this is an error severity.
21    pub fn is_error(&self) -> bool {
22        matches!(self, Severity::Error)
23    }
24
25    /// Returns true if this is a warning severity.
26    pub fn is_warning(&self) -> bool {
27        matches!(self, Severity::Warning)
28    }
29}
30
31impl std::fmt::Display for Severity {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        match self {
34            Severity::Error => write!(f, "error"),
35            Severity::Warning => write!(f, "warning"),
36            Severity::Info => write!(f, "info"),
37        }
38    }
39}
40
41/// A diagnostic message from a compilation phase.
42#[derive(Debug, Clone, Serialize)]
43pub struct Diagnostic {
44    /// The severity level of this diagnostic.
45    pub severity: Severity,
46    /// The phase that produced this diagnostic.
47    pub phase: String,
48    /// The diagnostic message.
49    pub message: String,
50    /// Optional location in the manifest (e.g., "commands.deploy").
51    pub location: Option<String>,
52}
53
54impl Diagnostic {
55    /// Create a new error diagnostic.
56    pub fn error(phase: impl Into<String>, message: impl Into<String>) -> Self {
57        Self {
58            severity: Severity::Error,
59            phase: phase.into(),
60            message: message.into(),
61            location: None,
62        }
63    }
64
65    /// Create a new warning diagnostic.
66    pub fn warning(phase: impl Into<String>, message: impl Into<String>) -> Self {
67        Self {
68            severity: Severity::Warning,
69            phase: phase.into(),
70            message: message.into(),
71            location: None,
72        }
73    }
74
75    /// Create a new info diagnostic.
76    pub fn info(phase: impl Into<String>, message: impl Into<String>) -> Self {
77        Self {
78            severity: Severity::Info,
79            phase: phase.into(),
80            message: message.into(),
81            location: None,
82        }
83    }
84
85    /// Add a location to this diagnostic.
86    pub fn at(mut self, location: impl Into<String>) -> Self {
87        self.location = Some(location.into());
88        self
89    }
90}
91
92impl std::fmt::Display for Diagnostic {
93    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94        write!(f, "{}: {}", self.severity, self.message)?;
95        if let Some(loc) = &self.location {
96            write!(f, " (at {})", loc)?;
97        }
98        Ok(())
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_diagnostic_error() {
108        let diag = Diagnostic::error("validate", "invalid command name");
109        assert!(diag.severity.is_error());
110        assert_eq!(diag.phase, "validate");
111    }
112
113    #[test]
114    fn test_diagnostic_with_location() {
115        let diag = Diagnostic::warning("validate", "missing description").at("commands.deploy");
116        assert!(diag.location.is_some());
117        assert_eq!(diag.location.as_deref(), Some("commands.deploy"));
118    }
119
120    #[test]
121    fn test_severity_display() {
122        assert_eq!(Severity::Error.to_string(), "error");
123        assert_eq!(Severity::Warning.to_string(), "warning");
124        assert_eq!(Severity::Info.to_string(), "info");
125    }
126}