Skip to main content

hayson_engine/
errors.rs

1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct HaysonSchemaError(pub String);
5
6impl fmt::Display for HaysonSchemaError {
7    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
8        write!(f, "{}", self.0)
9    }
10}
11
12impl std::error::Error for HaysonSchemaError {}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct ValidationIssue {
16    pub field: String,
17    pub reason: String,
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct HaysonValidationError {
22    pub table: String,
23    pub issues: Vec<ValidationIssue>,
24}
25
26impl fmt::Display for HaysonValidationError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        let detail = self
29            .issues
30            .iter()
31            .map(|issue| format!("{}: {}", issue.field, issue.reason))
32            .collect::<Vec<_>>()
33            .join("; ");
34        write!(f, "record rejected by schema of table \"{}\" — {}", self.table, detail)
35    }
36}
37
38impl std::error::Error for HaysonValidationError {}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct HaysonUniqueViolationError {
42    pub table: String,
43    pub field: String,
44    pub holder: String,
45}
46
47impl fmt::Display for HaysonUniqueViolationError {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        write!(
50            f,
51            "unique field \"{}\" of table \"{}\" is already taken by key \"{}\"",
52            self.field, self.table, self.holder
53        )
54    }
55}
56
57impl std::error::Error for HaysonUniqueViolationError {}
58
59/// A write refused because it broke an `enforce: true` relation — `from.field`'s new value has no
60/// matching record on the `to` side. `value` is the offending value's JSON text.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct HaysonRelationViolationError {
63    pub relation: String,
64    pub table: String,
65    pub field: String,
66    pub value: String,
67}
68
69impl fmt::Display for HaysonRelationViolationError {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(
72            f,
73            "relation \"{}\" refuses \"{}\".\"{}\" = {}: no record on the other side of the relation matches it",
74            self.relation, self.table, self.field, self.value
75        )
76    }
77}
78
79impl std::error::Error for HaysonRelationViolationError {}
80
81/// A `delete` refused because an `on_delete: 'restrict'` relation still has a live record
82/// referencing the key being deleted. `referencing` is the count of records found still pointing
83/// at it — capped at the same sample the diagnostic report uses, never a full scan's whole count.
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct HaysonRelationRestrictError {
86    pub relation: String,
87    pub table: String,
88    pub key: String,
89    pub referencing_table: String,
90    pub referencing_field: String,
91    pub referencing: usize,
92}
93
94impl fmt::Display for HaysonRelationRestrictError {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(
97            f,
98            "relation \"{}\" refuses deleting \"{}\".\"{}\": {} record(s) of \"{}\" still point at it through \"{}\"",
99            self.relation, self.table, self.key, self.referencing, self.referencing_table, self.referencing_field
100        )
101    }
102}
103
104impl std::error::Error for HaysonRelationRestrictError {}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct HaysonPendingConflictError {
108    pub table: String,
109    pub key: String,
110    pub fields: Vec<String>,
111}
112
113impl fmt::Display for HaysonPendingConflictError {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        let list = self
116            .fields
117            .iter()
118            .map(|field| format!("\"{field}\""))
119            .collect::<Vec<_>>()
120            .join(", ");
121        write!(
122            f,
123            "key \"{}\" of table \"{}\" already has a queued change on {} — pass force to overwrite it",
124            self.key, self.table, list
125        )
126    }
127}
128
129impl std::error::Error for HaysonPendingConflictError {}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct HaysonCorruptLogError {
133    pub path: String,
134    pub offset: u64,
135    pub reason: String,
136}
137
138impl fmt::Display for HaysonCorruptLogError {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        write!(
141            f,
142            "log \"{}\" is corrupt at offset {}: {}",
143            self.path, self.offset, self.reason
144        )
145    }
146}
147
148impl std::error::Error for HaysonCorruptLogError {}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct HaysonIndexMismatchError {
152    pub path: String,
153    /// The log the snapshot claimed to describe — on a partitioned table, the one partition whose
154    /// fingerprint disagreed, not the table as a whole.
155    pub log_path: String,
156    /// Byte the snapshot claims to describe `log_path` up to; equal to that log's size, because a
157    /// snapshot whose watermark differs is stale rather than mismatched.
158    pub watermark: u64,
159    pub expected_hash: String,
160    pub actual_hash: String,
161}
162
163impl fmt::Display for HaysonIndexMismatchError {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        write!(
166            f,
167            "snapshot \"{}\" claims watermark and schema match the log, but its content fingerprint ({}) does not match the log's ({}) — left on disk for inspection, not silently discarded",
168            self.path, self.expected_hash, self.actual_hash
169        )
170    }
171}
172
173impl std::error::Error for HaysonIndexMismatchError {}
174
175/// The log file behind an open handle is no longer the file at its path: something outside this
176/// engine unlinked or replaced it — a `git checkout` of a data directory kept under version
177/// control, a restore from backup, a stray `mv`. An append would land in the orphaned inode and
178/// vanish with it, so the engine refuses the write; the database has to be reopened to attach to
179/// the file now at that path.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct HaysonLogReplacedError {
182    pub path: String,
183}
184
185impl fmt::Display for HaysonLogReplacedError {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        write!(
188            f,
189            "log file \"{}\" was replaced on disk while this database held it open — reopen the database before writing again",
190            self.path
191        )
192    }
193}
194
195impl std::error::Error for HaysonLogReplacedError {}
196
197#[derive(Debug, Clone, PartialEq, Eq)]
198pub struct HaysonNotFoundError {
199    pub table: String,
200    pub key: String,
201}
202
203impl fmt::Display for HaysonNotFoundError {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        write!(f, "key \"{}\" is not in table \"{}\"", self.key, self.table)
206    }
207}
208
209impl std::error::Error for HaysonNotFoundError {}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
212pub struct HaysonQueryParseError {
213    pub position: usize,
214    pub reason: String,
215}
216
217impl fmt::Display for HaysonQueryParseError {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(
220            f,
221            "query text is invalid at position {}: {}",
222            self.position, self.reason
223        )
224    }
225}
226
227impl std::error::Error for HaysonQueryParseError {}
228
229/// A record cannot be placed in a partition file: its `partitionBy` value cannot name one, or the
230/// write would move an existing key out of the partition it already lives in.
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct HaysonPartitionError {
233    pub table: String,
234    pub field: String,
235    pub reason: String,
236}
237
238impl HaysonPartitionError {
239    pub fn new(table: impl Into<String>, field: impl Into<String>, reason: impl Into<String>) -> Self {
240        HaysonPartitionError {
241            table: table.into(),
242            field: field.into(),
243            reason: reason.into(),
244        }
245    }
246}
247
248impl fmt::Display for HaysonPartitionError {
249    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250        write!(
251            f,
252            "partition field \"{}\" of table \"{}\": {}",
253            self.field, self.table, self.reason
254        )
255    }
256}
257
258impl std::error::Error for HaysonPartitionError {}
259
260/// A `cypher` field failed to seal/open, or the keyring passed to `open()` is itself unusable.
261/// `table` empty means the second case — the message drops the "of table" clause then.
262#[derive(Debug, Clone, PartialEq, Eq)]
263pub struct HaysonCipherError {
264    pub table: String,
265    pub field: String,
266    pub reason: String,
267    pub key_id: Option<String>,
268}
269
270impl HaysonCipherError {
271    pub fn new(table: impl Into<String>, field: impl Into<String>, reason: impl Into<String>) -> Self {
272        HaysonCipherError {
273            table: table.into(),
274            field: field.into(),
275            reason: reason.into(),
276            key_id: None,
277        }
278    }
279
280    pub fn with_key_id(mut self, key_id: impl Into<String>) -> Self {
281        self.key_id = Some(key_id.into());
282        self
283    }
284}
285
286impl fmt::Display for HaysonCipherError {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        if self.table.is_empty() {
289            write!(f, "cypher configuration {}", self.reason)
290        } else {
291            write!(
292                f,
293                "encrypted field \"{}\" of table \"{}\" {}",
294                self.field, self.table, self.reason
295            )
296        }
297    }
298}
299
300impl std::error::Error for HaysonCipherError {}