jivanu 1.0.0

Jivanu — microbiology engine for growth kinetics, metabolism, genetics, and epidemiology
Documentation
//! Error types for the jivanu microbiology engine.

use serde::{Deserialize, Serialize};

/// Errors that can occur in jivanu operations.
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
#[non_exhaustive]
pub enum JivanuError {
    /// A concentration value was invalid.
    #[error("invalid concentration: {0}")]
    InvalidConcentration(String),

    /// A rate parameter was invalid.
    #[error("invalid rate: {0}")]
    InvalidRate(String),

    /// A simulation failed to converge or complete.
    #[error("simulation failed: {0}")]
    SimulationFailed(String),

    /// A general computation error.
    #[error("computation error: {0}")]
    ComputationError(String),
}

/// Result type alias for jivanu operations.
pub type Result<T> = core::result::Result<T, JivanuError>;

/// Validate that a value is finite and non-NaN.
#[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}"
        )))
    }
}

/// Validate that a value is positive (> 0).
#[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}"
        )))
    }
}

/// Validate that a value is non-negative (>= 0).
#[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());
    }
}