Skip to main content

mars_agents/
diagnostic.rs

1use serde::Serialize;
2
3/// A diagnostic message from library code.
4#[derive(Debug, Clone, Serialize)]
5pub struct Diagnostic {
6    pub level: DiagnosticLevel,
7    /// Machine-readable code, e.g. "shadow-collision", "manifest-path-dep".
8    pub code: &'static str,
9    /// Human-readable message.
10    pub message: String,
11    /// Optional context (source name, item path, etc.).
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub context: Option<String>,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
17#[serde(rename_all = "lowercase")]
18pub enum DiagnosticLevel {
19    Warning,
20    Info,
21}
22
23/// Collects diagnostics during pipeline execution.
24pub struct DiagnosticCollector {
25    diagnostics: Vec<Diagnostic>,
26}
27
28impl DiagnosticCollector {
29    pub fn new() -> Self {
30        Self {
31            diagnostics: Vec::new(),
32        }
33    }
34
35    pub fn warn(&mut self, code: &'static str, message: impl Into<String>) {
36        self.diagnostics.push(Diagnostic {
37            level: DiagnosticLevel::Warning,
38            code,
39            message: message.into(),
40            context: None,
41        });
42    }
43
44    pub fn info(&mut self, code: &'static str, message: impl Into<String>) {
45        self.diagnostics.push(Diagnostic {
46            level: DiagnosticLevel::Info,
47            code,
48            message: message.into(),
49            context: None,
50        });
51    }
52
53    pub fn warn_with_context(
54        &mut self,
55        code: &'static str,
56        message: impl Into<String>,
57        context: impl Into<String>,
58    ) {
59        self.diagnostics.push(Diagnostic {
60            level: DiagnosticLevel::Warning,
61            code,
62            message: message.into(),
63            context: Some(context.into()),
64        });
65    }
66
67    pub fn extend(&mut self, diagnostics: Vec<Diagnostic>) {
68        self.diagnostics.extend(diagnostics);
69    }
70
71    pub fn drain(&mut self) -> Vec<Diagnostic> {
72        std::mem::take(&mut self.diagnostics)
73    }
74
75    pub fn is_empty(&self) -> bool {
76        self.diagnostics.is_empty()
77    }
78}
79
80impl Default for DiagnosticCollector {
81    fn default() -> Self {
82        Self::new()
83    }
84}
85
86impl std::fmt::Display for Diagnostic {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        let prefix = match self.level {
89            DiagnosticLevel::Warning => "warning",
90            DiagnosticLevel::Info => "info",
91        };
92        write!(f, "{prefix}: {}", self.message)
93    }
94}