1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Debug, Clone, Error, PartialEq)]
10pub enum ValidationError {
11 #[error("Invalid blueprint: {0}")]
13 Blueprint(String),
14
15 #[error("Invalid stage '{stage}': {message}")]
17 Stage {
18 stage: String,
20 message: String,
22 },
23
24 #[error("Invalid region '{region}': {message}")]
26 Region {
27 region: String,
29 message: String,
31 },
32
33 #[error("Invalid layout: {0}")]
35 Layout(String),
36
37 #[error("Invalid graph: {0}")]
39 Graph(String),
40
41 #[error("Invalid transition from '{from}' to '{to}': {message}")]
43 Transition {
44 from: String,
46 to: String,
48 message: String,
50 },
51}
52
53#[derive(Error, Debug)]
55pub enum Error {
56 #[error("Region not found: {0}")]
58 RegionNotFound(String),
59
60 #[error("Region validation failed: {0}")]
62 ValidationFailed(String),
63
64 #[error("Content exceeds token budget: {used} > {max}")]
66 TokenBudgetExceeded {
67 used: usize,
69 max: usize,
71 },
72
73 #[error("Pinned regions ({pinned_tokens}) exceed total budget ({total_budget})")]
75 PinnedRegionsOverBudget {
76 pinned_tokens: usize,
79 total_budget: usize,
81 },
82
83 #[error("Blueprint validation failed: {0}")]
85 BlueprintInvalid(String),
86
87 #[error("Layout validation failed: {0}")]
89 LayoutInvalid(String),
90
91 #[error("Context transform failed: {0}")]
93 TransformFailed(String),
94
95 #[error("Serialization error: {0}")]
97 SerializationError(#[from] serde_json::Error),
98
99 #[error("{0}")]
101 Other(String),
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
111 fn validation_error_blueprint() {
112 let e = ValidationError::Blueprint("missing name".into());
113 assert_eq!(e.to_string(), "Invalid blueprint: missing name");
114 }
115
116 #[test]
117 fn validation_error_stage() {
118 let e = ValidationError::Stage {
119 stage: "init".into(),
120 message: "no prompt".into(),
121 };
122 assert_eq!(e.to_string(), "Invalid stage 'init': no prompt");
123 }
124
125 #[test]
126 fn validation_error_region() {
127 let e = ValidationError::Region {
128 region: "context".into(),
129 message: "too large".into(),
130 };
131 assert_eq!(e.to_string(), "Invalid region 'context': too large");
132 }
133
134 #[test]
135 fn validation_error_layout() {
136 let e = ValidationError::Layout("overlapping regions".into());
137 assert_eq!(e.to_string(), "Invalid layout: overlapping regions");
138 }
139
140 #[test]
141 fn validation_error_graph() {
142 let e = ValidationError::Graph("cycle detected".into());
143 assert_eq!(e.to_string(), "Invalid graph: cycle detected");
144 }
145
146 #[test]
147 fn validation_error_transition() {
148 let e = ValidationError::Transition {
149 from: "A".into(),
150 to: "B".into(),
151 message: "missing condition".into(),
152 };
153 assert_eq!(
154 e.to_string(),
155 "Invalid transition from 'A' to 'B': missing condition"
156 );
157 }
158
159 #[test]
162 fn error_region_not_found() {
163 let e = Error::RegionNotFound("history".into());
164 assert_eq!(e.to_string(), "Region not found: history");
165 }
166
167 #[test]
168 fn error_validation_failed() {
169 let e = Error::ValidationFailed("bad input".into());
170 assert_eq!(e.to_string(), "Region validation failed: bad input");
171 }
172
173 #[test]
174 fn error_token_budget_exceeded() {
175 let e = Error::TokenBudgetExceeded {
176 used: 500,
177 max: 100,
178 };
179 assert_eq!(e.to_string(), "Content exceeds token budget: 500 > 100");
180 }
181
182 #[test]
183 fn error_pinned_regions_over_budget() {
184 let e = Error::PinnedRegionsOverBudget {
185 pinned_tokens: 2000,
186 total_budget: 1000,
187 };
188 assert_eq!(
189 e.to_string(),
190 "Pinned regions (2000) exceed total budget (1000)"
191 );
192 }
193
194 #[test]
195 fn error_blueprint_invalid() {
196 let e = Error::BlueprintInvalid("parse error".into());
197 assert_eq!(e.to_string(), "Blueprint validation failed: parse error");
198 }
199
200 #[test]
201 fn error_layout_invalid() {
202 let e = Error::LayoutInvalid("bad layout".into());
203 assert_eq!(e.to_string(), "Layout validation failed: bad layout");
204 }
205
206 #[test]
207 fn error_transform_failed() {
208 let e = Error::TransformFailed("script error".into());
209 assert_eq!(e.to_string(), "Context transform failed: script error");
210 }
211
212 #[test]
213 fn error_other() {
214 let e = Error::Other("misc".into());
215 assert_eq!(e.to_string(), "misc");
216 }
217
218 #[test]
219 fn error_from_serde_json() {
220 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
221 let e = Error::from(json_err);
222 assert!(e.to_string().contains("Serialization error"));
223 }
224
225 #[test]
228 fn validation_error_is_cloneable() {
229 let e = ValidationError::Graph("cycle".into());
230 let cloned = e.clone();
231 assert_eq!(e.to_string(), cloned.to_string());
232 }
233}