1use crate::path::StorePath;
7use error_stack::{Frame, Report, ResultExt};
8use std::fmt;
9use std::path::{Path, PathBuf};
10
11pub fn all<T: Send + Sync + 'static, C>(report: &Report<C>) -> impl Iterator<Item = &T> {
16 report.frames().filter_map(Frame::downcast_ref::<T>)
17}
18
19macro_rules! fact {
20 ($(#[$meta:meta])* $name:ident($ty:ty) => $label:literal) => {
21 $(#[$meta])*
22 #[derive(Debug, Clone, PartialEq, Eq)]
23 pub struct $name(pub $ty);
24
25 impl fmt::Display for $name {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, concat!($label, ": {}"), self.0)
28 }
29 }
30 };
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub struct StoreFile(pub PathBuf);
36
37impl fmt::Display for StoreFile {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "store: {}", self.0.display())
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct MetaFile(pub PathBuf);
47
48impl fmt::Display for MetaFile {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(f, "meta file: {}", self.0.display())
51 }
52}
53
54fact!(
55 Key(StorePath) => "key"
57);
58fact!(
59 Prefix(StorePath) => "prefix"
61);
62fact!(
63 RawKey(String) => "stored key"
67);
68fact!(
69 Table(String) => "table"
71);
72fact!(
73 Refused(String) => "refused"
79);
80fact!(
81 Entry(String) => "entry"
84);
85fact!(
86 Migrating(String) => "migrating"
89);
90fact!(
91 MetaNode(String) => "meta node"
94);
95fact!(
96 ValueBytes(usize) => "value bytes"
98);
99fact!(
100 Read(usize) => "read so far"
102);
103fact!(
104 Buffered(usize) => "buffered entries"
106);
107
108pub trait Facts: ResultExt + Sized {
111 fn attach_store_file(self, file: &Path) -> Result<Self::Ok, Report<Self::Context>>;
112 fn attach_meta_file(self, file: &Path) -> Result<Self::Ok, Report<Self::Context>>;
113 fn attach_key(self, key: &StorePath) -> Result<Self::Ok, Report<Self::Context>>;
114 fn attach_prefix(self, prefix: &StorePath) -> Result<Self::Ok, Report<Self::Context>>;
115 fn attach_raw_key(self, key: &str) -> Result<Self::Ok, Report<Self::Context>>;
116 fn attach_table(self, table: &str) -> Result<Self::Ok, Report<Self::Context>>;
117 fn attach_meta_node(self, node: impl fmt::Display) -> Result<Self::Ok, Report<Self::Context>>;
121 fn attach_entry(self, name: &str) -> Result<Self::Ok, Report<Self::Context>>;
122 fn attach_migrating(self, prefix: &StorePath) -> Result<Self::Ok, Report<Self::Context>>;
123 fn attach_value_bytes(self, len: usize) -> Result<Self::Ok, Report<Self::Context>>;
124 fn attach_read_so_far(self, count: usize) -> Result<Self::Ok, Report<Self::Context>>;
125 fn attach_buffered(self, count: usize) -> Result<Self::Ok, Report<Self::Context>>;
126}
127
128impl<R: ResultExt> Facts for R {
129 fn attach_store_file(self, file: &Path) -> Result<R::Ok, Report<R::Context>> {
130 self.attach_with(|| StoreFile(file.to_path_buf()))
131 }
132
133 fn attach_meta_file(self, file: &Path) -> Result<R::Ok, Report<R::Context>> {
134 self.attach_with(|| MetaFile(file.to_path_buf()))
135 }
136
137 fn attach_key(self, key: &StorePath) -> Result<R::Ok, Report<R::Context>> {
138 self.attach_with(|| Key(key.clone()))
139 }
140
141 fn attach_prefix(self, prefix: &StorePath) -> Result<R::Ok, Report<R::Context>> {
142 self.attach_with(|| Prefix(prefix.clone()))
143 }
144
145 fn attach_raw_key(self, key: &str) -> Result<R::Ok, Report<R::Context>> {
146 self.attach_with(|| RawKey(key.to_owned()))
147 }
148
149 fn attach_table(self, table: &str) -> Result<R::Ok, Report<R::Context>> {
150 self.attach_with(|| Table(table.to_owned()))
151 }
152
153 fn attach_meta_node(self, node: impl fmt::Display) -> Result<R::Ok, Report<R::Context>> {
154 self.attach_with(|| MetaNode(node.to_string()))
155 }
156
157 fn attach_entry(self, name: &str) -> Result<R::Ok, Report<R::Context>> {
158 self.attach_with(|| Entry(name.to_owned()))
159 }
160
161 fn attach_migrating(self, prefix: &StorePath) -> Result<R::Ok, Report<R::Context>> {
162 self.attach_with(|| Migrating(prefix.to_string()))
163 }
164
165 fn attach_value_bytes(self, len: usize) -> Result<R::Ok, Report<R::Context>> {
166 self.attach_with(|| ValueBytes(len))
167 }
168
169 fn attach_read_so_far(self, count: usize) -> Result<R::Ok, Report<R::Context>> {
170 self.attach_with(|| Read(count))
171 }
172
173 fn attach_buffered(self, count: usize) -> Result<R::Ok, Report<R::Context>> {
174 self.attach_with(|| Buffered(count))
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use crate::failure::StorageError;
182
183 fn failing() -> Result<(), Report<StorageError>> {
184 Err(Report::new(StorageError::Write))
185 }
186
187 #[test]
188 fn a_fact_reads_back_by_its_type() {
189 let path = StorePath::from_segments(["ui", "theme"]);
190 let report = failing().attach_key(&path).unwrap_err();
191
192 let found: Vec<&Key> = all::<Key, _>(&report).collect();
193 assert_eq!(found, vec![&Key(path)]);
194 }
195
196 #[test]
197 fn two_facts_of_different_types_do_not_collide() {
198 let key = StorePath::from_segments(["ui", "theme"]);
199 let prefix = StorePath::segment("ui");
200 let report = failing()
201 .attach_key(&key)
202 .attach_prefix(&prefix)
203 .unwrap_err();
204
205 assert_eq!(all::<Key, _>(&report).count(), 1);
206 assert_eq!(all::<Prefix, _>(&report).count(), 1);
207 assert_eq!(
208 all::<Prefix, _>(&report).next(),
209 Some(&Prefix(prefix)),
210 "a prefix must not be read back as a key"
211 );
212 }
213
214 #[test]
215 fn the_same_fact_attached_twice_is_visible_as_twice() {
216 let key = StorePath::from_segments(["ui", "theme"]);
217 let report = failing().attach_key(&key).attach_key(&key).unwrap_err();
218
219 assert_eq!(
220 all::<Key, _>(&report).count(),
221 2,
222 "a duplicate has to be countable before anything can collapse it"
223 );
224 }
225
226 #[test]
227 fn a_fact_prints_with_its_label() {
228 let report = failing()
229 .attach_key(&StorePath::from_segments(["ui", "theme"]))
230 .unwrap_err();
231
232 assert!(
233 format!("{report:?}").contains("key: ui.theme"),
234 "the label lives on the type, so it must reach the printed report"
235 );
236 }
237}