use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
#[non_exhaustive]
pub enum JivanuError {
#[error("invalid concentration: {0}")]
InvalidConcentration(String),
#[error("invalid rate: {0}")]
InvalidRate(String),
#[error("simulation failed: {0}")]
SimulationFailed(String),
#[error("computation error: {0}")]
ComputationError(String),
}
pub type Result<T> = core::result::Result<T, JivanuError>;
#[inline]
pub(crate) fn validate_finite(value: f64, name: &str) -> Result<()> {
if value.is_finite() {
Ok(())
} else {
Err(JivanuError::ComputationError(format!(
"{name} must be finite, got {value}"
)))
}
}
#[inline]
pub(crate) fn validate_positive(value: f64, name: &str) -> Result<()> {
validate_finite(value, name)?;
if value > 0.0 {
Ok(())
} else {
Err(JivanuError::ComputationError(format!(
"{name} must be positive, got {value}"
)))
}
}
#[inline]
pub(crate) fn validate_non_negative(value: f64, name: &str) -> Result<()> {
validate_finite(value, name)?;
if value >= 0.0 {
Ok(())
} else {
Err(JivanuError::ComputationError(format!(
"{name} must be non-negative, got {value}"
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = JivanuError::InvalidConcentration("negative".into());
assert_eq!(err.to_string(), "invalid concentration: negative");
}
#[test]
fn test_error_serde_roundtrip() {
let err = JivanuError::SimulationFailed("diverged".into());
let json = serde_json::to_string(&err).unwrap();
let back: JivanuError = serde_json::from_str(&json).unwrap();
assert_eq!(err.to_string(), back.to_string());
}
#[test]
fn test_validate_finite() {
assert!(validate_finite(1.0, "x").is_ok());
assert!(validate_finite(f64::NAN, "x").is_err());
}
#[test]
fn test_validate_positive() {
assert!(validate_positive(1.0, "x").is_ok());
assert!(validate_positive(0.0, "x").is_err());
}
}