use alloc::string::String;
use core::fmt;
pub type Result<T> = core::result::Result<T, NavigationError>;
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum NavigationError {
NotFinite {
parameter: &'static str,
value: f64,
},
OutOfRange {
parameter: &'static str,
value: f64,
min: f64,
max: f64,
},
InvalidStep {
step: i32,
},
InsufficientNodes {
found: usize,
required: usize,
context: &'static str,
},
DuplicateCourse {
course: i32,
},
CourseNotInTable {
course: i32,
},
UnknownCardinalDirection {
direction: String,
},
UnexpectedTableLength {
found: usize,
expected: usize,
},
SingularSystem {
context: &'static str,
},
NotConverged {
iterations: u32,
residual: f64,
},
Parallel {
context: &'static str,
},
NoSolution {
context: &'static str,
},
Parse {
what: &'static str,
input: String,
},
Indeterminate {
quantity: &'static str,
},
CurrentTooStrong {
drift: f64,
speed_through_water: f64,
},
}
impl fmt::Display for NavigationError {
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} degrees"
),
Self::InvalidStep { step } => write!(
f,
"invalid deviation table step: {step}. Must be between 1 and 180 degrees"
),
Self::InsufficientNodes {
found,
required,
context,
} => write!(
f,
"{context} needs at least {required} deviation nodes, table has {found}"
),
Self::DuplicateCourse { course } => {
write!(f, "duplicate compass course in deviation table: {course}")
}
Self::CourseNotInTable { course } => write!(
f,
"compass course {course} is not a node of this deviation table"
),
Self::UnknownCardinalDirection { direction } => write!(
f,
"unknown cardinal direction: {direction}. Expected one of N, NE, E, SE, S, SW, W, NW"
),
Self::UnexpectedTableLength { found, expected } => write!(
f,
"expected {expected} deviation values, got {found}"
),
Self::SingularSystem { context } => {
write!(f, "singular system while solving {context}")
}
Self::NotConverged {
iterations,
residual,
} => write!(
f,
"solver did not converge after {iterations} iterations, residual {residual} degrees"
),
Self::Parse { what, input } => {
write!(f, "could not read {input:?} as a {what}")
}
Self::Parallel { context } => write!(f, "{context} never meet"),
Self::NoSolution { context } => {
write!(f, "no solution exists for {context}")
}
Self::Indeterminate { quantity } => {
write!(f, "{quantity} is indeterminate for these inputs")
}
Self::CurrentTooStrong {
drift,
speed_through_water,
} => write!(
f,
"current of {drift} is too strong for a speed through water of {speed_through_water}"
),
}
}
}
impl core::error::Error for NavigationError {}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn every_variant_has_a_message() {
let errors = [
NavigationError::NotFinite {
parameter: "course",
value: f64::NAN,
},
NavigationError::OutOfRange {
parameter: "course",
value: 400.0,
min: 0.0,
max: 360.0,
},
NavigationError::InvalidStep { step: 0 },
NavigationError::InsufficientNodes {
found: 1,
required: 2,
context: "interpolation",
},
NavigationError::DuplicateCourse { course: 10 },
NavigationError::CourseNotInTable { course: 50 },
NavigationError::UnknownCardinalDirection {
direction: "XYZ".to_string(),
},
NavigationError::UnexpectedTableLength {
found: 5,
expected: 36,
},
NavigationError::SingularSystem {
context: "parametric fit",
},
NavigationError::NotConverged {
iterations: 64,
residual: 1.0,
},
NavigationError::Parse {
what: "latitude",
input: "north-ish".to_string(),
},
NavigationError::Parallel {
context: "the two great circles",
},
NavigationError::NoSolution {
context: "a course achieving that closest approach",
},
NavigationError::Indeterminate {
quantity: "course over ground",
},
NavigationError::CurrentTooStrong {
drift: 10.0,
speed_through_water: 2.0,
},
];
for error in &errors {
assert!(!error.to_string().is_empty());
}
}
#[test]
fn errors_compare_by_value() {
let a = NavigationError::InvalidStep { step: 0 };
let b = NavigationError::InvalidStep { step: 0 };
let c = NavigationError::InvalidStep { step: -1 };
assert_eq!(a, b);
assert_ne!(a, c);
}
}