use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
pub fn check_range(name: &str, value: f64, min: f64, max: f64) -> Result<()> {
if value >= min && value <= max {
Ok(())
} else {
Err(Error::OutOfRange {
parameter: name.to_string(),
id: None,
min: Some(min),
max: Some(max),
exclusive_min: None,
exclusive_max: None,
got: value,
range: format!("in [{min}, {max}]"),
})
}
}
pub fn check_at_least(name: &str, value: f64, min: f64) -> Result<()> {
if value.is_finite() && value >= min {
Ok(())
} else {
Err(Error::OutOfRange {
parameter: name.to_string(),
id: None,
min: Some(min),
max: None,
exclusive_min: None,
exclusive_max: None,
got: value,
range: format!("a finite number >= {min}"),
})
}
}
pub fn check_above(name: &str, value: f64, bound: f64) -> Result<()> {
if value.is_finite() && value > bound {
Ok(())
} else {
Err(Error::OutOfRange {
parameter: name.to_string(),
id: None,
min: None,
max: None,
exclusive_min: Some(bound),
exclusive_max: None,
got: value,
range: format!("a finite number > {bound}"),
})
}
}
pub trait WholeNumber: TryFrom<i128> + Copy {
const MAX: i128;
}
impl WholeNumber for u32 {
const MAX: i128 = u32::MAX as i128;
}
impl WholeNumber for u64 {
const MAX: i128 = u64::MAX as i128;
}
impl WholeNumber for usize {
const MAX: i128 = usize::MAX as i128;
}
pub fn whole_at_least<T: WholeNumber>(parameter: &str, value: i128, min: i128) -> Result<T> {
match T::try_from(value) {
Ok(v) if value >= min => Ok(v),
_ => {
let above = value > T::MAX;
Err(Error::OutOfRange {
parameter: parameter.to_string(),
id: None,
min: Some(min as f64),
max: above.then_some(T::MAX as f64),
exclusive_min: None,
exclusive_max: None,
got: value as f64,
range: if above {
format!("a whole number in [{min}, {}]", T::MAX)
} else {
format!("a whole number >= {min}")
},
})
}
}
}
#[derive(Debug, Clone, PartialEq, Error)]
#[non_exhaustive]
pub enum Error {
#[error("{parameter} must be {range}, got {got}")]
OutOfRange {
parameter: String,
id: Option<String>,
min: Option<f64>,
max: Option<f64>,
exclusive_min: Option<f64>,
exclusive_max: Option<f64>,
got: f64,
range: String,
},
#[error("{parameter} is empty; give at least one")]
EmptyInput { parameter: String },
#[error("{parameter}: {message}")]
InvalidOption { parameter: String, message: String },
#[error("unknown {parameter} '{got}'; expected one of {}", expected.join(", "))]
UnknownOption {
parameter: String,
got: String,
expected: Vec<String>,
},
#[error("{message}")]
MalformedInput {
parameter: Option<String>,
message: String,
},
#[error("{}: expected a finite number, got {got}", match index {
Some(i) => format!("{parameter}[{i}]"),
None => parameter.clone(),
})]
NotFinite {
parameter: String,
id: Option<String>,
index: Option<usize>,
got: f64,
},
#[error("cancelled by the caller")]
Cancelled,
#[error("Invalid geometry: {message}")]
InvalidGeometry { id: Option<String>, message: String },
#[error(
"the id '{id}' is given twice, at positions {first} and {second} of geometries \
(counting from 0); placements name geometries by id, so every geometry needs \
its own"
)]
DuplicateId {
id: String,
first: usize,
second: usize,
},
#[error("Invalid boundary: {message}")]
InvalidBoundary {
parameter: Option<String>,
message: String,
},
#[error("Internal error: {0}")]
Internal(String),
}
impl Error {
#[cfg(feature = "serde")]
pub(crate) fn examples() -> Vec<Self> {
let s = String::new;
vec![
Error::OutOfRange {
parameter: s(),
id: Some(s()),
min: Some(0.0),
max: Some(0.0),
exclusive_min: Some(0.0),
exclusive_max: Some(0.0),
got: 0.0,
range: s(),
},
Error::EmptyInput { parameter: s() },
Error::InvalidOption {
parameter: s(),
message: s(),
},
Error::UnknownOption {
parameter: s(),
got: s(),
expected: vec![],
},
Error::MalformedInput {
parameter: Some(s()),
message: s(),
},
Error::NotFinite {
parameter: s(),
id: Some(s()),
index: Some(0),
got: f64::NAN,
},
Error::Cancelled,
Error::InvalidGeometry {
id: Some(s()),
message: s(),
},
Error::DuplicateId {
id: s(),
first: 0,
second: 0,
},
Error::InvalidBoundary {
parameter: Some(s()),
message: s(),
},
Error::Internal(s()),
]
}
pub fn code(&self) -> &'static str {
match self {
Error::OutOfRange { .. } => "parameter_out_of_range",
Error::EmptyInput { .. } => "empty_input",
Error::InvalidOption { .. } => "invalid_option",
Error::UnknownOption { .. } => "unknown_option",
Error::MalformedInput { .. } => "malformed_input",
Error::NotFinite { .. } => "value_not_finite",
Error::Cancelled => "cancelled",
Error::InvalidGeometry { .. } => "invalid_geometry",
Error::DuplicateId { .. } => "duplicate_id",
Error::InvalidBoundary { .. } => "invalid_boundary",
Error::Internal(_) => "internal",
}
}
pub fn parameter(&self) -> Option<&str> {
match self {
Error::OutOfRange { parameter, .. }
| Error::EmptyInput { parameter }
| Error::InvalidOption { parameter, .. }
| Error::UnknownOption { parameter, .. }
| Error::NotFinite { parameter, .. } => Some(parameter),
Error::MalformedInput { parameter, .. } | Error::InvalidBoundary { parameter, .. } => {
parameter.as_deref()
}
Error::DuplicateId { .. } => Some("geometries"),
_ => None,
}
}
pub fn located(mut self, section: &str, fields: &[&str]) -> Self {
let parameter = match &mut self {
Error::OutOfRange { parameter, .. }
| Error::EmptyInput { parameter }
| Error::InvalidOption { parameter, .. }
| Error::UnknownOption { parameter, .. }
| Error::NotFinite { parameter, .. } => Some(parameter),
Error::MalformedInput { parameter, .. } | Error::InvalidBoundary { parameter, .. } => {
parameter.as_mut()
}
_ => None,
};
if let Some(parameter) = parameter {
if fields.contains(¶meter.as_str()) {
*parameter = format!("{section}.{parameter}");
}
}
self
}
pub fn empty_input(parameter: &str) -> Self {
Error::EmptyInput {
parameter: parameter.to_string(),
}
}
pub fn invalid_option(parameter: &str, message: String) -> Self {
Error::InvalidOption {
parameter: parameter.to_string(),
message,
}
}
pub fn invalid_geometry(id: Option<&str>, message: String) -> Self {
Error::InvalidGeometry {
id: id.map(str::to_string),
message,
}
}
pub fn malformed(parameter: Option<&str>, message: String) -> Self {
Error::MalformedInput {
parameter: parameter.map(str::to_string),
message,
}
}
pub fn invalid_boundary(parameter: Option<&str>, message: String) -> Self {
Error::InvalidBoundary {
parameter: parameter.map(str::to_string),
message,
}
}
}