pub enum MoneyError {
CurrencyMismatch {
expected: &'static str,
found: &'static str,
context: String,
},
ConversionRateMissing {
from: &'static str,
to: &'static str,
},
PrecisionError {
currency: &'static str,
expected: u8,
actual: u32,
suggestion: String,
},
InvalidAmount {
reason: String,
currency: Option<&'static str>,
},
ParseError {
input: String,
expected_currency: Option<&'static str>,
reason: String,
},
RoundingError {
currency: &'static str,
reason: String,
},
InvalidRate {
value: String,
reason: String,
},
Overflow {
operation: String,
currency: &'static str,
},
Underflow {
operation: String,
currency: &'static str,
},
}Expand description
Errors that can occur during monetary operations.
All error variants include context to help diagnose and fix issues.
Variants§
CurrencyMismatch
Attempted to perform an operation between incompatible currencies.
This error is primarily for runtime checks. The type system usually prevents this at compile time.
Fields
ConversionRateMissing
No conversion rate available for the requested currency pair.
PrecisionError
Precision would be lost in the operation.
This warning indicates that an amount has more decimal places than the currency supports.
Fields
InvalidAmount
Invalid amount value (NaN, Infinity, or other invalid state).
Fields
ParseError
Failed to parse a string into a monetary amount.
Fields
RoundingError
Rounding operation failed.
InvalidRate
Invalid exchange rate value.
Fields
Overflow
Arithmetic overflow occurred.
Underflow
Arithmetic underflow occurred.
Implementations§
Source§impl MoneyError
impl MoneyError
Sourcepub fn suggestion(&self) -> &str
pub fn suggestion(&self) -> &str
Returns a suggestion for how to fix this error.
§Examples
use typed_money::MoneyError;
let error = MoneyError::InvalidRate {
value: "0.0".to_string(),
reason: "Rate must be positive".to_string(),
};
println!("{}", error.suggestion());Examples found in repository?
11fn main() {
12 println!("=== Error Handling Examples ===\n");
13
14 // ========================================
15 // Parse Errors
16 // ========================================
17 println!("1. PARSING ERRORS");
18 println!("-----------------");
19
20 // Valid parsing
21 match Amount::<USD>::from_str("123.45") {
22 Ok(amount) => println!("✓ Parsed: {}", amount),
23 Err(e) => println!("✗ Error: {}", e),
24 }
25
26 // Invalid formats
27 let invalid_inputs = vec!["invalid", "12.34.56", "abc123", "", " "];
28
29 println!("\nInvalid inputs:");
30 for input in invalid_inputs {
31 match Amount::<USD>::from_str(input) {
32 Ok(amount) => println!(" '{}' → {}", input, amount),
33 Err(MoneyError::ParseError { input: inp, .. }) => {
34 println!(" '{}' → Parse error (input: '{}')", input, inp);
35 }
36 Err(e) => println!(" '{}' → Error: {}", input, e),
37 }
38 }
39
40 // ========================================
41 // Precision Errors
42 // ========================================
43 println!("\n2. PRECISION ERRORS");
44 println!("-------------------");
45
46 // Division creates excess precision
47 let amount = Amount::<USD>::from_major(10) / 3; // $3.333...
48 println!("After division: {}", amount);
49 println!("Decimal places: {}", amount.precision());
50 println!("Currency expects: {}", Amount::<USD>::currency_precision());
51
52 // Check for precision issues
53 match amount.check_precision() {
54 Ok(()) => println!("✓ Precision OK"),
55 Err(MoneyError::PrecisionError {
56 expected,
57 actual,
58 suggestion,
59 ..
60 }) => {
61 println!("✗ Precision error:");
62 println!(" Expected: {} decimals", expected);
63 println!(" Actual: {} decimals", actual);
64 println!(" Suggestion: {}", suggestion);
65
66 // Recover by normalizing
67 let normalized = amount.normalize();
68 println!("\n✓ After normalizing: {}", normalized);
69 println!(" Precision OK: {}", normalized.check_precision().is_ok());
70 }
71 Err(e) => println!("✗ Unexpected error: {}", e),
72 }
73
74 // ========================================
75 // Invalid Rate Errors
76 // ========================================
77 println!("\n3. INVALID RATE ERRORS");
78 println!("----------------------");
79
80 // Valid rate
81 match Rate::<USD, EUR>::try_new(0.85) {
82 Ok(rate) => println!("✓ Valid rate: {}", rate.value()),
83 Err(e) => println!("✗ Error: {}", e),
84 }
85
86 // Invalid rates
87 let invalid_rates = vec![
88 ("negative", -1.0),
89 ("zero", 0.0),
90 ("NaN", f64::NAN),
91 ("infinity", f64::INFINITY),
92 ];
93
94 println!("\nInvalid rates:");
95 for (desc, value) in invalid_rates {
96 match Rate::<USD, EUR>::try_new(value) {
97 Ok(rate) => println!(
98 " {} ({}) → Unexpected success: {}",
99 desc,
100 value,
101 rate.value()
102 ),
103 Err(MoneyError::InvalidRate { reason, .. }) => {
104 println!(" {} ({}) → {}", desc, value, reason);
105 }
106 Err(e) => println!(" {} ({}) → Error: {}", desc, value, e),
107 }
108 }
109
110 // ========================================
111 // Error Suggestions
112 // ========================================
113 println!("\n4. ERROR RECOVERY SUGGESTIONS");
114 println!("------------------------------");
115
116 let errors = vec![
117 MoneyError::PrecisionError {
118 currency: "USD",
119 expected: 2,
120 actual: 5,
121 suggestion: "Use normalize() or round() to adjust precision".to_string(),
122 },
123 MoneyError::InvalidRate {
124 value: "-1.0".to_string(),
125 reason: "Rate must be positive".to_string(),
126 },
127 MoneyError::ParseError {
128 input: "abc".to_string(),
129 expected_currency: Some("USD"),
130 reason: "Invalid number format".to_string(),
131 },
132 ];
133
134 for error in errors {
135 println!("\nError: {}", error);
136 println!("Suggestion: {}", error.suggestion());
137 if let Some(currency) = error.currency() {
138 println!("Currency: {}", currency);
139 }
140 }
141
142 // ========================================
143 // Safe Division
144 // ========================================
145 println!("\n5. SAFE DIVISION");
146 println!("----------------");
147
148 let amount = Amount::<USD>::from_major(100);
149
150 // Division by zero is handled
151 let divisor = 0;
152 println!("Dividing {} by {}...", amount, divisor);
153
154 if divisor == 0 {
155 println!("✗ Cannot divide by zero!");
156 println!(" Handled before attempting division");
157 } else {
158 let result = amount / divisor;
159 println!("✓ Result: {}", result);
160 }
161
162 // Safe division with non-zero
163 let safe_divisor = 3;
164 let result = amount / safe_divisor;
165 println!("\nDividing {} by {}: {}", amount, safe_divisor, result);
166
167 // ========================================
168 // Pattern Matching on Errors
169 // ========================================
170 println!("\n6. PATTERN MATCHING");
171 println!("-------------------");
172
173 fn parse_and_handle(input: &str) -> MoneyResult<Amount<USD>> {
174 Amount::<USD>::from_str(input)
175 }
176
177 let inputs = vec!["123.45", "invalid", "999.99"];
178
179 for input in inputs {
180 print!("Parsing '{}': ", input);
181
182 match parse_and_handle(input) {
183 Ok(amount) => {
184 println!("✓ Success: {}", amount);
185 }
186 Err(MoneyError::ParseError { input, reason, .. }) => {
187 println!("✗ Parse error");
188 println!(" Input: '{}'", input);
189 println!(" Reason: {}", reason);
190 }
191 Err(e) => {
192 println!("✗ Other error: {}", e);
193 }
194 }
195 }
196
197 // ========================================
198 // Error Propagation with ?
199 // ========================================
200 println!("\n7. ERROR PROPAGATION");
201 println!("--------------------");
202
203 fn calculate_total(price_str: &str, quantity: i64) -> MoneyResult<Amount<USD>> {
204 let price = Amount::<USD>::from_str(price_str)?;
205 let total = price * quantity;
206 total.check_precision()?;
207 Ok(total)
208 }
209
210 match calculate_total("29.99", 3) {
211 Ok(total) => println!("✓ Total: {}", total),
212 Err(e) => println!("✗ Error: {}", e),
213 }
214
215 match calculate_total("invalid", 3) {
216 Ok(total) => println!("✓ Total: {}", total),
217 Err(e) => println!("✗ Error: {}", e),
218 }
219
220 // ========================================
221 // Recovering from Errors
222 // ========================================
223 println!("\n8. ERROR RECOVERY");
224 println!("-----------------");
225
226 fn parse_with_fallback(input: &str, fallback: Amount<USD>) -> Amount<USD> {
227 Amount::<USD>::from_str(input).unwrap_or(fallback)
228 }
229
230 let fallback = Amount::<USD>::from_major(0);
231
232 let inputs = vec!["123.45", "invalid", "67.89"];
233 for input in inputs {
234 let amount = parse_with_fallback(input, fallback);
235 println!("'{}' → {}", input, amount);
236 }
237
238 // ========================================
239 // Validation Before Operations
240 // ========================================
241 println!("\n9. VALIDATION");
242 println!("-------------");
243
244 fn safe_divide(amount: Amount<USD>, divisor: i64) -> MoneyResult<Amount<USD>> {
245 if divisor == 0 {
246 return Err(MoneyError::InvalidAmount {
247 currency: Some("USD"),
248 reason: "Cannot divide by zero".to_string(),
249 });
250 }
251
252 let result = amount / divisor;
253
254 // Check precision after division
255 result.check_precision()?;
256
257 Ok(result)
258 }
259
260 let amount = Amount::<USD>::from_major(100);
261
262 match safe_divide(amount, 0) {
263 Ok(result) => println!(" Division by 0: {}", result),
264 Err(e) => println!(" Division by 0: Error - {}", e),
265 }
266
267 match safe_divide(amount, 3) {
268 Ok(result) => println!(" Division by 3: {}", result),
269 Err(e) => println!(" Division by 3: Error - {}", e),
270 }
271
272 // ========================================
273 // Real-World: Parsing User Input
274 // ========================================
275 println!("\n10. REAL-WORLD: PARSING USER INPUT");
276 println!("-----------------------------------");
277
278 fn process_payment(amount_str: &str) -> MoneyResult<String> {
279 // Parse the amount
280 let amount = Amount::<USD>::from_str(amount_str)?;
281
282 // Validate it's positive
283 if amount.to_minor() <= 0 {
284 return Err(MoneyError::InvalidAmount {
285 currency: Some("USD"),
286 reason: "Payment amount must be positive".to_string(),
287 });
288 }
289
290 // Process the payment
291 let fee = (amount * 3) / 100; // 3% fee
292 let total = amount + fee;
293
294 Ok(format!(
295 "Payment: {}, Fee: {}, Total: {}",
296 amount, fee, total
297 ))
298 }
299
300 let test_inputs = vec!["100.00", "0", "-50", "abc", "250.50"];
301
302 for input in test_inputs {
303 println!("\nProcessing payment: '{}'", input);
304 match process_payment(input) {
305 Ok(summary) => println!(" ✓ {}", summary),
306 Err(e) => {
307 println!(" ✗ Error: {}", e);
308 let suggestion = e.suggestion();
309 println!(" Hint: {}", suggestion);
310 }
311 }
312 }
313
314 // ========================================
315 // Rounding to Fix Precision
316 // ========================================
317 println!("\n11. FIXING PRECISION WITH ROUNDING");
318 println!("-----------------------------------");
319
320 let amount = Amount::<USD>::from_major(10) / 3; // Creates excess precision
321
322 println!("Original: {}", amount);
323 println!(
324 "Precision check: {}",
325 if amount.check_precision().is_ok() {
326 "✓ OK"
327 } else {
328 "✗ Excess precision"
329 }
330 );
331
332 // Fix with rounding
333 let rounded = amount.round(RoundingMode::HalfUp);
334 println!("\nRounded: {}", rounded);
335 println!(
336 "Precision check: {}",
337 if rounded.check_precision().is_ok() {
338 "✓ OK"
339 } else {
340 "✗ Excess precision"
341 }
342 );
343
344 // Or normalize (uses banker's rounding)
345 let normalized = amount.normalize();
346 println!("\nNormalized: {}", normalized);
347 println!(
348 "Precision check: {}",
349 if normalized.check_precision().is_ok() {
350 "✓ OK"
351 } else {
352 "✗ Excess precision"
353 }
354 );
355
356 println!("\n=== All error handling examples completed! ===");
357}Sourcepub fn currency(&self) -> Option<&'static str>
pub fn currency(&self) -> Option<&'static str>
Returns the currency code associated with this error, if any.
Examples found in repository?
11fn main() {
12 println!("=== Error Handling Examples ===\n");
13
14 // ========================================
15 // Parse Errors
16 // ========================================
17 println!("1. PARSING ERRORS");
18 println!("-----------------");
19
20 // Valid parsing
21 match Amount::<USD>::from_str("123.45") {
22 Ok(amount) => println!("✓ Parsed: {}", amount),
23 Err(e) => println!("✗ Error: {}", e),
24 }
25
26 // Invalid formats
27 let invalid_inputs = vec!["invalid", "12.34.56", "abc123", "", " "];
28
29 println!("\nInvalid inputs:");
30 for input in invalid_inputs {
31 match Amount::<USD>::from_str(input) {
32 Ok(amount) => println!(" '{}' → {}", input, amount),
33 Err(MoneyError::ParseError { input: inp, .. }) => {
34 println!(" '{}' → Parse error (input: '{}')", input, inp);
35 }
36 Err(e) => println!(" '{}' → Error: {}", input, e),
37 }
38 }
39
40 // ========================================
41 // Precision Errors
42 // ========================================
43 println!("\n2. PRECISION ERRORS");
44 println!("-------------------");
45
46 // Division creates excess precision
47 let amount = Amount::<USD>::from_major(10) / 3; // $3.333...
48 println!("After division: {}", amount);
49 println!("Decimal places: {}", amount.precision());
50 println!("Currency expects: {}", Amount::<USD>::currency_precision());
51
52 // Check for precision issues
53 match amount.check_precision() {
54 Ok(()) => println!("✓ Precision OK"),
55 Err(MoneyError::PrecisionError {
56 expected,
57 actual,
58 suggestion,
59 ..
60 }) => {
61 println!("✗ Precision error:");
62 println!(" Expected: {} decimals", expected);
63 println!(" Actual: {} decimals", actual);
64 println!(" Suggestion: {}", suggestion);
65
66 // Recover by normalizing
67 let normalized = amount.normalize();
68 println!("\n✓ After normalizing: {}", normalized);
69 println!(" Precision OK: {}", normalized.check_precision().is_ok());
70 }
71 Err(e) => println!("✗ Unexpected error: {}", e),
72 }
73
74 // ========================================
75 // Invalid Rate Errors
76 // ========================================
77 println!("\n3. INVALID RATE ERRORS");
78 println!("----------------------");
79
80 // Valid rate
81 match Rate::<USD, EUR>::try_new(0.85) {
82 Ok(rate) => println!("✓ Valid rate: {}", rate.value()),
83 Err(e) => println!("✗ Error: {}", e),
84 }
85
86 // Invalid rates
87 let invalid_rates = vec![
88 ("negative", -1.0),
89 ("zero", 0.0),
90 ("NaN", f64::NAN),
91 ("infinity", f64::INFINITY),
92 ];
93
94 println!("\nInvalid rates:");
95 for (desc, value) in invalid_rates {
96 match Rate::<USD, EUR>::try_new(value) {
97 Ok(rate) => println!(
98 " {} ({}) → Unexpected success: {}",
99 desc,
100 value,
101 rate.value()
102 ),
103 Err(MoneyError::InvalidRate { reason, .. }) => {
104 println!(" {} ({}) → {}", desc, value, reason);
105 }
106 Err(e) => println!(" {} ({}) → Error: {}", desc, value, e),
107 }
108 }
109
110 // ========================================
111 // Error Suggestions
112 // ========================================
113 println!("\n4. ERROR RECOVERY SUGGESTIONS");
114 println!("------------------------------");
115
116 let errors = vec![
117 MoneyError::PrecisionError {
118 currency: "USD",
119 expected: 2,
120 actual: 5,
121 suggestion: "Use normalize() or round() to adjust precision".to_string(),
122 },
123 MoneyError::InvalidRate {
124 value: "-1.0".to_string(),
125 reason: "Rate must be positive".to_string(),
126 },
127 MoneyError::ParseError {
128 input: "abc".to_string(),
129 expected_currency: Some("USD"),
130 reason: "Invalid number format".to_string(),
131 },
132 ];
133
134 for error in errors {
135 println!("\nError: {}", error);
136 println!("Suggestion: {}", error.suggestion());
137 if let Some(currency) = error.currency() {
138 println!("Currency: {}", currency);
139 }
140 }
141
142 // ========================================
143 // Safe Division
144 // ========================================
145 println!("\n5. SAFE DIVISION");
146 println!("----------------");
147
148 let amount = Amount::<USD>::from_major(100);
149
150 // Division by zero is handled
151 let divisor = 0;
152 println!("Dividing {} by {}...", amount, divisor);
153
154 if divisor == 0 {
155 println!("✗ Cannot divide by zero!");
156 println!(" Handled before attempting division");
157 } else {
158 let result = amount / divisor;
159 println!("✓ Result: {}", result);
160 }
161
162 // Safe division with non-zero
163 let safe_divisor = 3;
164 let result = amount / safe_divisor;
165 println!("\nDividing {} by {}: {}", amount, safe_divisor, result);
166
167 // ========================================
168 // Pattern Matching on Errors
169 // ========================================
170 println!("\n6. PATTERN MATCHING");
171 println!("-------------------");
172
173 fn parse_and_handle(input: &str) -> MoneyResult<Amount<USD>> {
174 Amount::<USD>::from_str(input)
175 }
176
177 let inputs = vec!["123.45", "invalid", "999.99"];
178
179 for input in inputs {
180 print!("Parsing '{}': ", input);
181
182 match parse_and_handle(input) {
183 Ok(amount) => {
184 println!("✓ Success: {}", amount);
185 }
186 Err(MoneyError::ParseError { input, reason, .. }) => {
187 println!("✗ Parse error");
188 println!(" Input: '{}'", input);
189 println!(" Reason: {}", reason);
190 }
191 Err(e) => {
192 println!("✗ Other error: {}", e);
193 }
194 }
195 }
196
197 // ========================================
198 // Error Propagation with ?
199 // ========================================
200 println!("\n7. ERROR PROPAGATION");
201 println!("--------------------");
202
203 fn calculate_total(price_str: &str, quantity: i64) -> MoneyResult<Amount<USD>> {
204 let price = Amount::<USD>::from_str(price_str)?;
205 let total = price * quantity;
206 total.check_precision()?;
207 Ok(total)
208 }
209
210 match calculate_total("29.99", 3) {
211 Ok(total) => println!("✓ Total: {}", total),
212 Err(e) => println!("✗ Error: {}", e),
213 }
214
215 match calculate_total("invalid", 3) {
216 Ok(total) => println!("✓ Total: {}", total),
217 Err(e) => println!("✗ Error: {}", e),
218 }
219
220 // ========================================
221 // Recovering from Errors
222 // ========================================
223 println!("\n8. ERROR RECOVERY");
224 println!("-----------------");
225
226 fn parse_with_fallback(input: &str, fallback: Amount<USD>) -> Amount<USD> {
227 Amount::<USD>::from_str(input).unwrap_or(fallback)
228 }
229
230 let fallback = Amount::<USD>::from_major(0);
231
232 let inputs = vec!["123.45", "invalid", "67.89"];
233 for input in inputs {
234 let amount = parse_with_fallback(input, fallback);
235 println!("'{}' → {}", input, amount);
236 }
237
238 // ========================================
239 // Validation Before Operations
240 // ========================================
241 println!("\n9. VALIDATION");
242 println!("-------------");
243
244 fn safe_divide(amount: Amount<USD>, divisor: i64) -> MoneyResult<Amount<USD>> {
245 if divisor == 0 {
246 return Err(MoneyError::InvalidAmount {
247 currency: Some("USD"),
248 reason: "Cannot divide by zero".to_string(),
249 });
250 }
251
252 let result = amount / divisor;
253
254 // Check precision after division
255 result.check_precision()?;
256
257 Ok(result)
258 }
259
260 let amount = Amount::<USD>::from_major(100);
261
262 match safe_divide(amount, 0) {
263 Ok(result) => println!(" Division by 0: {}", result),
264 Err(e) => println!(" Division by 0: Error - {}", e),
265 }
266
267 match safe_divide(amount, 3) {
268 Ok(result) => println!(" Division by 3: {}", result),
269 Err(e) => println!(" Division by 3: Error - {}", e),
270 }
271
272 // ========================================
273 // Real-World: Parsing User Input
274 // ========================================
275 println!("\n10. REAL-WORLD: PARSING USER INPUT");
276 println!("-----------------------------------");
277
278 fn process_payment(amount_str: &str) -> MoneyResult<String> {
279 // Parse the amount
280 let amount = Amount::<USD>::from_str(amount_str)?;
281
282 // Validate it's positive
283 if amount.to_minor() <= 0 {
284 return Err(MoneyError::InvalidAmount {
285 currency: Some("USD"),
286 reason: "Payment amount must be positive".to_string(),
287 });
288 }
289
290 // Process the payment
291 let fee = (amount * 3) / 100; // 3% fee
292 let total = amount + fee;
293
294 Ok(format!(
295 "Payment: {}, Fee: {}, Total: {}",
296 amount, fee, total
297 ))
298 }
299
300 let test_inputs = vec!["100.00", "0", "-50", "abc", "250.50"];
301
302 for input in test_inputs {
303 println!("\nProcessing payment: '{}'", input);
304 match process_payment(input) {
305 Ok(summary) => println!(" ✓ {}", summary),
306 Err(e) => {
307 println!(" ✗ Error: {}", e);
308 let suggestion = e.suggestion();
309 println!(" Hint: {}", suggestion);
310 }
311 }
312 }
313
314 // ========================================
315 // Rounding to Fix Precision
316 // ========================================
317 println!("\n11. FIXING PRECISION WITH ROUNDING");
318 println!("-----------------------------------");
319
320 let amount = Amount::<USD>::from_major(10) / 3; // Creates excess precision
321
322 println!("Original: {}", amount);
323 println!(
324 "Precision check: {}",
325 if amount.check_precision().is_ok() {
326 "✓ OK"
327 } else {
328 "✗ Excess precision"
329 }
330 );
331
332 // Fix with rounding
333 let rounded = amount.round(RoundingMode::HalfUp);
334 println!("\nRounded: {}", rounded);
335 println!(
336 "Precision check: {}",
337 if rounded.check_precision().is_ok() {
338 "✓ OK"
339 } else {
340 "✗ Excess precision"
341 }
342 );
343
344 // Or normalize (uses banker's rounding)
345 let normalized = amount.normalize();
346 println!("\nNormalized: {}", normalized);
347 println!(
348 "Precision check: {}",
349 if normalized.check_precision().is_ok() {
350 "✓ OK"
351 } else {
352 "✗ Excess precision"
353 }
354 );
355
356 println!("\n=== All error handling examples completed! ===");
357}Trait Implementations§
Source§impl Clone for MoneyError
impl Clone for MoneyError
Source§fn clone(&self) -> MoneyError
fn clone(&self) -> MoneyError
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more