comnum_core/error.rs
1//! The error type of comnum.
2//!
3//! Every fallible function returns [`Result<T>`], whose error says what kind of mistake
4//! was made: an invalid configuration (a parameter out of its domain), inconsistent
5//! sizes, a numerical failure (a non-finite value, a singular system), a missing value
6//! in a simulation condition, or an input/output error.
7//!
8//! The crates of layers 0 to 4 share this one enum, re-exported as `comnum::Error`, so
9//! that `?` composes across blocks of different crates inside a simulation trial (see
10//! `docs/adr/0003-shared-error-type.md`).
11//!
12//! # Examples
13//! ```
14//! use comnum_core::{ensure, Error};
15//! let err = ensure(false, || "order must be a power of two, got 6".into()).unwrap_err();
16//! assert!(matches!(err, Error::Config(_)));
17//! assert_eq!(err.context("qam").to_string(), "invalid configuration: qam: order must be a power of two, got 6");
18//! ```
19
20use std::fmt;
21
22/// The error of every fallible operation of comnum.
23#[derive(Debug, thiserror::Error)]
24#[non_exhaustive]
25pub enum Error {
26 /// A parameter is outside its domain (the message names it and its value).
27 #[error("invalid configuration: {0}")]
28 Config(String),
29 /// Two inputs have inconsistent lengths or shapes.
30 #[error("inconsistent shapes: {0}")]
31 Shape(String),
32 /// A numerical routine failed (non-finite value, singular system, no convergence).
33 #[error("numerical failure: {0}")]
34 Numerical(String),
35 /// A simulation condition does not hold the requested key, or holds another type.
36 #[error("condition: {0}")]
37 Condition(String),
38 /// An input/output error (a logger, a recording).
39 #[error("input/output: {0}")]
40 Io(#[from] std::io::Error),
41}
42
43/// `Result` with the error type of comnum.
44pub type Result<T> = std::result::Result<T, Error>;
45
46impl Error {
47 /// The same error with `context` prepended to its message (the kind is kept, so
48 /// that a configuration error found while building a condition stays one).
49 #[must_use]
50 pub fn context(self, context: impl fmt::Display) -> Error {
51 match self {
52 Error::Config(m) => Error::Config(format!("{context}: {m}")),
53 Error::Shape(m) => Error::Shape(format!("{context}: {m}")),
54 Error::Numerical(m) => Error::Numerical(format!("{context}: {m}")),
55 Error::Condition(m) => Error::Condition(format!("{context}: {m}")),
56 Error::Io(e) => Error::Io(std::io::Error::new(e.kind(), format!("{context}: {e}"))),
57 }
58 }
59}
60
61/// Return [`Error::Config`] with the message of `msg` unless `ok`.
62///
63/// # Errors
64/// [`Error::Config`] when `ok` is false.
65pub fn ensure(ok: bool, msg: impl FnOnce() -> String) -> Result<()> {
66 if ok { Ok(()) } else { Err(Error::Config(msg())) }
67}
68
69/// Return [`Error::Shape`] with the message of `msg` unless `ok` (inconsistent lengths
70/// or shapes; [`ensure`] for a parameter outside its domain).
71///
72/// # Errors
73/// [`Error::Shape`] when `ok` is false.
74///
75/// # Examples
76/// ```
77/// use comnum_core::{ensure_shape, Error};
78/// let err = ensure_shape(false, || "3 rows for a code of 2 outputs".into()).unwrap_err();
79/// assert!(matches!(err, Error::Shape(_)));
80/// assert_eq!(err.to_string(), "inconsistent shapes: 3 rows for a code of 2 outputs");
81/// ```
82pub fn ensure_shape(ok: bool, msg: impl FnOnce() -> String) -> Result<()> {
83 if ok { Ok(()) } else { Err(Error::Shape(msg())) }
84}
85
86/// Return [`Error::Shape`] unless a block of `got` elements has the `want` expected
87/// (`what` names the elements).
88///
89/// # Errors
90/// [`Error::Shape`] when the lengths differ.
91pub fn expect_len(got: usize, want: usize, what: &str) -> Result<()> {
92 if got == want { Ok(()) } else { Err(Error::Shape(format!("{got} {what} for a block of {want}"))) }
93}
94
95/// Return [`Error::Shape`] unless `a == b` (`what` names the two inputs).
96///
97/// # Errors
98/// [`Error::Shape`] when the lengths differ.
99pub fn same_len(a: usize, b: usize, what: &str) -> Result<()> {
100 if a == b { Ok(()) } else { Err(Error::Shape(format!("{what}: lengths {a} and {b} differ"))) }
101}