use crate::inline::InlineStr;
use core::fmt;
pub type Result<T> = core::result::Result<T, KernelError>;
pub const EXCERPT_BYTES: usize = 32;
pub type Excerpt = InlineStr<EXCERPT_BYTES>;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum KernelError {
NotFinite {
parameter: &'static str,
value: f64,
},
OutOfRange {
parameter: &'static str,
value: f64,
min: f64,
max: f64,
},
InsufficientData {
found: usize,
required: usize,
context: &'static str,
},
UnknownCardinalDirection {
direction: Excerpt,
},
BufferTooSmall {
needed: usize,
found: usize,
},
CapacityExceeded {
context: &'static str,
needed: usize,
capacity: usize,
},
SingularSystem {
context: &'static str,
},
NotCovariance {
context: &'static str,
},
NotConverged {
iterations: u32,
residual: f64,
},
Parse {
what: &'static str,
input: Excerpt,
},
Indeterminate {
quantity: &'static str,
},
Missing {
what: &'static str,
},
Unrepresentable {
what: &'static str,
},
TimeReversed {
by: core::time::Duration,
},
VerticalDatumMismatch {
required: crate::geodesy::VerticalDatum,
found: crate::geodesy::VerticalDatum,
},
OutsideValidity {
data: &'static str,
},
}
pub fn ensure_finite(parameter: &'static str, value: f64) -> Result<()> {
if value.is_finite() {
Ok(())
} else {
Err(KernelError::NotFinite { parameter, value })
}
}
pub fn ensure_range(parameter: &'static str, value: f64, min: f64, max: f64) -> Result<()> {
ensure_finite(parameter, value)?;
if value < min || value > max {
return Err(KernelError::OutOfRange {
parameter,
value,
min,
max,
});
}
Ok(())
}
impl fmt::Display for KernelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotFinite { parameter, value } => {
write!(f, "{parameter} must be a finite number, got {value}")
}
Self::OutOfRange {
parameter,
value,
min,
max,
} => write!(
f,
"{parameter} out of range: {value}. Must be between {min} and {max}"
),
Self::InsufficientData {
found,
required,
context,
} => write!(f, "{context} needs at least {required}, and has {found}"),
Self::UnknownCardinalDirection { direction } => write!(
f,
"unknown cardinal direction: {direction}. Expected one of N, NE, E, SE, S, SW, W, NW"
),
Self::CapacityExceeded {
context,
needed,
capacity,
} => write!(
f,
"{context} needs room for {needed}, and the limit is {capacity}"
),
Self::BufferTooSmall { needed, found } => write!(
f,
"output buffer holds {found} values, {needed} are needed"
),
Self::SingularSystem { context } => {
write!(f, "singular system while solving {context}")
}
Self::NotCovariance { context } => {
write!(f, "{context} is not a covariance matrix")
}
Self::NotConverged {
iterations,
residual,
} => write!(
f,
"solver did not converge after {iterations} iterations, residual {residual}"
),
Self::Parse { what, input } => {
write!(f, "could not read {input:?} as a {what}")
}
Self::Indeterminate { quantity } => {
write!(f, "{quantity} is indeterminate for these inputs")
}
Self::Missing { what } => write!(f, "{what} is not available"),
Self::Unrepresentable { what } => write!(f, "{what} cannot be represented"),
Self::TimeReversed { by } => {
write!(f, "time ran backwards by {} s", by.as_secs_f64())
}
Self::VerticalDatumMismatch { required, found } => write!(
f,
"a height above {found:?} was given where one above {required:?} is required"
),
Self::OutsideValidity { data } => {
write!(f, "the {data} is not valid for the requested moment")
}
}
}
}
impl core::error::Error for KernelError {}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn every_variant_has_a_message() {
let errors = [
KernelError::NotFinite {
parameter: "course",
value: f64::NAN,
},
KernelError::OutOfRange {
parameter: "course",
value: 400.0,
min: 0.0,
max: 360.0,
},
KernelError::InsufficientData {
found: 1,
required: 2,
context: "interpolation",
},
KernelError::UnknownCardinalDirection {
direction: Excerpt::new("XYZ"),
},
KernelError::BufferTooSmall {
needed: 36,
found: 8,
},
KernelError::CapacityExceeded {
context: "a deviation table",
needed: 90,
capacity: 72,
},
KernelError::SingularSystem {
context: "parametric fit",
},
KernelError::NotCovariance {
context: "the observation noise",
},
KernelError::TimeReversed {
by: core::time::Duration::from_secs(18),
},
KernelError::OutsideValidity {
data: "leap second table",
},
KernelError::VerticalDatumMismatch {
required: crate::geodesy::VerticalDatum::Ellipsoid,
found: crate::geodesy::VerticalDatum::MeanSeaLevel,
},
KernelError::NotConverged {
iterations: 64,
residual: 1.0,
},
KernelError::Parse {
what: "latitude",
input: Excerpt::new("north-ish"),
},
KernelError::Indeterminate {
quantity: "a rhumb line through a pole",
},
KernelError::Missing {
what: "the vessel's position",
},
KernelError::Unrepresentable {
what: "a moment beyond the end of time",
},
];
for error in errors {
assert!(!error.to_string().is_empty(), "{error:?}");
}
}
#[test]
fn errors_compare_by_value() {
let a = KernelError::OutOfRange {
parameter: "x",
value: 1.0,
min: 0.0,
max: 0.5,
};
assert_eq!(a, a.clone());
assert_ne!(
a,
KernelError::Missing {
what: "the vessel's position"
}
);
}
#[test]
fn the_checks_report_what_they_reject() {
assert!(ensure_finite("x", 1.0).is_ok());
assert!(matches!(
ensure_finite("x", f64::NAN),
Err(KernelError::NotFinite { parameter: "x", .. })
));
assert!(matches!(
ensure_range("x", 2.0, 0.0, 1.0),
Err(KernelError::OutOfRange { parameter: "x", .. })
));
assert!(matches!(
ensure_range("x", f64::INFINITY, 0.0, 1.0),
Err(KernelError::NotFinite { .. })
));
}
}