Skip to main content

busbar_sf_agentscript/graph/
error.rs

1//! Error types for graph building and validation.
2
3use super::nodes::Span;
4use thiserror::Error;
5
6/// Errors that can occur when building a reference graph from an AST.
7#[derive(Debug, Error)]
8pub enum GraphBuildError {
9    /// The AST is missing required elements
10    #[error("Missing required element: {element}")]
11    MissingElement { element: String },
12
13    /// A duplicate definition was found
14    #[error("Duplicate {kind} definition: {name} at {span:?}")]
15    DuplicateDefinition {
16        kind: String,
17        name: String,
18        span: Span,
19    },
20}
21
22/// Validation errors found in the reference graph.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum ValidationError {
25    /// A reference could not be resolved to a definition
26    UnresolvedReference {
27        /// The reference string (e.g., "@variables.customer_id")
28        reference: String,
29        /// The namespace of the reference
30        namespace: String,
31        /// Source location of the reference
32        span: Span,
33        /// Context where the reference was used
34        context: String,
35    },
36
37    /// A cycle was detected in topic transitions
38    CycleDetected {
39        /// The topics involved in the cycle
40        path: Vec<String>,
41    },
42
43    /// A topic is unreachable from start_agent
44    UnreachableTopic {
45        /// The unreachable topic name
46        name: String,
47        /// Source location
48        span: Span,
49    },
50
51    /// An action definition is never invoked
52    UnusedActionDef {
53        /// The action name
54        name: String,
55        /// The parent topic name
56        topic: String,
57        /// Source location
58        span: Span,
59    },
60
61    /// A variable is never read
62    UnusedVariable {
63        /// The variable name
64        name: String,
65        /// Source location
66        span: Span,
67    },
68
69    /// A variable is read but never written
70    UninitializedVariable {
71        /// The variable name
72        name: String,
73        /// Source location where it's read
74        read_span: Span,
75    },
76}
77
78impl ValidationError {
79    /// Get the primary span for this error.
80    pub fn span(&self) -> Option<Span> {
81        match self {
82            ValidationError::UnresolvedReference { span, .. }
83            | ValidationError::UnreachableTopic { span, .. }
84            | ValidationError::UnusedActionDef { span, .. }
85            | ValidationError::UnusedVariable { span, .. }
86            | ValidationError::UninitializedVariable {
87                read_span: span, ..
88            } => Some(*span),
89            ValidationError::CycleDetected { .. } => None,
90        }
91    }
92
93    /// Get a human-readable error message.
94    pub fn message(&self) -> String {
95        match self {
96            ValidationError::UnresolvedReference {
97                reference, context, ..
98            } => {
99                format!("Unresolved reference '{}' in {}", reference, context)
100            }
101            ValidationError::CycleDetected { path } => {
102                format!("Cycle detected in topic transitions: {}", path.join(" -> "))
103            }
104            ValidationError::UnreachableTopic { name, .. } => {
105                format!("Topic '{}' is unreachable from start_agent", name)
106            }
107            ValidationError::UnusedActionDef { name, topic, .. } => {
108                format!("Action '{}' in topic '{}' is never invoked", name, topic)
109            }
110            ValidationError::UnusedVariable { name, .. } => {
111                format!("Variable '{}' is never read", name)
112            }
113            ValidationError::UninitializedVariable { name, .. } => {
114                format!("Variable '{}' is read but never written", name)
115            }
116        }
117    }
118
119    /// Check if this is a reference resolution error.
120    pub fn is_unresolved_reference(&self) -> bool {
121        matches!(self, ValidationError::UnresolvedReference { .. })
122    }
123
124    /// Check if this is a cycle error.
125    pub fn is_cycle(&self) -> bool {
126        matches!(self, ValidationError::CycleDetected { .. })
127    }
128
129    /// Check if this is an unused definition warning.
130    pub fn is_unused(&self) -> bool {
131        matches!(
132            self,
133            ValidationError::UnusedActionDef { .. } | ValidationError::UnusedVariable { .. }
134        )
135    }
136}