Skip to main content

u_nesting_core/
error.rs

1//! Error types for U-Nesting.
2
3use thiserror::Error;
4
5/// Result type alias for U-Nesting operations.
6pub type Result<T> = std::result::Result<T, Error>;
7
8/// `value` inside `[min, max]` (a NaN is outside every range), or an
9/// [`Error::OutOfRange`] naming the field and the range.
10pub(crate) fn check_range(name: &str, value: f64, min: f64, max: f64) -> Result<()> {
11    if value >= min && value <= max {
12        Ok(())
13    } else {
14        Err(Error::OutOfRange {
15            parameter: name.to_string(),
16            min: Some(min),
17            max: Some(max),
18            got: value,
19            range: format!("in [{min}, {max}]"),
20        })
21    }
22}
23
24/// `value` finite and at least `min`, or an [`Error::OutOfRange`] naming the
25/// field.
26pub(crate) fn check_at_least(name: &str, value: f64, min: f64) -> Result<()> {
27    if value.is_finite() && value >= min {
28        Ok(())
29    } else {
30        Err(Error::OutOfRange {
31            parameter: name.to_string(),
32            min: Some(min),
33            max: None,
34            got: value,
35            range: format!("a finite number >= {min}"),
36        })
37    }
38}
39
40/// Why the engine refused a request or could not finish it. Each variant
41/// carries what a caller needs to point at the input to change -- the setting,
42/// the geometry id, the positions -- as fields, not only in the message;
43/// [`Error::code`] is the stable name of the reason.
44#[derive(Debug, Clone, PartialEq, Error)]
45#[non_exhaustive]
46pub enum Error {
47    /// A setting outside the values it accepts: `min` / `max` are the bounds
48    /// (`None` where unbounded) and `range` says it in words.
49    #[error("{parameter} must be {range}, got {got}")]
50    OutOfRange {
51        parameter: String,
52        min: Option<f64>,
53        max: Option<f64>,
54        got: f64,
55        range: String,
56    },
57
58    /// A setting that cannot be used as given (a strategy this build or this
59    /// dimension does not offer, ...).
60    #[error("{parameter}: {message}")]
61    InvalidOption { parameter: String, message: String },
62
63    /// An option name the engine does not know: `got` is the name given and
64    /// `expected` the names it accepts.
65    #[error("unknown {parameter} '{got}'; expected one of {}", expected.join(", "))]
66    UnknownOption {
67        parameter: String,
68        got: String,
69        expected: Vec<String>,
70    },
71
72    /// A request of the wrong shape: not JSON, a missing or unknown key, a
73    /// value of the wrong type. `parameter` names the part when it is known.
74    #[error("{message}")]
75    MalformedInput {
76        parameter: Option<String>,
77        message: String,
78    },
79
80    /// The caller cancelled the run.
81    #[error("cancelled by the caller")]
82    Cancelled,
83
84    /// A geometry the engine cannot work with, named by its id when the check
85    /// that refused it knows the geometry.
86    #[error("Invalid geometry: {message}")]
87    InvalidGeometry { id: Option<String>, message: String },
88
89    /// Two geometries share an id, at positions `first` and `second` of the
90    /// request (counting from 0).
91    #[error(
92        "the id '{id}' is given twice, at positions {first} and {second} of geometries \
93         (counting from 0); placements name geometries by id, so every geometry needs \
94         its own"
95    )]
96    DuplicateId {
97        id: String,
98        first: usize,
99        second: usize,
100    },
101
102    /// A boundary the engine cannot place into; `parameter` names the field
103    /// when one field is at fault.
104    #[error("Invalid boundary: {message}")]
105    InvalidBoundary {
106        parameter: Option<String>,
107        message: String,
108    },
109
110    /// A failure inside the engine, not caused by the request.
111    #[error("Internal error: {0}")]
112    Internal(String),
113}
114
115impl Error {
116    /// The stable name of the reason: `parameter_out_of_range`,
117    /// `invalid_option`, `unknown_option`, `malformed_input`,
118    /// `invalid_geometry`, `duplicate_id`, `invalid_boundary`, `cancelled`
119    /// or `internal`.
120    pub fn code(&self) -> &'static str {
121        match self {
122            Error::OutOfRange { .. } => "parameter_out_of_range",
123            Error::InvalidOption { .. } => "invalid_option",
124            Error::UnknownOption { .. } => "unknown_option",
125            Error::MalformedInput { .. } => "malformed_input",
126            Error::Cancelled => "cancelled",
127            Error::InvalidGeometry { .. } => "invalid_geometry",
128            Error::DuplicateId { .. } => "duplicate_id",
129            Error::InvalidBoundary { .. } => "invalid_boundary",
130            Error::Internal(_) => "internal",
131        }
132    }
133
134    /// A setting that cannot be used as given.
135    pub fn invalid_option(parameter: &str, message: String) -> Self {
136        Error::InvalidOption {
137            parameter: parameter.to_string(),
138            message,
139        }
140    }
141
142    /// A geometry refused by a check that may or may not know which one.
143    pub fn invalid_geometry(id: Option<&str>, message: String) -> Self {
144        Error::InvalidGeometry {
145            id: id.map(str::to_string),
146            message,
147        }
148    }
149
150    /// A request of the wrong shape, naming the part when it is known.
151    pub fn malformed(parameter: Option<&str>, message: String) -> Self {
152        Error::MalformedInput {
153            parameter: parameter.map(str::to_string),
154            message,
155        }
156    }
157
158    /// A boundary refused, naming the field when one is at fault.
159    pub fn invalid_boundary(parameter: Option<&str>, message: String) -> Self {
160        Error::InvalidBoundary {
161            parameter: parameter.map(str::to_string),
162            message,
163        }
164    }
165}