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}