1use crate::cell::CellId;
4use crate::relationship::RelationshipId;
5use std::any::TypeId;
6
7#[non_exhaustive]
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum ErrorSite {
14 MethodIndex(usize),
17 Method(RelationshipId, usize),
19 Relationship(RelationshipId),
21 Cell(CellId),
23}
24
25#[derive(Debug)]
27#[non_exhaustive]
28pub enum Error {
29 TypeMismatch {
34 expected: TypeId,
36 found: TypeId,
38 sites: Vec<ErrorSite>,
40 },
41
42 InvalidId,
44
45 Conflict {
48 sites: Vec<ErrorSite>,
51 },
52
53 Cycle {
55 sites: Vec<ErrorSite>,
60 },
61
62 SeedCycle {
64 sites: Vec<ErrorSite>,
69 },
70
71 MethodFailed {
73 error: anyhow::Error,
76 sites: Vec<ErrorSite>,
78 },
79
80 InvalidMethod {
85 sites: Vec<ErrorSite>,
88 },
89
90 MismatchedMethodCells {
94 sites: Vec<ErrorSite>,
100 },
101
102 InvalidMethodOutputs {
106 sites: Vec<ErrorSite>,
112 },
113
114 InvalidConditional {
119 sites: Vec<ErrorSite>,
124 },
125
126 InvalidOutput,
129
130 InvalidCellKind {
135 sites: Vec<ErrorSite>,
140 },
141
142 InvalidFilter,
149
150 InvalidRequirement,
154
155 DependencyCycle {
163 sites: Vec<ErrorSite>,
168 },
169}
170
171impl std::fmt::Display for Error {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 match self {
174 Error::TypeMismatch {
175 expected, found, ..
176 } => {
177 write!(f, "type mismatch: expected {expected:?}, found {found:?}")
178 }
179 Error::InvalidId => write!(f, "invalid cell or relationship id"),
180 Error::Conflict { .. } => write!(f, "no valid method assignment (overconstrained)"),
181 Error::Cycle { .. } => write!(f, "selected methods form a cycle"),
182 Error::SeedCycle { .. } => write!(f, "seed dependency cycle"),
183 Error::MethodFailed { error, .. } => write!(f, "method execution failed: {error}"),
184 Error::InvalidMethod { .. } => write!(f, "method is structurally invalid"),
185 Error::MismatchedMethodCells { .. } => write!(
186 f,
187 "methods in a relationship must reference the same set of cells"
188 ),
189 Error::InvalidMethodOutputs { .. } => write!(
190 f,
191 "a method's outputs must be duplicate-free, and no two methods in a \
192 relationship may have identical or nested output sets"
193 ),
194 Error::InvalidConditional { .. } => write!(f, "conditional is structurally invalid"),
195 Error::InvalidOutput => write!(f, "output is structurally invalid"),
196 Error::InvalidCellKind { .. } => {
197 write!(f, "cell's kind does not permit this operation")
198 }
199 Error::InvalidFilter => write!(f, "filter is structurally invalid"),
200 Error::InvalidRequirement => write!(f, "requirement is structurally invalid"),
201 Error::DependencyCycle { .. } => write!(
202 f,
203 "a filter or conditional depends on a cell it governs (dependency cycle)"
204 ),
205 }
206 }
207}
208
209impl std::error::Error for Error {
210 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
212 if let Error::MethodFailed { error, .. } = self {
213 Some(error.as_ref())
214 } else {
215 None
216 }
217 }
218}
219
220impl Error {
221 pub fn sites(&self) -> &[ErrorSite] {
227 match self {
228 Error::TypeMismatch { sites, .. }
229 | Error::MethodFailed { sites, .. }
230 | Error::InvalidMethod { sites }
231 | Error::MismatchedMethodCells { sites }
232 | Error::InvalidMethodOutputs { sites }
233 | Error::InvalidCellKind { sites }
234 | Error::Conflict { sites }
235 | Error::Cycle { sites }
236 | Error::SeedCycle { sites }
237 | Error::DependencyCycle { sites }
238 | Error::InvalidConditional { sites } => sites,
239 _ => &[],
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn type_mismatch_fields_convention() {
250 use std::any::TypeId;
251 let expected = TypeId::of::<i32>();
252 let found = TypeId::of::<f64>();
253 let e = Error::TypeMismatch {
254 expected,
255 found,
256 sites: vec![],
257 };
258 match e {
259 Error::TypeMismatch {
260 expected: e,
261 found: f,
262 ..
263 } => {
264 assert_eq!(e, TypeId::of::<i32>());
265 assert_eq!(f, TypeId::of::<f64>());
266 }
267 _ => panic!("wrong variant"),
268 }
269 }
270
271 #[test]
272 fn type_mismatch_display_contains_type_mismatch() {
273 let err = Error::TypeMismatch {
274 expected: TypeId::of::<i32>(),
275 found: TypeId::of::<f64>(),
276 sites: vec![],
277 };
278 assert!(err.to_string().contains("type mismatch"));
279 }
280
281 #[test]
282 fn invalid_id_display_contains_invalid() {
283 assert!(Error::InvalidId.to_string().contains("invalid"));
284 }
285
286 #[test]
287 fn conflict_display_contains_overconstrained() {
288 assert!(
289 Error::Conflict { sites: vec![] }
290 .to_string()
291 .contains("overconstrained")
292 );
293 }
294
295 #[test]
296 fn cycle_display_contains_cycle() {
297 assert!(Error::Cycle { sites: vec![] }.to_string().contains("cycle"));
298 }
299
300 #[test]
301 fn method_failed_display_contains_source_message() {
302 let err = Error::MethodFailed {
303 error: anyhow::anyhow!("division by zero"),
304 sites: vec![],
305 };
306 assert!(err.to_string().contains("division by zero"));
307 }
308
309 #[test]
310 fn invalid_method_display_contains_invalid() {
311 assert!(
312 Error::InvalidMethod { sites: vec![] }
313 .to_string()
314 .contains("invalid")
315 );
316 }
317
318 #[test]
319 fn error_implements_std_error() {
320 fn takes_error(_: &dyn std::error::Error) {}
321 takes_error(&Error::InvalidId);
322 takes_error(&Error::Conflict { sites: vec![] });
323 }
324
325 #[test]
326 fn method_failed_source_returns_some() {
327 let err = Error::MethodFailed {
328 error: anyhow::anyhow!("inner"),
329 sites: vec![],
330 };
331 assert!(std::error::Error::source(&err).is_some());
332 }
333
334 #[test]
335 fn non_method_failed_variants_have_no_source() {
336 assert!(std::error::Error::source(&Error::InvalidId).is_none());
337 assert!(std::error::Error::source(&Error::Conflict { sites: vec![] }).is_none());
338 assert!(std::error::Error::source(&Error::Cycle { sites: vec![] }).is_none());
339 assert!(std::error::Error::source(&Error::SeedCycle { sites: vec![] }).is_none());
340 assert!(std::error::Error::source(&Error::InvalidMethod { sites: vec![] }).is_none());
341 assert!(
342 std::error::Error::source(&Error::TypeMismatch {
343 expected: std::any::TypeId::of::<i32>(),
344 found: std::any::TypeId::of::<f64>(),
345 sites: vec![],
346 })
347 .is_none()
348 );
349 assert!(
350 std::error::Error::source(&Error::MismatchedMethodCells { sites: vec![] }).is_none()
351 );
352 assert!(
353 std::error::Error::source(&Error::InvalidMethodOutputs { sites: vec![] }).is_none()
354 );
355 }
356
357 #[test]
358 fn invalid_conditional_display_contains_conditional() {
359 assert!(
360 Error::InvalidConditional { sites: vec![] }
361 .to_string()
362 .contains("conditional")
363 );
364 }
365
366 #[test]
367 fn invalid_conditional_has_no_source() {
368 assert!(std::error::Error::source(&Error::InvalidConditional { sites: vec![] }).is_none());
369 }
370
371 #[test]
372 fn mismatched_method_cells_display_contains_cells() {
373 assert!(
374 Error::MismatchedMethodCells { sites: vec![] }
375 .to_string()
376 .contains("cells")
377 );
378 }
379
380 #[test]
381 fn invalid_method_outputs_display_contains_outputs() {
382 assert!(
383 Error::InvalidMethodOutputs { sites: vec![] }
384 .to_string()
385 .contains("outputs")
386 );
387 }
388
389 #[test]
390 fn invalid_output_display_contains_invalid() {
391 assert!(Error::InvalidOutput.to_string().contains("invalid"));
392 }
393
394 #[test]
395 fn invalid_output_has_no_source() {
396 assert!(std::error::Error::source(&Error::InvalidOutput).is_none());
397 }
398
399 #[test]
400 fn invalid_cell_kind_display_contains_kind() {
401 assert!(
402 Error::InvalidCellKind { sites: vec![] }
403 .to_string()
404 .contains("kind")
405 );
406 }
407
408 #[test]
412 fn invalid_cell_kind_display_does_not_mention_terminal() {
413 assert!(
414 !Error::InvalidCellKind { sites: vec![] }
415 .to_string()
416 .contains("terminal")
417 );
418 }
419
420 #[test]
421 fn invalid_cell_kind_has_no_source() {
422 assert!(std::error::Error::source(&Error::InvalidCellKind { sites: vec![] }).is_none());
423 }
424
425 #[test]
426 fn invalid_filter_display_contains_filter() {
427 assert!(Error::InvalidFilter.to_string().contains("filter"));
428 }
429
430 #[test]
431 fn invalid_filter_has_no_source() {
432 assert!(std::error::Error::source(&Error::InvalidFilter).is_none());
433 }
434
435 #[test]
436 fn invalid_requirement_display_contains_requirement() {
437 assert!(
438 Error::InvalidRequirement
439 .to_string()
440 .contains("requirement")
441 );
442 }
443
444 #[test]
445 fn invalid_requirement_has_no_source() {
446 assert!(std::error::Error::source(&Error::InvalidRequirement).is_none());
447 }
448
449 #[test]
450 fn dependency_cycle_display_contains_cycle() {
451 assert!(
452 Error::DependencyCycle { sites: vec![] }
453 .to_string()
454 .contains("cycle")
455 );
456 }
457
458 #[test]
459 fn seed_cycle_display_mentions_seed_dependency_cycle() {
460 assert!(
461 Error::SeedCycle { sites: vec![] }
462 .to_string()
463 .contains("seed dependency cycle")
464 );
465 }
466
467 #[test]
468 fn dependency_cycle_has_no_source() {
469 assert!(std::error::Error::source(&Error::DependencyCycle { sites: vec![] }).is_none());
470 }
471
472 #[test]
473 fn dependency_cycle_exposes_its_sites() {
474 let site = ErrorSite::Cell(CellId::default());
475 let e = Error::DependencyCycle { sites: vec![site] };
476 assert_eq!(e.sites(), &[site]);
477 }
478
479 #[test]
480 fn seed_cycle_exposes_its_sites() {
481 let site = ErrorSite::Cell(CellId::default());
482 let e = Error::SeedCycle { sites: vec![site] };
483 assert_eq!(e.sites(), &[site]);
484 }
485
486 #[test]
487 fn error_site_variants_are_distinct() {
488 let a = ErrorSite::MethodIndex(0);
489 let b = ErrorSite::Method(RelationshipId::default(), 0);
490 let c = ErrorSite::Relationship(RelationshipId::default());
491 assert_ne!(a, b);
492 assert_ne!(b, c);
493 assert_eq!(a, ErrorSite::MethodIndex(0));
494 }
495
496 #[test]
497 fn sites_is_empty_for_a_siteless_variant() {
498 assert!(Error::InvalidId.sites().is_empty());
499 }
500
501 #[test]
502 fn sites_returns_the_recorded_site() {
503 let rid = RelationshipId::default();
504 let e = Error::MethodFailed {
505 error: anyhow::anyhow!("x"),
506 sites: vec![ErrorSite::Method(rid, 2)],
507 };
508 assert_eq!(e.sites(), &[ErrorSite::Method(rid, 2)]);
509 }
510}