logicaffeine_data/
failure.rs1pub trait ContractReason {
15 fn contract_reason(&self) -> String;
17}
18
19fn refusal(type_name: &str) -> String {
20 format!("The reason after `otherwise` must be Text, but it was {type_name}.")
21}
22
23impl ContractReason for String {
24 fn contract_reason(&self) -> String {
25 self.clone()
26 }
27}
28
29impl ContractReason for &str {
30 fn contract_reason(&self) -> String {
31 (*self).to_string()
32 }
33}
34
35impl ContractReason for i64 {
36 fn contract_reason(&self) -> String {
37 refusal("Int")
38 }
39}
40
41impl ContractReason for crate::types::LogosInt {
42 fn contract_reason(&self) -> String {
43 refusal("Int")
44 }
45}
46
47impl ContractReason for f64 {
48 fn contract_reason(&self) -> String {
49 refusal("Real")
50 }
51}
52
53impl ContractReason for bool {
54 fn contract_reason(&self) -> String {
55 refusal("Bool")
56 }
57}
58
59impl ContractReason for char {
60 fn contract_reason(&self) -> String {
61 refusal("Char")
62 }
63}
64
65pub fn contract_reason<R: ContractReason + ?Sized>(reason: &R) -> String {
67 reason.contract_reason()
68}
69
70pub fn index_out_of_bounds(index: impl std::fmt::Display) -> String {
73 format!("Index {index} out of bounds")
74}
75
76pub fn pop_from_empty(list: &str) -> String {
79 format!("Cannot pop from {list}: it is empty")
80}
81
82pub fn field_access_on(type_name: &str) -> String {
84 format!("Cannot access field on {type_name}")
85}
86
87pub const SET_FIELD_ON_NON_RECORD: &str = "Cannot set field on non-struct value";
89
90pub fn key_not_found(key: &(impl FailureText + ?Sized)) -> String {
92 format!("Key '{}' not found in map", key.failure_text())
93}
94
95pub fn invalid_utf8(error: impl std::fmt::Display) -> String {
98 format!("text_from_bytes(): invalid UTF-8: {error}")
99}
100
101pub fn quantities_differ(
105 verb: &str,
106 a: impl std::fmt::Display,
107 b: impl std::fmt::Display,
108) -> String {
109 format!("cannot {verb} quantities of different dimensions ({a} vs {b})")
110}
111
112pub fn quantities_uncomparable(a: impl std::fmt::Display, b: impl std::fmt::Display) -> String {
114 format!("Cannot compare quantities of different dimensions ({a} vs {b})")
115}
116
117pub const DIVIDE_BY_ZERO_QUANTITY: &str = "cannot divide by a zero quantity";
119
120pub const DIVIDE_QUANTITY_BY_ZERO: &str = "cannot divide a quantity by zero";
122
123pub fn unknown_unit(name: &str) -> String {
125 format!("Unknown unit '{name}'")
126}
127
128pub fn cannot_convert(from_unit: &str, to_unit: &str) -> String {
130 format!("cannot convert a {from_unit} quantity to '{to_unit}' — different dimension")
131}
132
133pub fn unknown_currency(code: &str) -> String {
135 format!("Unknown currency '{code}'")
136}
137
138pub fn currencies_differ(verb: &str, a: &str, b: &str) -> String {
140 format!("cannot {verb} money of different currencies ({a} vs {b})")
141}
142
143pub const DIVIDE_MONEY_BY_ZERO_AMOUNT: &str = "cannot divide money by a zero amount";
145
146pub const DIVIDE_MONEY_BY_ZERO: &str = "cannot divide money by zero";
148
149pub fn no_exchange_rate(from: &str, to: &str) -> String {
151 format!("no exchange rate for {from} or {to}")
152}
153
154pub const NO_EXCHANGE_RATES: &str = "no exchange rates in scope (set a rate first)";
156
157pub const MODULUS_NOT_POSITIVE: &str = "modular() requires a positive modulus";
159
160pub fn modular_rings_differ(op: &str) -> String {
162 format!("cannot {op} values in different modular rings")
163}
164
165pub const NO_MODULAR_INVERSE: &str = "modular divisor has no inverse (not coprime to the modulus)";
167
168pub fn not_a_decimal(text: &str) -> String {
170 format!("Cannot parse '{text}' as Decimal")
171}
172
173pub fn decimal_needs_text_or_int(type_name: &str) -> String {
175 format!("decimal() requires a Text or Int, got {type_name}")
176}
177
178pub fn invalid_uuid(text: &str) -> String {
182 format!("invalid UUID '{text}'")
183}
184
185pub fn uuid_needs_16_bytes(got: usize) -> String {
187 format!("uuid_from_bytes() needs 16 bytes, got {got}")
188}
189
190pub fn not_a_timestamp(text: &str) -> String {
192 format!("Cannot parse '{text}' as an RFC 3339 timestamp")
193}
194
195pub fn unknown_time_zone(name: &str) -> String {
197 format!("Unknown time zone '{name}'")
198}
199
200pub fn cannot_read_bytes(path: &str, reason: impl std::fmt::Display) -> String {
202 format!("readBytes: cannot read '{path}': {reason}")
203}
204
205#[inline]
207pub fn found<V, K: FailureText + ?Sized>(value: Option<V>, key: &K) -> V {
208 match value {
209 Some(value) => value,
210 None => no_value_for(key),
211 }
212}
213
214#[cold]
215#[inline(never)]
216fn no_value_for<K: FailureText + ?Sized>(key: &K) -> ! {
217 panic!("{}", key_not_found(key))
218}
219
220#[inline]
223pub fn answered<V>(answer: Result<V, String>) -> V {
224 match answer {
225 Ok(value) => value,
226 Err(why) => refused(why),
227 }
228}
229
230#[cold]
231#[inline(never)]
232fn refused(why: String) -> ! {
233 panic!("{why}")
234}
235
236pub const CALL_DEPTH_EXCEEDED: &str = "Stack overflow: maximum call depth exceeded";
239
240pub const DIVISION_BY_ZERO: &str = "Division by zero";
242
243pub const MODULO_BY_ZERO: &str = "Modulo by zero";
245
246pub const INT_TOO_WIDE: &str = "integer value out of range for this int64 context";
248
249pub const BITAND_OPERANDS: &str = "`&` requires integer, boolean, or Set operands";
252
253pub const BITOR_OPERANDS: &str = "`|` requires integer, boolean, or Set operands";
255
256pub const BITXOR_OPERANDS: &str = "Bitwise XOR requires integer, boolean, or Set operands";
258
259pub const SHL_OPERANDS: &str = "Left shift requires integer operands";
261
262pub const SHR_OPERANDS: &str = "Right shift requires integer operands";
264
265pub trait FailureText {
267 fn failure_text(&self) -> String;
268}
269
270impl FailureText for i64 {
271 fn failure_text(&self) -> String {
272 self.to_string()
273 }
274}
275
276impl FailureText for String {
277 fn failure_text(&self) -> String {
278 self.clone()
279 }
280}
281
282impl FailureText for str {
283 fn failure_text(&self) -> String {
284 self.to_string()
285 }
286}
287
288impl FailureText for bool {
289 fn failure_text(&self) -> String {
290 if *self { "true" } else { "false" }.to_string()
291 }
292}
293
294impl FailureText for char {
295 fn failure_text(&self) -> String {
296 self.to_string()
297 }
298}
299
300impl FailureText for f64 {
301 fn failure_text(&self) -> String {
302 crate::fmt::fmt_f64(*self)
303 }
304}
305
306impl FailureText for crate::types::LogosInt {
307 fn failure_text(&self) -> String {
308 self.to_string()
309 }
310}
311
312impl<T: FailureText + ?Sized> FailureText for &T {
313 fn failure_text(&self) -> String {
314 (**self).failure_text()
315 }
316}
317
318impl<A: FailureText, B: FailureText> FailureText for (A, B) {
319 fn failure_text(&self) -> String {
320 format!("({}, {})", self.0.failure_text(), self.1.failure_text())
321 }
322}
323
324impl<A: FailureText, B: FailureText, C: FailureText> FailureText for (A, B, C) {
325 fn failure_text(&self) -> String {
326 format!(
327 "({}, {}, {})",
328 self.0.failure_text(),
329 self.1.failure_text(),
330 self.2.failure_text()
331 )
332 }
333}
334
335#[cfg(test)]
336#[path = "../../logicaffeine_tests/unit/logicaffeine_data/failure/tests.rs"]
337mod tests;