1use thiserror::Error;
4
5#[derive(Debug, Error)]
7#[non_exhaustive]
8pub enum XlogError {
9 #[error("Parse error: {0}")]
11 Parse(String),
12
13 #[error("Stratification failed: cycle through negation involving {0:?}")]
15 StratificationCycle(Vec<String>),
16
17 #[error("Domain safety: variable {0} not bound in positive literal")]
19 UnsafeVariable(String),
20
21 #[error("Invalid configuration {name}={value:?}; expected {expected}")]
23 Configuration {
24 name: String,
26 value: String,
28 expected: &'static str,
30 },
31
32 #[error("Resource exhausted: {context}, estimated {estimated_bytes} bytes, budget {budget_bytes} bytes")]
34 ResourceExhausted {
35 context: String,
37 estimated_bytes: u64,
39 budget_bytes: u64,
41 },
42
43 #[error("Capacity exceeded: {context}, required {required} {unit}, limit {limit} {unit}")]
45 CapacityExceeded {
46 context: String,
48 required: u64,
50 limit: u64,
52 unit: String,
54 },
55
56 #[error(
58 "Convergence failed: {context}, {unconverged_worlds} of {total_worlds} worlds did not converge within {iteration_limit} iterations"
59 )]
60 ConvergenceFailure {
61 context: String,
63 unconverged_worlds: u64,
65 total_worlds: u64,
67 iteration_limit: u64,
69 },
70
71 #[error("D4 compile declined: {context}: {detail}")]
78 CompileCapacityExceeded {
79 context: String,
81 detail: String,
83 },
84
85 #[error("D4 equivalence verify declined: {context}: {detail}")]
93 VerifyBudgetExceeded {
94 context: String,
96 detail: String,
98 },
99
100 #[error("Kernel error: {0}")]
102 Kernel(String),
103
104 #[error("Type error: {0}")]
106 Type(String),
107
108 #[error("Compilation error: {0}")]
110 Compilation(String),
111
112 #[error("Unsupported epistemic construct: {construct} ({context})")]
114 UnsupportedEpistemicConstruct {
115 construct: String,
117 context: String,
119 },
120
121 #[error(
123 "Constraint {constraint_index} violated: {relation_name} produced {witness_rows} witness row(s)"
124 )]
125 ConstraintViolation {
126 constraint_index: usize,
128 relation_name: String,
130 witness_rows: usize,
132 },
133
134 #[error("Execution error: {0}")]
136 Execution(String),
137}
138
139impl XlogError {
140 pub fn kernel_ctx(op: &str, detail: &str, source: &impl std::fmt::Display) -> Self {
142 XlogError::Kernel(format!("{op}: {detail}: {source}"))
143 }
144
145 pub fn execution_ctx(op: &str, detail: &str, source: &impl std::fmt::Display) -> Self {
147 XlogError::Execution(format!("{op}: {detail}: {source}"))
148 }
149
150 pub fn compilation_ctx(op: &str, detail: &str, source: &impl std::fmt::Display) -> Self {
152 XlogError::Compilation(format!("{op}: {detail}: {source}"))
153 }
154}
155
156pub type Result<T> = std::result::Result<T, XlogError>;
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 #[test]
164 fn test_parse_error_display() {
165 let err = XlogError::Parse("unexpected token".to_string());
166 assert_eq!(err.to_string(), "Parse error: unexpected token");
167 }
168
169 #[test]
170 fn test_stratification_cycle_display() {
171 let err = XlogError::StratificationCycle(vec!["foo".to_string(), "bar".to_string()]);
172 assert!(err.to_string().contains("foo"));
173 assert!(err.to_string().contains("bar"));
174 }
175
176 #[test]
177 fn test_resource_exhausted_display() {
178 let err = XlogError::ResourceExhausted {
179 context: "join operation".to_string(),
180 estimated_bytes: 1024,
181 budget_bytes: 512,
182 };
183 assert!(err.to_string().contains("1024"));
184 assert!(err.to_string().contains("512"));
185 }
186
187 #[test]
188 fn test_capacity_exceeded_display_preserves_non_byte_units() {
189 let err = XlogError::CapacityExceeded {
190 context: "resident sparse rows".to_string(),
191 required: 65,
192 limit: 64,
193 unit: "rows per world".to_string(),
194 };
195 assert_eq!(
196 err.to_string(),
197 "Capacity exceeded: resident sparse rows, required 65 rows per world, limit 64 rows per world"
198 );
199 }
200
201 #[test]
202 fn test_convergence_failure_display_reports_affected_worlds_and_limit() {
203 let err = XlogError::ConvergenceFailure {
204 context: "resident Monte Carlo fixpoint".to_string(),
205 unconverged_worlds: 3,
206 total_worlds: 128,
207 iteration_limit: 7,
208 };
209 assert_eq!(
210 err.to_string(),
211 "Convergence failed: resident Monte Carlo fixpoint, 3 of 128 worlds did not converge within 7 iterations"
212 );
213 }
214
215 #[test]
216 fn test_constraint_violation_display() {
217 let err = XlogError::ConstraintViolation {
218 constraint_index: 3,
219 relation_name: "__xlog_constraint_3".to_string(),
220 witness_rows: 2,
221 };
222 assert_eq!(
223 err.to_string(),
224 "Constraint 3 violated: __xlog_constraint_3 produced 2 witness row(s)"
225 );
226 }
227
228 #[test]
229 fn test_kernel_ctx() {
230 let err = XlogError::kernel_ctx("download_column", "dtoh copy failed", &"device error 42");
231 assert_eq!(
232 err.to_string(),
233 "Kernel error: download_column: dtoh copy failed: device error 42"
234 );
235 }
236
237 #[test]
238 fn test_execution_ctx() {
239 let err = XlogError::execution_ctx("execute_node", "filter failed", &"type mismatch");
240 assert_eq!(
241 err.to_string(),
242 "Execution error: execute_node: filter failed: type mismatch"
243 );
244 }
245
246 #[test]
247 fn test_compilation_ctx() {
248 let err = XlogError::compilation_ctx("compile_d4", "frontier overflow", &"limit 1024");
249 assert_eq!(
250 err.to_string(),
251 "Compilation error: compile_d4: frontier overflow: limit 1024"
252 );
253 }
254}