concinnity_world/check/
fault.rs1#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum Step {
11 Field(String),
13 Index(usize),
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Fault {
20 pub message: String,
22 pub at: Vec<Step>,
25}
26
27impl Fault {
28 pub(crate) fn new(message: impl Into<String>) -> Fault {
29 Fault {
30 message: message.into(),
31 at: Vec::new(),
32 }
33 }
34
35 pub(crate) fn within(mut self, step: Step) -> Fault {
38 self.at.insert(0, step);
39 self
40 }
41
42 pub(crate) fn about(mut self, what: &str) -> Fault {
46 self.message = format!("{what} {}", self.message);
47 self
48 }
49}
50
51impl std::fmt::Display for Fault {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.write_str(&self.message)
54 }
55}
56
57pub(crate) fn field(name: &str) -> Step {
58 Step::Field(name.to_string())
59}
60
61pub(crate) trait Locate<T> {
64 fn at_field(self, name: &str) -> Result<T, Fault>;
65 fn at_index(self, index: usize) -> Result<T, Fault>;
66}
67
68impl<T> Locate<T> for Result<T, Fault> {
69 fn at_field(self, name: &str) -> Result<T, Fault> {
70 self.map_err(|f| f.within(field(name)))
71 }
72
73 fn at_index(self, index: usize) -> Result<T, Fault> {
74 self.map_err(|f| f.within(Step::Index(index)))
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
85 fn hops_attached_while_unwinding_read_outermost_first() {
86 let deep: Result<(), Fault> = Err(Fault::new("is missing"));
87 let out = deep
88 .at_field("cond")
89 .at_field("if")
90 .at_index(1)
91 .at_field("do")
92 .unwrap_err();
93 assert_eq!(
94 out.at,
95 vec![field("do"), Step::Index(1), field("if"), field("cond")],
96 );
97 }
98
99 #[test]
100 fn a_fault_with_nothing_to_blame_carries_no_location() {
101 let f = Fault::new("duplicate name");
102 assert!(f.at.is_empty());
103 assert_eq!(f.to_string(), "duplicate name");
104 }
105
106 #[test]
109 fn labels_compose_and_leave_the_location_alone() {
110 let f = Fault::new("is missing")
111 .within(field("value"))
112 .about("`let`")
113 .about("Behavior 'chase':");
114 assert_eq!(f.message, "Behavior 'chase': `let` is missing");
115 assert_eq!(f.at, vec![field("value")]);
116 }
117
118 #[test]
119 fn success_passes_through_untouched() {
120 let ok: Result<u8, Fault> = Ok(7);
121 assert_eq!(ok.at_field("cond").at_index(0).unwrap(), 7);
122 }
123}