Skip to main content

reliakit_primitives/
error.rs

1use core::fmt;
2
3/// Stable category for a primitive validation error.
4///
5/// This lets callers match on broad failure kinds without depending on display
6/// text or static validation messages.
7#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
8pub enum PrimitiveErrorKind {
9    /// The value was empty or whitespace-only.
10    Empty,
11    /// The value was shorter than the minimum allowed length.
12    TooShort,
13    /// The value was longer than the maximum allowed length.
14    TooLong,
15    /// The value was outside the inclusive allowed range.
16    OutOfRange,
17    /// The value did not match the expected format.
18    InvalidFormat,
19}
20
21/// Error returned when a primitive value fails validation.
22#[non_exhaustive]
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub enum PrimitiveError {
25    /// The value was empty or contained only whitespace.
26    Empty,
27    /// The value was shorter than the minimum allowed length.
28    TooShort {
29        /// Minimum allowed length.
30        min: usize,
31        /// Actual observed length.
32        actual: usize,
33    },
34    /// The value was longer than the maximum allowed length.
35    TooLong {
36        /// Maximum allowed length.
37        max: usize,
38        /// Actual observed length.
39        actual: usize,
40    },
41    /// The value was outside the inclusive allowed range.
42    OutOfRange {
43        /// Minimum allowed value.
44        min: u128,
45        /// Maximum allowed value.
46        max: u128,
47        /// Actual observed value.
48        actual: u128,
49    },
50    /// The value did not match the expected format or pattern.
51    Invalid {
52        /// Static validation message describing why the value is invalid.
53        message: &'static str,
54    },
55}
56
57/// Result alias used by Reliakit primitive constructors.
58pub type PrimitiveResult<T> = Result<T, PrimitiveError>;
59
60impl PrimitiveError {
61    /// Returns the stable category for this error.
62    pub const fn kind(&self) -> PrimitiveErrorKind {
63        match self {
64            Self::Empty => PrimitiveErrorKind::Empty,
65            Self::TooShort { .. } => PrimitiveErrorKind::TooShort,
66            Self::TooLong { .. } => PrimitiveErrorKind::TooLong,
67            Self::OutOfRange { .. } => PrimitiveErrorKind::OutOfRange,
68            Self::Invalid { .. } => PrimitiveErrorKind::InvalidFormat,
69        }
70    }
71}
72
73impl fmt::Display for PrimitiveError {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            Self::Empty => f.write_str("value must not be empty"),
77            Self::TooShort { min, actual } => {
78                write!(
79                    f,
80                    "value is too short: minimum is {min}, actual is {actual}"
81                )
82            }
83            Self::TooLong { max, actual } => {
84                write!(f, "value is too long: maximum is {max}, actual is {actual}")
85            }
86            Self::OutOfRange { min, max, actual } => {
87                write!(
88                    f,
89                    "value is out of range: expected {min}..={max}, actual is {actual}"
90                )
91            }
92            Self::Invalid { message } => write!(f, "invalid value: {message}"),
93        }
94    }
95}
96
97#[cfg(feature = "std")]
98impl std::error::Error for PrimitiveError {}
99
100#[cfg(test)]
101mod tests {
102    use super::{PrimitiveError, PrimitiveErrorKind};
103    use alloc::string::ToString;
104
105    #[test]
106    fn display_empty() {
107        assert_eq!(PrimitiveError::Empty.to_string(), "value must not be empty");
108    }
109
110    #[test]
111    fn display_too_short() {
112        assert_eq!(
113            PrimitiveError::TooShort { min: 3, actual: 1 }.to_string(),
114            "value is too short: minimum is 3, actual is 1"
115        );
116    }
117
118    #[test]
119    fn display_too_long() {
120        assert_eq!(
121            PrimitiveError::TooLong { max: 5, actual: 8 }.to_string(),
122            "value is too long: maximum is 5, actual is 8"
123        );
124    }
125
126    #[test]
127    fn display_out_of_range() {
128        assert_eq!(
129            PrimitiveError::OutOfRange {
130                min: 1,
131                max: 100,
132                actual: 200
133            }
134            .to_string(),
135            "value is out of range: expected 1..=100, actual is 200"
136        );
137    }
138
139    #[test]
140    fn display_invalid() {
141        assert_eq!(
142            PrimitiveError::Invalid {
143                message: "bad format"
144            }
145            .to_string(),
146            "invalid value: bad format"
147        );
148    }
149
150    #[test]
151    fn kind_returns_stable_error_category() {
152        assert_eq!(PrimitiveError::Empty.kind(), PrimitiveErrorKind::Empty);
153        assert_eq!(
154            PrimitiveError::TooShort { min: 3, actual: 1 }.kind(),
155            PrimitiveErrorKind::TooShort
156        );
157        assert_eq!(
158            PrimitiveError::TooLong { max: 5, actual: 8 }.kind(),
159            PrimitiveErrorKind::TooLong
160        );
161        assert_eq!(
162            PrimitiveError::OutOfRange {
163                min: 1,
164                max: 100,
165                actual: 200
166            }
167            .kind(),
168            PrimitiveErrorKind::OutOfRange
169        );
170        assert_eq!(
171            PrimitiveError::Invalid {
172                message: "bad format"
173            }
174            .kind(),
175            PrimitiveErrorKind::InvalidFormat
176        );
177    }
178}