1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8pub fn check_range(name: &str, value: f64, min: f64, max: f64) -> Result<()> {
15 if value >= min && value <= max {
16 Ok(())
17 } else {
18 Err(Error::OutOfRange {
19 parameter: name.to_string(),
20 id: None,
21 min: Some(min),
22 max: Some(max),
23 exclusive_min: None,
24 exclusive_max: None,
25 got: value,
26 range: format!("in [{min}, {max}]"),
27 })
28 }
29}
30
31pub fn check_at_least(name: &str, value: f64, min: f64) -> Result<()> {
38 if value.is_finite() && value >= min {
39 Ok(())
40 } else {
41 Err(Error::OutOfRange {
42 parameter: name.to_string(),
43 id: None,
44 min: Some(min),
45 max: None,
46 exclusive_min: None,
47 exclusive_max: None,
48 got: value,
49 range: format!("a finite number >= {min}"),
50 })
51 }
52}
53
54pub fn check_above(name: &str, value: f64, bound: f64) -> Result<()> {
61 if value.is_finite() && value > bound {
62 Ok(())
63 } else {
64 Err(Error::OutOfRange {
65 parameter: name.to_string(),
66 id: None,
67 min: None,
68 max: None,
69 exclusive_min: Some(bound),
70 exclusive_max: None,
71 got: value,
72 range: format!("a finite number > {bound}"),
73 })
74 }
75}
76
77pub trait WholeNumber: TryFrom<i128> + Copy {
80 const MAX: i128;
82}
83
84impl WholeNumber for u32 {
85 const MAX: i128 = u32::MAX as i128;
86}
87
88impl WholeNumber for u64 {
89 const MAX: i128 = u64::MAX as i128;
90}
91
92impl WholeNumber for usize {
93 const MAX: i128 = usize::MAX as i128;
94}
95
96pub fn whole_at_least<T: WholeNumber>(parameter: &str, value: i128, min: i128) -> Result<T> {
107 match T::try_from(value) {
108 Ok(v) if value >= min => Ok(v),
109 _ => {
110 let above = value > T::MAX;
111 Err(Error::OutOfRange {
112 parameter: parameter.to_string(),
113 id: None,
114 min: Some(min as f64),
115 max: above.then_some(T::MAX as f64),
116 exclusive_min: None,
117 exclusive_max: None,
118 got: value as f64,
119 range: if above {
120 format!("a whole number in [{min}, {}]", T::MAX)
121 } else {
122 format!("a whole number >= {min}")
123 },
124 })
125 }
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Error)]
134#[non_exhaustive]
135pub enum Error {
136 #[error("{parameter} must be {range}, got {got}")]
143 OutOfRange {
144 parameter: String,
145 id: Option<String>,
146 min: Option<f64>,
147 max: Option<f64>,
148 exclusive_min: Option<f64>,
149 exclusive_max: Option<f64>,
150 got: f64,
151 range: String,
152 },
153
154 #[error("{parameter} is empty; give at least one")]
157 EmptyInput { parameter: String },
158
159 #[error("{parameter}: {message}")]
162 InvalidOption { parameter: String, message: String },
163
164 #[error("unknown {parameter} '{got}'; expected one of {}", expected.join(", "))]
167 UnknownOption {
168 parameter: String,
169 got: String,
170 expected: Vec<String>,
171 },
172
173 #[error("{message}")]
176 MalformedInput {
177 parameter: Option<String>,
178 message: String,
179 },
180
181 #[error("{}: expected a finite number, got {got}", match index {
187 Some(i) => format!("{parameter}[{i}]"),
188 None => parameter.clone(),
189 })]
190 NotFinite {
191 parameter: String,
192 id: Option<String>,
193 index: Option<usize>,
194 got: f64,
195 },
196
197 #[error("cancelled by the caller")]
199 Cancelled,
200
201 #[error("Invalid geometry: {message}")]
204 InvalidGeometry { id: Option<String>, message: String },
205
206 #[error(
209 "the id '{id}' is given twice, at positions {first} and {second} of geometries \
210 (counting from 0); placements name geometries by id, so every geometry needs \
211 its own"
212 )]
213 DuplicateId {
214 id: String,
215 first: usize,
216 second: usize,
217 },
218
219 #[error("Invalid boundary: {message}")]
222 InvalidBoundary {
223 parameter: Option<String>,
224 message: String,
225 },
226
227 #[error("Internal error: {0}")]
229 Internal(String),
230}
231
232impl Error {
233 #[cfg(feature = "serde")]
237 pub(crate) fn examples() -> Vec<Self> {
238 let s = String::new;
239 vec![
240 Error::OutOfRange {
241 parameter: s(),
242 id: Some(s()),
243 min: Some(0.0),
244 max: Some(0.0),
245 exclusive_min: Some(0.0),
246 exclusive_max: Some(0.0),
247 got: 0.0,
248 range: s(),
249 },
250 Error::EmptyInput { parameter: s() },
251 Error::InvalidOption {
252 parameter: s(),
253 message: s(),
254 },
255 Error::UnknownOption {
256 parameter: s(),
257 got: s(),
258 expected: vec![],
259 },
260 Error::MalformedInput {
261 parameter: Some(s()),
262 message: s(),
263 },
264 Error::NotFinite {
265 parameter: s(),
266 id: Some(s()),
267 index: Some(0),
268 got: f64::NAN,
269 },
270 Error::Cancelled,
271 Error::InvalidGeometry {
272 id: Some(s()),
273 message: s(),
274 },
275 Error::DuplicateId {
276 id: s(),
277 first: 0,
278 second: 0,
279 },
280 Error::InvalidBoundary {
281 parameter: Some(s()),
282 message: s(),
283 },
284 Error::Internal(s()),
285 ]
286 }
287
288 pub fn code(&self) -> &'static str {
293 match self {
294 Error::OutOfRange { .. } => "parameter_out_of_range",
295 Error::EmptyInput { .. } => "empty_input",
296 Error::InvalidOption { .. } => "invalid_option",
297 Error::UnknownOption { .. } => "unknown_option",
298 Error::MalformedInput { .. } => "malformed_input",
299 Error::NotFinite { .. } => "value_not_finite",
300 Error::Cancelled => "cancelled",
301 Error::InvalidGeometry { .. } => "invalid_geometry",
302 Error::DuplicateId { .. } => "duplicate_id",
303 Error::InvalidBoundary { .. } => "invalid_boundary",
304 Error::Internal(_) => "internal",
305 }
306 }
307
308 pub fn parameter(&self) -> Option<&str> {
310 match self {
311 Error::OutOfRange { parameter, .. }
312 | Error::EmptyInput { parameter }
313 | Error::InvalidOption { parameter, .. }
314 | Error::UnknownOption { parameter, .. }
315 | Error::NotFinite { parameter, .. } => Some(parameter),
316 Error::MalformedInput { parameter, .. } | Error::InvalidBoundary { parameter, .. } => {
317 parameter.as_deref()
318 }
319 Error::DuplicateId { .. } => Some("geometries"),
320 _ => None,
321 }
322 }
323
324 pub fn located(mut self, section: &str, fields: &[&str]) -> Self {
332 let parameter = match &mut self {
333 Error::OutOfRange { parameter, .. }
334 | Error::EmptyInput { parameter }
335 | Error::InvalidOption { parameter, .. }
336 | Error::UnknownOption { parameter, .. }
337 | Error::NotFinite { parameter, .. } => Some(parameter),
338 Error::MalformedInput { parameter, .. } | Error::InvalidBoundary { parameter, .. } => {
339 parameter.as_mut()
340 }
341 _ => None,
342 };
343 if let Some(parameter) = parameter {
344 if fields.contains(¶meter.as_str()) {
345 *parameter = format!("{section}.{parameter}");
346 }
347 }
348 self
349 }
350
351 pub fn empty_input(parameter: &str) -> Self {
353 Error::EmptyInput {
354 parameter: parameter.to_string(),
355 }
356 }
357
358 pub fn invalid_option(parameter: &str, message: String) -> Self {
360 Error::InvalidOption {
361 parameter: parameter.to_string(),
362 message,
363 }
364 }
365
366 pub fn invalid_geometry(id: Option<&str>, message: String) -> Self {
368 Error::InvalidGeometry {
369 id: id.map(str::to_string),
370 message,
371 }
372 }
373
374 pub fn malformed(parameter: Option<&str>, message: String) -> Self {
376 Error::MalformedInput {
377 parameter: parameter.map(str::to_string),
378 message,
379 }
380 }
381
382 pub fn invalid_boundary(parameter: Option<&str>, message: String) -> Self {
384 Error::InvalidBoundary {
385 parameter: parameter.map(str::to_string),
386 message,
387 }
388 }
389}