stats_claw/error.rs
1//! Typed error and `Result` alias for the framework's fallible numerics.
2//!
3//! This module defines the crate-wide `Error` enum and the `Result<T>` alias
4//! that all hand-written numerics return on bad input or domain violations
5//! (e.g. a negative scale parameter, an empty sample). It is the single place
6//! recoverable failures are modelled, so call sites never reach for `unwrap`.
7
8use std::fmt;
9
10/// A recoverable failure raised by the framework's hand-written numerics.
11///
12/// Numerics never panic on bad input — they return one of these variants so the
13/// caller can decide how to recover. The message-carrying variants embed a short
14/// human-readable reason; the unit variants are self-describing.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum Error {
17 /// A required sample or collection was empty.
18 EmptyInput,
19 /// A parameter or value violated a precondition; carries the reason.
20 InvalidInput(String),
21 /// The input is technically valid but degenerate (e.g. zero variance);
22 /// carries the reason.
23 DegenerateInput(String),
24 /// There were too few observations to compute the requested quantity.
25 InsufficientData,
26 /// An iterative procedure exhausted its budget without converging.
27 NotConverged,
28}
29
30impl fmt::Display for Error {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 Self::EmptyInput => write!(f, "input was empty"),
34 Self::InvalidInput(m) => write!(f, "invalid input: {m}"),
35 Self::DegenerateInput(m) => write!(f, "degenerate input: {m}"),
36 Self::InsufficientData => write!(f, "insufficient data"),
37 Self::NotConverged => write!(f, "did not converge"),
38 }
39 }
40}
41
42impl std::error::Error for Error {}
43
44/// Result alias for the framework's fallible numerics, defaulting the error to
45/// [`Error`].
46pub type Result<T> = std::result::Result<T, Error>;
47
48/// Kani formal-verification harness for the error type.
49///
50/// Compiled only under `cargo kani` (behind `#[cfg(kani)]`); invisible to normal
51/// build/test/clippy.
52///
53/// ## Scope note (honest disclosure)
54///
55/// [`Error`] is plain data: four data-free variants and two `String`-carrying
56/// variants, with a [`fmt::Display`] impl whose arms are single `write!` calls.
57/// The message-variant arms (`write!(f, "invalid input: {m}")`) are total by
58/// inspection — `write!` into a `String` formatter cannot fail — but their `String`
59/// payload is not a tractable symbolic input for CBMC, so they are covered by the
60/// `#[cfg(test)]` suite rather than a proof. The harness below proves the
61/// data-free path exhaustively: formatting every unit variant is panic-free and
62/// yields a non-empty message.
63#[cfg(kani)]
64mod verification {
65 use super::Error;
66
67 /// Proves that displaying each data-free [`Error`] variant neither panics nor
68 /// produces an empty message. A symbolic index selects the variant so the proof
69 /// covers all three data-free arms in one harness.
70 #[kani::proof]
71 fn error_display_unit_variants_total() {
72 let which: u8 = kani::any();
73 let err = match which % 3 {
74 0 => Error::EmptyInput,
75 1 => Error::InsufficientData,
76 _ => Error::NotConverged,
77 };
78 let text = err.to_string();
79 assert!(!text.is_empty(), "error display produced an empty message");
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn display_is_human_readable() {
89 assert_eq!(
90 Error::EmptyInput.to_string(),
91 "input was empty",
92 "EmptyInput display text changed"
93 );
94 }
95
96 #[test]
97 fn display_includes_detail_for_message_variants() {
98 assert_eq!(
99 Error::InvalidInput("scale must be > 0".to_owned()).to_string(),
100 "invalid input: scale must be > 0"
101 );
102 assert_eq!(
103 Error::DegenerateInput("zero variance".to_owned()).to_string(),
104 "degenerate input: zero variance"
105 );
106 }
107
108 #[test]
109 fn usable_as_std_error() {
110 fn boxed() -> Box<dyn std::error::Error> {
111 Box::new(Error::NotConverged)
112 }
113 assert_eq!(boxed().to_string(), "did not converge");
114 }
115}