use std::borrow::Cow;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum FalakError {
#[error("invalid parameter: {0}")]
InvalidParameter(Cow<'static, str>),
#[error("math error: {0}")]
MathError(Cow<'static, str>),
#[error("convergence error: {message} (after {iterations} iterations)")]
ConvergenceError {
message: Cow<'static, str>,
iterations: u32,
},
#[error("ephemeris error: {0}")]
EphemerisError(Cow<'static, str>),
#[error("non-finite input in {context}: {value}")]
NonFinite {
context: &'static str,
value: f64,
},
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
}
pub type Result<T> = std::result::Result<T, FalakError>;
#[inline]
pub fn require_finite(value: f64, context: &'static str) -> Result<()> {
if value.is_finite() {
Ok(())
} else {
Err(FalakError::NonFinite { context, value })
}
}
#[inline]
pub fn require_all_finite(values: &[f64], context: &'static str) -> Result<()> {
for &v in values {
require_finite(v, context)?;
}
Ok(())
}
#[inline]
pub fn ensure_finite(value: f64, context: &'static str) -> Result<f64> {
if value.is_finite() {
Ok(value)
} else {
Err(FalakError::MathError(
format!("{context}: result is {value}").into(),
))
}
}
#[inline]
pub fn require_finite_vec3(v: [f64; 3], context: &'static str) -> Result<()> {
for &c in &v {
require_finite(c, context)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn require_finite_accepts_normal() {
assert!(require_finite(42.0, "test").is_ok());
assert!(require_finite(0.0, "test").is_ok());
assert!(require_finite(-1e300, "test").is_ok());
}
#[test]
fn require_finite_rejects_nan() {
let err = require_finite(f64::NAN, "position").unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("position"));
assert!(msg.contains("NaN"));
}
#[test]
fn require_finite_rejects_infinity() {
assert!(require_finite(f64::INFINITY, "velocity").is_err());
assert!(require_finite(f64::NEG_INFINITY, "velocity").is_err());
}
#[test]
fn require_all_finite_rejects_first_bad() {
assert!(require_all_finite(&[1.0, 2.0, 3.0], "test").is_ok());
assert!(require_all_finite(&[1.0, f64::NAN, 3.0], "test").is_err());
}
#[test]
fn ensure_finite_passes_through() {
assert_eq!(ensure_finite(42.0, "test").unwrap(), 42.0);
}
#[test]
fn ensure_finite_catches_nan() {
assert!(ensure_finite(f64::NAN, "result").is_err());
}
#[test]
fn ensure_finite_catches_infinity() {
assert!(ensure_finite(f64::INFINITY, "result").is_err());
}
#[test]
fn require_finite_vec3_ok() {
assert!(require_finite_vec3([1.0, 2.0, 3.0], "pos").is_ok());
}
#[test]
fn require_finite_vec3_catches_nan() {
assert!(require_finite_vec3([1.0, f64::NAN, 3.0], "pos").is_err());
}
}