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    /// Property access (dot notation) on a non-object variable
78    InvalidPropertyAccess {
79        /// The full reference (e.g., "@variables.count.foo")
80        reference: String,
81        /// The variable name
82        variable: String,
83        /// The declared type of the variable
84        variable_type: String,
85        /// Source location of the reference
86        span: Span,
87    },
88}
89
90impl ValidationError {
91    /// Get the primary span for this error.
92    pub fn span(&self) -> Option<Span> {
93        match self {
94            ValidationError::UnresolvedReference { span, .. }
95            | ValidationError::UnreachableTopic { span, .. }
96            | ValidationError::UnusedActionDef { span, .. }
97            | ValidationError::UnusedVariable { span, .. }
98            | ValidationError::InvalidPropertyAccess { span, .. }
99            | ValidationError::UninitializedVariable {
100                read_span: span, ..
101            } => Some(*span),
102            ValidationError::CycleDetected { .. } => None,
103        }
104    }
105
106    /// Get a human-readable error message.
107    pub fn message(&self) -> String {
108        match self {
109            ValidationError::UnresolvedReference {
110                reference, context, ..
111            } => {
112                format!("Unresolved reference '{}' in {}", reference, context)
113            }
114            ValidationError::CycleDetected { path } => {
115                format!("Cycle detected in topic transitions: {}", path.join(" -> "))
116            }
117            ValidationError::UnreachableTopic { name, .. } => {
118                format!("Topic '{}' is unreachable from start_agent", name)
119            }
120            ValidationError::UnusedActionDef { name, topic, .. } => {
121                format!("Action '{}' in topic '{}' is never invoked", name, topic)
122            }
123            ValidationError::UnusedVariable { name, .. } => {
124                format!("Variable '{}' is never read", name)
125            }
126            ValidationError::UninitializedVariable { name, .. } => {
127                format!("Variable '{}' is read but never written", name)
128            }
129            ValidationError::InvalidPropertyAccess {
130                reference,
131                variable,
132                variable_type,
133                ..
134            } => {
135                format!(
136                    "Property access on '{}' is invalid: variable '{}' has type '{}', only 'object' supports property access",
137                    reference, variable, variable_type
138                )
139            }
140        }
141    }
142
143    /// Check if this is a reference resolution error.
144    pub fn is_unresolved_reference(&self) -> bool {
145        matches!(self, ValidationError::UnresolvedReference { .. })
146    }
147
148    /// Check if this is a cycle error.
149    pub fn is_cycle(&self) -> bool {
150        matches!(self, ValidationError::CycleDetected { .. })
151    }
152
153    /// Check if this is an unused definition warning.
154    pub fn is_unused(&self) -> bool {
155        matches!(
156            self,
157            ValidationError::UnusedActionDef { .. } | ValidationError::UnusedVariable { .. }
158        )
159    }
160}