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(
81 "Region '{region}' is full ({used}/{max} tokens) and does not evict automatically - \
82 release an entry before adding another"
83 )]
84 RegionFull {
85 region: String,
87 used: usize,
89 max: usize,
91 },
92
93 #[error("Pinned regions ({pinned_tokens}) exceed total budget ({total_budget})")]
95 PinnedRegionsOverBudget {
96 pinned_tokens: usize,
99 total_budget: usize,
101 },
102
103 #[error("Blueprint validation failed: {0}")]
105 BlueprintInvalid(String),
106
107 #[error("Layout validation failed: {0}")]
109 LayoutInvalid(String),
110
111 #[error("Context transform failed: {0}")]
113 TransformFailed(String),
114
115 #[error("Serialization error: {0}")]
117 SerializationError(#[from] serde_json::Error),
118
119 #[error("{0}")]
121 Other(String),
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
131 fn validation_error_blueprint() {
132 let e = ValidationError::Blueprint("missing name".into());
133 assert_eq!(e.to_string(), "Invalid blueprint: missing name");
134 }
135
136 #[test]
137 fn validation_error_stage() {
138 let e = ValidationError::Stage {
139 stage: "init".into(),
140 message: "no prompt".into(),
141 };
142 assert_eq!(e.to_string(), "Invalid stage 'init': no prompt");
143 }
144
145 #[test]
146 fn validation_error_region() {
147 let e = ValidationError::Region {
148 region: "context".into(),
149 message: "too large".into(),
150 };
151 assert_eq!(e.to_string(), "Invalid region 'context': too large");
152 }
153
154 #[test]
155 fn validation_error_layout() {
156 let e = ValidationError::Layout("overlapping regions".into());
157 assert_eq!(e.to_string(), "Invalid layout: overlapping regions");
158 }
159
160 #[test]
161 fn validation_error_graph() {
162 let e = ValidationError::Graph("cycle detected".into());
163 assert_eq!(e.to_string(), "Invalid graph: cycle detected");
164 }
165
166 #[test]
167 fn validation_error_transition() {
168 let e = ValidationError::Transition {
169 from: "A".into(),
170 to: "B".into(),
171 message: "missing condition".into(),
172 };
173 assert_eq!(
174 e.to_string(),
175 "Invalid transition from 'A' to 'B': missing condition"
176 );
177 }
178
179 #[test]
182 fn error_region_not_found() {
183 let e = Error::RegionNotFound("history".into());
184 assert_eq!(e.to_string(), "Region not found: history");
185 }
186
187 #[test]
188 fn error_validation_failed() {
189 let e = Error::ValidationFailed("bad input".into());
190 assert_eq!(e.to_string(), "Region validation failed: bad input");
191 }
192
193 #[test]
194 fn error_token_budget_exceeded() {
195 let e = Error::TokenBudgetExceeded {
196 used: 500,
197 max: 100,
198 };
199 assert_eq!(e.to_string(), "Content exceeds token budget: 500 > 100");
200 }
201
202 #[test]
203 fn error_pinned_regions_over_budget() {
204 let e = Error::PinnedRegionsOverBudget {
205 pinned_tokens: 2000,
206 total_budget: 1000,
207 };
208 assert_eq!(
209 e.to_string(),
210 "Pinned regions (2000) exceed total budget (1000)"
211 );
212 }
213
214 #[test]
215 fn error_blueprint_invalid() {
216 let e = Error::BlueprintInvalid("parse error".into());
217 assert_eq!(e.to_string(), "Blueprint validation failed: parse error");
218 }
219
220 #[test]
221 fn error_layout_invalid() {
222 let e = Error::LayoutInvalid("bad layout".into());
223 assert_eq!(e.to_string(), "Layout validation failed: bad layout");
224 }
225
226 #[test]
227 fn error_transform_failed() {
228 let e = Error::TransformFailed("script error".into());
229 assert_eq!(e.to_string(), "Context transform failed: script error");
230 }
231
232 #[test]
233 fn error_other() {
234 let e = Error::Other("misc".into());
235 assert_eq!(e.to_string(), "misc");
236 }
237
238 #[test]
239 fn error_from_serde_json() {
240 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
241 let e = Error::from(json_err);
242 assert!(e.to_string().contains("Serialization error"));
243 }
244
245 #[test]
248 fn validation_error_is_cloneable() {
249 let e = ValidationError::Graph("cycle".into());
250 let cloned = e.clone();
251 assert_eq!(e.to_string(), cloned.to_string());
252 }
253}