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