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