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 { stage: String, message: String },
18
19 #[error("Invalid region '{region}': {message}")]
21 Region { region: String, message: String },
22
23 #[error("Invalid layout: {0}")]
25 Layout(String),
26
27 #[error("Invalid graph: {0}")]
29 Graph(String),
30
31 #[error("Invalid transition from '{from}' to '{to}': {message}")]
33 Transition {
34 from: String,
35 to: String,
36 message: String,
37 },
38}
39
40#[derive(Error, Debug)]
42pub enum Error {
43 #[error("Region not found: {0}")]
45 RegionNotFound(String),
46
47 #[error("Region validation failed: {0}")]
49 ValidationFailed(String),
50
51 #[error("Content exceeds token budget: {used} > {max}")]
53 TokenBudgetExceeded { used: usize, max: usize },
54
55 #[error("Pinned regions ({pinned_tokens}) exceed total budget ({total_budget})")]
57 PinnedRegionsOverBudget {
58 pinned_tokens: usize,
59 total_budget: usize,
60 },
61
62 #[error("Blueprint validation failed: {0}")]
64 BlueprintInvalid(String),
65
66 #[error("Layout validation failed: {0}")]
68 LayoutInvalid(String),
69
70 #[error("Context transform failed: {0}")]
72 TransformFailed(String),
73
74 #[error("Serialization error: {0}")]
76 SerializationError(#[from] serde_json::Error),
77
78 #[error("{0}")]
80 Other(String),
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
90 fn validation_error_blueprint() {
91 let e = ValidationError::Blueprint("missing name".into());
92 assert_eq!(e.to_string(), "Invalid blueprint: missing name");
93 }
94
95 #[test]
96 fn validation_error_stage() {
97 let e = ValidationError::Stage {
98 stage: "init".into(),
99 message: "no prompt".into(),
100 };
101 assert_eq!(e.to_string(), "Invalid stage 'init': no prompt");
102 }
103
104 #[test]
105 fn validation_error_region() {
106 let e = ValidationError::Region {
107 region: "context".into(),
108 message: "too large".into(),
109 };
110 assert_eq!(e.to_string(), "Invalid region 'context': too large");
111 }
112
113 #[test]
114 fn validation_error_layout() {
115 let e = ValidationError::Layout("overlapping regions".into());
116 assert_eq!(e.to_string(), "Invalid layout: overlapping regions");
117 }
118
119 #[test]
120 fn validation_error_graph() {
121 let e = ValidationError::Graph("cycle detected".into());
122 assert_eq!(e.to_string(), "Invalid graph: cycle detected");
123 }
124
125 #[test]
126 fn validation_error_transition() {
127 let e = ValidationError::Transition {
128 from: "A".into(),
129 to: "B".into(),
130 message: "missing condition".into(),
131 };
132 assert_eq!(
133 e.to_string(),
134 "Invalid transition from 'A' to 'B': missing condition"
135 );
136 }
137
138 #[test]
141 fn error_region_not_found() {
142 let e = Error::RegionNotFound("history".into());
143 assert_eq!(e.to_string(), "Region not found: history");
144 }
145
146 #[test]
147 fn error_validation_failed() {
148 let e = Error::ValidationFailed("bad input".into());
149 assert_eq!(e.to_string(), "Region validation failed: bad input");
150 }
151
152 #[test]
153 fn error_token_budget_exceeded() {
154 let e = Error::TokenBudgetExceeded {
155 used: 500,
156 max: 100,
157 };
158 assert_eq!(e.to_string(), "Content exceeds token budget: 500 > 100");
159 }
160
161 #[test]
162 fn error_pinned_regions_over_budget() {
163 let e = Error::PinnedRegionsOverBudget {
164 pinned_tokens: 2000,
165 total_budget: 1000,
166 };
167 assert_eq!(
168 e.to_string(),
169 "Pinned regions (2000) exceed total budget (1000)"
170 );
171 }
172
173 #[test]
174 fn error_blueprint_invalid() {
175 let e = Error::BlueprintInvalid("parse error".into());
176 assert_eq!(e.to_string(), "Blueprint validation failed: parse error");
177 }
178
179 #[test]
180 fn error_layout_invalid() {
181 let e = Error::LayoutInvalid("bad layout".into());
182 assert_eq!(e.to_string(), "Layout validation failed: bad layout");
183 }
184
185 #[test]
186 fn error_transform_failed() {
187 let e = Error::TransformFailed("script error".into());
188 assert_eq!(e.to_string(), "Context transform failed: script error");
189 }
190
191 #[test]
192 fn error_other() {
193 let e = Error::Other("misc".into());
194 assert_eq!(e.to_string(), "misc");
195 }
196
197 #[test]
198 fn error_from_serde_json() {
199 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
200 let e = Error::from(json_err);
201 assert!(e.to_string().contains("Serialization error"));
202 }
203
204 #[test]
207 fn validation_error_is_cloneable() {
208 let e = ValidationError::Graph("cycle".into());
209 let cloned = e.clone();
210 assert_eq!(e.to_string(), cloned.to_string());
211 }
212}