Skip to main content

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}