use std::time::Duration;
use snafu::Snafu;
#[derive(Debug, Clone, PartialEq, Snafu)]
pub enum CancelGraceError {
#[snafu(display("cancel_grace is NaN"))]
Nan,
#[snafu(display("cancel_grace is infinite"))]
Infinite,
#[snafu(display("cancel_grace is {value} seconds; must be >= 0"))]
Negative {
value: f64,
},
#[snafu(display("cancel_grace value {value} seconds exceeds the representable range"))]
OutOfRange {
value: f64,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CancelGrace(Duration);
impl CancelGrace {
pub fn try_from_secs_f64(secs: f64) -> Result<Self, CancelGraceError> {
if secs.is_nan() {
return Err(CancelGraceError::Nan);
}
if secs.is_infinite() {
return Err(CancelGraceError::Infinite);
}
if secs < 0.0 {
return Err(CancelGraceError::Negative { value: secs });
}
Duration::try_from_secs_f64(secs)
.map(Self)
.map_err(|_| CancelGraceError::OutOfRange { value: secs })
}
#[must_use]
pub const fn as_duration(&self) -> Duration {
self.0
}
}
impl Default for CancelGrace {
fn default() -> Self {
Self(Duration::from_secs(5))
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct ExecutionSettings {
pub cancel_grace: CancelGrace,
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use crate::settings::execution::{CancelGrace, CancelGraceError, ExecutionSettings};
#[test]
fn default_cancel_grace_is_five_seconds() {
assert_eq!(CancelGrace::default().as_duration(), Duration::from_secs(5));
}
#[test]
fn try_from_secs_accepts_zero() {
let cg = CancelGrace::try_from_secs_f64(0.0).unwrap();
assert_eq!(cg.as_duration(), Duration::ZERO);
}
#[test]
fn try_from_secs_accepts_fractional() {
let cg = CancelGrace::try_from_secs_f64(0.5).unwrap();
assert_eq!(cg.as_duration(), Duration::from_millis(500));
}
#[test]
fn try_from_secs_accepts_integer_form() {
let cg = CancelGrace::try_from_secs_f64(10.0).unwrap();
assert_eq!(cg.as_duration(), Duration::from_secs(10));
}
#[test]
fn try_from_secs_rejects_nan() {
assert!(matches!(
CancelGrace::try_from_secs_f64(f64::NAN),
Err(CancelGraceError::Nan)
));
}
#[test]
fn try_from_secs_rejects_positive_infinity() {
assert!(matches!(
CancelGrace::try_from_secs_f64(f64::INFINITY),
Err(CancelGraceError::Infinite)
));
}
#[test]
fn try_from_secs_rejects_negative_infinity() {
assert!(matches!(
CancelGrace::try_from_secs_f64(f64::NEG_INFINITY),
Err(CancelGraceError::Infinite)
));
}
#[test]
fn try_from_secs_rejects_negative() {
match CancelGrace::try_from_secs_f64(-1.0) {
Err(CancelGraceError::Negative { value }) => {
assert!((value - -1.0).abs() < f64::EPSILON);
}
other => panic!("expected Negative, got {other:?}"),
}
}
#[test]
fn try_from_secs_rejects_overflow() {
let huge = 1.0e20_f64;
assert!(matches!(
CancelGrace::try_from_secs_f64(huge),
Err(CancelGraceError::OutOfRange { .. })
));
}
#[test]
fn execution_settings_default_uses_default_cancel_grace() {
assert_eq!(
ExecutionSettings::default().cancel_grace.as_duration(),
Duration::from_secs(5)
);
}
}