Skip to main content

positive/
error.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 25/12/25
5******************************************************************************/
6
7//! Error types for the Positive decimal type.
8//!
9//! This module provides error handling for operations involving positive decimal values,
10//! including validation, arithmetic operations, conversions, and precision issues.
11
12use thiserror::Error;
13
14/// Represents errors that can occur during positive decimal operations.
15///
16/// This enum provides a structured way to handle various error conditions that may arise
17/// when working with positive decimal values, including validation, arithmetic operations,
18/// conversions, and precision issues.
19///
20/// # Variants
21///
22/// * `InvalidValue` - Value cannot be represented as a valid positive decimal
23/// * `ArithmeticError` - Error during mathematical operations
24/// * `ConversionError` - Error when converting between types
25/// * `OutOfBounds` - Value exceeds defined limits
26/// * `InvalidPrecision` - Invalid decimal precision settings
27/// * `Other` - Catch-all for other errors
28#[derive(Error, Debug)]
29pub enum PositiveError {
30    /// Error when attempting to create a positive decimal from an invalid value.
31    ///
32    /// Occurs when a value cannot be properly represented as a positive decimal,
33    /// such as when it's NaN, infinity, negative, or otherwise unsuitable.
34    #[error("Invalid positive value {value}: {reason}")]
35    InvalidValue {
36        /// The problematic value that caused the error.
37        value: f64,
38        /// Detailed explanation of why the value is invalid.
39        reason: String,
40    },
41
42    /// Error when performing decimal arithmetic operations.
43    ///
44    /// Occurs during mathematical operations such as addition, subtraction,
45    /// multiplication, or division when the operation cannot be completed
46    /// correctly (e.g., division by zero, overflow, result would be negative).
47    #[error("Arithmetic error during {operation}: {reason}")]
48    ArithmeticError {
49        /// The operation that failed (e.g., "subtraction", "division").
50        operation: String,
51        /// Detailed explanation of why the operation failed.
52        reason: String,
53    },
54
55    /// Error when converting between decimal types.
56    ///
57    /// Occurs when a decimal value cannot be correctly converted from one
58    /// representation to another, such as between different precision levels
59    /// or between different decimal formats.
60    #[error("Failed to convert from {from_type} to {to_type}: {reason}")]
61    ConversionError {
62        /// The source type being converted from.
63        from_type: String,
64        /// The destination type being converted to.
65        to_type: String,
66        /// Detailed explanation of why the conversion failed.
67        reason: String,
68    },
69
70    /// Error when a decimal value exceeds its bounds.
71    ///
72    /// Occurs when a decimal value falls outside of acceptable minimum
73    /// or maximum values for a specific calculation context.
74    #[error("Value {value} is out of bounds (min: {min}, max: {max})")]
75    OutOfBounds {
76        /// The value that is out of bounds.
77        value: f64,
78        /// The minimum acceptable value.
79        min: f64,
80        /// The maximum acceptable value.
81        max: f64,
82    },
83
84    /// Error when decimal precision is invalid.
85    ///
86    /// Occurs when an operation specifies or results in an invalid precision
87    /// level that cannot be properly handled.
88    #[error("Invalid precision {precision}: {reason}")]
89    InvalidPrecision {
90        /// The problematic precision value.
91        precision: i32,
92        /// Detailed explanation of why the precision is invalid.
93        reason: String,
94    },
95
96    /// Catch-all error for other positive decimal errors.
97    #[error("Positive error: {0}")]
98    Other(String),
99}
100
101/// A specialized `Result` type for positive decimal operations.
102///
103/// This type alias provides a convenient shorthand for operations that can result in a
104/// `PositiveError`. It helps improve code readability and reduces boilerplate.
105///
106/// # Type Parameters
107///
108/// * `T` - The successful result type of the operation
109pub type PositiveResult<T> = Result<T, PositiveError>;
110
111impl PositiveError {
112    /// Creates a new `InvalidValue` error.
113    ///
114    /// # Arguments
115    ///
116    /// * `value` - The problematic floating-point value
117    /// * `reason` - Explanation of why the value is invalid
118    ///
119    /// # Returns
120    ///
121    /// A new `PositiveError::InvalidValue` instance
122    #[cold]
123    #[inline(never)]
124    #[must_use]
125    pub fn invalid_value(value: f64, reason: &str) -> Self {
126        PositiveError::InvalidValue {
127            value,
128            reason: reason.to_string(),
129        }
130    }
131
132    /// Creates a new `ArithmeticError` error.
133    ///
134    /// # Arguments
135    ///
136    /// * `operation` - The name of the operation that failed
137    /// * `reason` - Explanation of why the operation failed
138    ///
139    /// # Returns
140    ///
141    /// A new `PositiveError::ArithmeticError` instance
142    #[cold]
143    #[inline(never)]
144    #[must_use]
145    pub fn arithmetic_error(operation: &str, reason: &str) -> Self {
146        PositiveError::ArithmeticError {
147            operation: operation.to_string(),
148            reason: reason.to_string(),
149        }
150    }
151
152    /// Creates a new `ConversionError` error.
153    ///
154    /// # Arguments
155    ///
156    /// * `from_type` - The source type being converted from
157    /// * `to_type` - The destination type being converted to
158    /// * `reason` - Explanation of why the conversion failed
159    ///
160    /// # Returns
161    ///
162    /// A new `PositiveError::ConversionError` instance
163    #[cold]
164    #[inline(never)]
165    #[must_use]
166    pub fn conversion_error(from_type: &str, to_type: &str, reason: &str) -> Self {
167        PositiveError::ConversionError {
168            from_type: from_type.to_string(),
169            to_type: to_type.to_string(),
170            reason: reason.to_string(),
171        }
172    }
173
174    /// Creates a new `OutOfBounds` error.
175    ///
176    /// # Arguments
177    ///
178    /// * `value` - The out-of-bounds floating-point value
179    /// * `min` - The lower bound (inclusive) of the valid range
180    /// * `max` - The upper bound (inclusive) of the valid range
181    ///
182    /// # Returns
183    ///
184    /// A new `PositiveError::OutOfBounds` instance
185    #[cold]
186    #[inline(never)]
187    #[must_use]
188    pub fn out_of_bounds(value: f64, min: f64, max: f64) -> Self {
189        PositiveError::OutOfBounds { value, min, max }
190    }
191
192    /// Creates a new `InvalidPrecision` error.
193    ///
194    /// # Arguments
195    ///
196    /// * `precision` - The problematic precision value
197    /// * `reason` - Explanation of why the precision is invalid
198    ///
199    /// # Returns
200    ///
201    /// A new `PositiveError::InvalidPrecision` instance
202    #[cold]
203    #[inline(never)]
204    #[must_use]
205    pub fn invalid_precision(precision: i32, reason: &str) -> Self {
206        PositiveError::InvalidPrecision {
207            precision,
208            reason: reason.to_string(),
209        }
210    }
211}
212
213impl From<&str> for PositiveError {
214    #[cold]
215    #[inline(never)]
216    fn from(s: &str) -> Self {
217        PositiveError::Other(s.to_string())
218    }
219}
220
221impl From<String> for PositiveError {
222    #[cold]
223    #[inline(never)]
224    fn from(s: String) -> Self {
225        PositiveError::Other(s)
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn test_invalid_value_error() {
235        let error = PositiveError::invalid_value(-1.0, "Value cannot be negative");
236        assert!(matches!(error, PositiveError::InvalidValue { .. }));
237        assert!(error.to_string().contains("cannot be negative"));
238    }
239
240    #[test]
241    fn test_arithmetic_error() {
242        let error = PositiveError::arithmetic_error("subtraction", "Result would be negative");
243        assert!(matches!(error, PositiveError::ArithmeticError { .. }));
244        assert!(error.to_string().contains("would be negative"));
245    }
246
247    #[test]
248    fn test_conversion_error() {
249        let error = PositiveError::conversion_error("f64", "Positive", "Value out of range");
250        assert!(matches!(error, PositiveError::ConversionError { .. }));
251        assert!(error.to_string().contains("out of range"));
252    }
253
254    #[test]
255    fn test_out_of_bounds_error() {
256        let error = PositiveError::out_of_bounds(-5.0, 0.0, 100.0);
257        assert!(matches!(error, PositiveError::OutOfBounds { .. }));
258        assert!(error.to_string().contains("-5"));
259    }
260
261    #[test]
262    fn test_invalid_precision_error() {
263        let error = PositiveError::invalid_precision(-1, "Precision must be non-negative");
264        assert!(matches!(error, PositiveError::InvalidPrecision { .. }));
265        assert!(error.to_string().contains("non-negative"));
266    }
267
268    #[test]
269    fn test_from_str() {
270        let error: PositiveError = "Custom error message".into();
271        assert!(matches!(error, PositiveError::Other(_)));
272        assert!(error.to_string().contains("Custom error message"));
273    }
274
275    #[test]
276    fn test_from_string() {
277        let error: PositiveError = String::from("Another error").into();
278        assert!(matches!(error, PositiveError::Other(_)));
279        assert!(error.to_string().contains("Another error"));
280    }
281}