1use core::convert::Infallible;
6use core::fmt;
7
8use internals::error::InputString;
9use internals::write_err;
10
11use super::INPUT_STRING_LEN_LIMIT;
12use crate::parse_int::{PrefixedHexError, UnprefixedHexError};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ParseError(pub(crate) ParseErrorInner);
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub(crate) enum ParseErrorInner {
20 Amount(ParseAmountError),
22 Denomination(ParseDenominationError),
24 MissingDenomination(MissingDenominationError),
26}
27
28impl From<Infallible> for ParseError {
29 fn from(never: Infallible) -> Self { match never {} }
30}
31
32impl From<Infallible> for ParseErrorInner {
33 fn from(never: Infallible) -> Self { match never {} }
34}
35
36impl fmt::Display for ParseError {
37 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38 match self.0 {
39 ParseErrorInner::Amount(ref e) => write_err!(f, "invalid amount"; e),
40 ParseErrorInner::Denomination(ref e) => write_err!(f, "invalid denomination"; e),
41 ParseErrorInner::MissingDenomination(ref e) => write_err!(f, "missing denomination"; e),
43 }
44 }
45}
46
47#[cfg(feature = "std")]
48impl std::error::Error for ParseError {
49 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
50 match self.0 {
51 ParseErrorInner::Amount(ref e) => Some(e),
52 ParseErrorInner::Denomination(ref e) => Some(e),
53 ParseErrorInner::MissingDenomination(ref e) => Some(e),
55 }
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct ParseAmountError(pub(crate) ParseAmountErrorInner);
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub(crate) enum ParseAmountErrorInner {
65 OutOfRange(OutOfRangeError),
67 TooPrecise(TooPreciseError),
69 MissingDigits(MissingDigitsError),
71 InputTooLarge(InputTooLargeError),
73 InvalidCharacter(InvalidCharacterError),
75 BadPosition(BadPositionError),
77 PrefixedHex(PrefixedHexError),
79 UnprefixedHex(UnprefixedHexError),
81}
82
83impl From<Infallible> for ParseAmountError {
84 fn from(never: Infallible) -> Self { match never {} }
85}
86
87impl From<Infallible> for ParseAmountErrorInner {
88 fn from(never: Infallible) -> Self { match never {} }
89}
90
91impl fmt::Display for ParseAmountError {
92 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93 use ParseAmountErrorInner as E;
94
95 match self.0 {
96 E::OutOfRange(ref error) => write_err!(f, "amount out of range"; error),
97 E::TooPrecise(ref error) => write_err!(f, "amount has a too high precision"; error),
98 E::MissingDigits(ref error) => write_err!(f, "the input has too few digits"; error),
99 E::InputTooLarge(ref error) => write_err!(f, "the input is too large"; error),
100 E::InvalidCharacter(ref error) => {
101 write_err!(f, "invalid character in the input"; error)
102 }
103 E::BadPosition(ref error) => write_err!(f, "valid character in bad position"; error),
104 E::PrefixedHex(ref error) => write_err!(f, "prefixed hex is invalid"; error),
105 E::UnprefixedHex(ref error) => write_err!(f, "unprefixed hex is invalid"; error),
106 }
107 }
108}
109
110#[cfg(feature = "std")]
111impl std::error::Error for ParseAmountError {
112 #[inline]
113 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
114 use ParseAmountErrorInner as E;
115
116 match self.0 {
117 E::TooPrecise(ref error) => Some(error),
118 E::InputTooLarge(ref error) => Some(error),
119 E::OutOfRange(ref error) => Some(error),
120 E::MissingDigits(ref error) => Some(error),
121 E::InvalidCharacter(ref error) => Some(error),
122 E::BadPosition(ref error) => Some(error),
123 E::PrefixedHex(ref error) => Some(error),
124 E::UnprefixedHex(ref error) => Some(error),
125 }
126 }
127}
128
129#[derive(Debug, Copy, Clone, Eq, PartialEq)]
131pub struct OutOfRangeError {
132 pub(super) is_signed: bool,
133 pub(super) is_greater_than_max: bool,
134}
135
136impl OutOfRangeError {
137 #[inline]
141 pub fn valid_range(self) -> (i64, u64) {
142 match self.is_signed {
143 true => (i64::MIN, i64::MAX as u64),
144 false => (0, u64::MAX),
145 }
146 }
147
148 #[inline]
150 pub fn is_above_max(self) -> bool { self.is_greater_than_max }
151
152 #[inline]
154 pub fn is_below_min(self) -> bool { !self.is_greater_than_max }
155
156 #[cfg(test)]
157 #[inline]
158 pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
159
160 #[cfg(test)]
161 #[inline]
162 pub(crate) fn too_small() -> Self {
163 Self {
164 is_signed: true,
166 is_greater_than_max: false,
167 }
168 }
169
170 #[inline]
171 pub(crate) fn negative() -> Self {
172 Self {
173 is_signed: false,
175 is_greater_than_max: false,
176 }
177 }
178}
179
180impl From<Infallible> for OutOfRangeError {
181 fn from(never: Infallible) -> Self { match never {} }
182}
183
184impl fmt::Display for OutOfRangeError {
185 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186 if self.is_greater_than_max {
187 write!(f, "the amount is greater than {}", self.valid_range().1)
188 } else {
189 write!(f, "the amount is less than {}", self.valid_range().0)
190 }
191 }
192}
193
194#[cfg(feature = "std")]
195impl std::error::Error for OutOfRangeError {
196 #[inline]
197 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
198}
199
200#[derive(Debug, Clone, Eq, PartialEq)]
202pub struct TooPreciseError {
203 pub(super) position: usize,
204}
205
206impl From<Infallible> for TooPreciseError {
207 fn from(never: Infallible) -> Self { match never {} }
208}
209
210impl fmt::Display for TooPreciseError {
211 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212 match self.position {
213 0 => f.write_str("the amount is less than 1 satoshi but it's not zero"),
214 pos => write!(
215 f,
216 "the digits starting from position {} represent a sub-satoshi amount",
217 pos
218 ),
219 }
220 }
221}
222
223#[cfg(feature = "std")]
224impl std::error::Error for TooPreciseError {
225 #[inline]
226 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
227}
228
229#[derive(Debug, Clone, Eq, PartialEq)]
231pub struct InputTooLargeError {
232 pub(super) len: usize,
233}
234
235impl From<Infallible> for InputTooLargeError {
236 fn from(never: Infallible) -> Self { match never {} }
237}
238
239impl fmt::Display for InputTooLargeError {
240 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
241 match self.len - INPUT_STRING_LEN_LIMIT {
242 1 => write!(
243 f,
244 "the input is one character longer than the maximum allowed length ({})",
245 INPUT_STRING_LEN_LIMIT
246 ),
247 n => write!(
248 f,
249 "the input is {} characters longer than the maximum allowed length ({})",
250 n, INPUT_STRING_LEN_LIMIT
251 ),
252 }
253 }
254}
255
256#[cfg(feature = "std")]
257impl std::error::Error for InputTooLargeError {
258 #[inline]
259 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
260}
261
262#[derive(Debug, Clone, Eq, PartialEq)]
266pub struct MissingDigitsError {
267 pub(super) kind: MissingDigitsKind,
268}
269
270impl From<Infallible> for MissingDigitsError {
271 fn from(never: Infallible) -> Self { match never {} }
272}
273
274impl fmt::Display for MissingDigitsError {
275 #[inline]
276 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277 match self.kind {
278 MissingDigitsKind::Empty => f.write_str("the input is empty"),
279 MissingDigitsKind::OnlyMinusSign =>
280 f.write_str("there are no digits following the minus (-) sign"),
281 }
282 }
283}
284
285#[cfg(feature = "std")]
286impl std::error::Error for MissingDigitsError {
287 #[inline]
288 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
289}
290
291#[derive(Debug, Clone, Eq, PartialEq)]
292pub(super) enum MissingDigitsKind {
293 Empty,
294 OnlyMinusSign,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
299pub struct InvalidCharacterError {
300 pub(super) invalid_char: char,
301 pub(super) position: usize,
302}
303
304impl From<Infallible> for InvalidCharacterError {
305 fn from(never: Infallible) -> Self { match never {} }
306}
307
308impl fmt::Display for InvalidCharacterError {
309 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
310 match self.invalid_char {
311 '.' => f.write_str("there is more than one decimal separator (dot) in the input"),
312 '-' => f.write_str("there is more than one minus sign (-) in the input"),
313 c => write!(
314 f,
315 "the character '{}' at position {} is not a valid digit",
316 c, self.position
317 ),
318 }
319 }
320}
321
322#[cfg(feature = "std")]
323impl std::error::Error for InvalidCharacterError {
324 #[inline]
325 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
326}
327
328#[derive(Debug, Clone, PartialEq, Eq)]
330pub struct BadPositionError {
331 pub(super) char: char,
332 pub(super) position: usize,
333}
334
335impl From<Infallible> for BadPositionError {
336 fn from(never: Infallible) -> Self { match never {} }
337}
338
339impl fmt::Display for BadPositionError {
340 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
341 match self.char {
342 '_' => match self.position {
343 0 => f.write_str("the input amount is prefixed with an underscore (_)"),
344 1 => f.write_str("the input amount is prefixed with an underscore (_)"),
348 _ => f.write_str("there are consecutive underscores (_) in the input"),
349 },
350 c => write!(f, "The character '{}' is at a bad position: {}", c, self.position),
351 }
352 }
353}
354
355#[cfg(feature = "std")]
356impl std::error::Error for BadPositionError {
357 #[inline]
358 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
359}
360
361#[derive(Debug, Clone, PartialEq, Eq)]
363#[non_exhaustive]
364pub enum ParseDenominationError {
365 Unknown(UnknownDenominationError),
367 PossiblyConfusing(PossiblyConfusingDenominationError),
369}
370
371impl From<Infallible> for ParseDenominationError {
372 fn from(never: Infallible) -> Self { match never {} }
373}
374
375impl fmt::Display for ParseDenominationError {
376 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
377 match *self {
378 Self::Unknown(ref e) => write_err!(f, "denomination parse error"; e),
379 Self::PossiblyConfusing(ref e) => write_err!(f, "denomination parse error"; e),
380 }
381 }
382}
383
384#[cfg(feature = "std")]
385impl std::error::Error for ParseDenominationError {
386 #[inline]
387 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
388 match *self {
389 Self::Unknown(ref e) => Some(e),
390 Self::PossiblyConfusing(ref e) => Some(e),
391 }
392 }
393}
394
395#[derive(Debug, Clone, PartialEq, Eq)]
397#[non_exhaustive]
398pub struct MissingDenominationError;
399
400impl From<Infallible> for MissingDenominationError {
401 fn from(never: Infallible) -> Self { match never {} }
402}
403
404impl fmt::Display for MissingDenominationError {
405 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
406 write!(f, "the input does not contain a denomination")
407 }
408}
409
410#[cfg(feature = "std")]
411impl std::error::Error for MissingDenominationError {
412 #[inline]
413 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
414}
415
416#[derive(Debug, Clone, PartialEq, Eq)]
418#[non_exhaustive]
419pub struct UnknownDenominationError(pub(super) InputString);
420
421impl From<Infallible> for UnknownDenominationError {
422 fn from(never: Infallible) -> Self { match never {} }
423}
424
425impl fmt::Display for UnknownDenominationError {
426 #[inline]
427 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
428 self.0.unknown_variant("bitcoin denomination", f)
429 }
430}
431
432#[cfg(feature = "std")]
433impl std::error::Error for UnknownDenominationError {
434 #[inline]
435 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
436}
437
438#[derive(Debug, Clone, PartialEq, Eq)]
440#[non_exhaustive]
441pub struct PossiblyConfusingDenominationError(pub(super) InputString);
442
443impl From<Infallible> for PossiblyConfusingDenominationError {
444 fn from(never: Infallible) -> Self { match never {} }
445}
446
447impl fmt::Display for PossiblyConfusingDenominationError {
448 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
449 write!(f, "{}: possibly confusing denomination - we intentionally do not support 'M' and 'P' so as to not confuse mega/milli and peta/pico", self.0.display_cannot_parse("bitcoin denomination"))
450 }
451}
452
453#[cfg(feature = "std")]
454impl std::error::Error for PossiblyConfusingDenominationError {
455 #[inline]
456 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
457}
458
459#[cfg(feature = "encoding")]
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct AmountDecoderError(pub(super) AmountDecoderErrorInner);
463
464#[cfg(feature = "encoding")]
465impl AmountDecoderError {
466 #[inline]
468 pub(super) fn eof(e: encoding::UnexpectedEofError) -> Self {
469 Self(AmountDecoderErrorInner::UnexpectedEof(e))
470 }
471
472 #[inline]
474 pub(super) fn out_of_range(e: OutOfRangeError) -> Self {
475 Self(AmountDecoderErrorInner::OutOfRange(e))
476 }
477}
478
479#[cfg(feature = "encoding")]
480#[derive(Debug, Clone, PartialEq, Eq)]
481pub(super) enum AmountDecoderErrorInner {
482 UnexpectedEof(encoding::UnexpectedEofError),
484 OutOfRange(OutOfRangeError),
486}
487
488#[cfg(feature = "encoding")]
489impl From<Infallible> for AmountDecoderError {
490 fn from(never: Infallible) -> Self { match never {} }
491}
492
493#[cfg(feature = "encoding")]
494impl fmt::Display for AmountDecoderError {
495 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
496 use AmountDecoderErrorInner as E;
497
498 match self.0 {
499 E::UnexpectedEof(ref e) => write_err!(f, "decode error"; e),
500 E::OutOfRange(ref e) => write_err!(f, "decode error"; e),
501 }
502 }
503}
504
505#[cfg(feature = "encoding")]
506#[cfg(feature = "std")]
507impl std::error::Error for AmountDecoderError {
508 #[inline]
509 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
510 use AmountDecoderErrorInner as E;
511
512 match self.0 {
513 E::UnexpectedEof(ref e) => Some(e),
514 E::OutOfRange(ref e) => Some(e),
515 }
516 }
517}
518
519#[cfg(test)]
520mod tests {
521 #[cfg(feature = "alloc")]
522 use alloc::string::ToString;
523 #[cfg(feature = "alloc")]
524 use core::str::FromStr;
525 #[cfg(feature = "std")]
526 use std::error::Error;
527
528 #[cfg(feature = "encoding")]
529 use encoding::{Decode as _, Decoder as _};
530
531 #[cfg(feature = "alloc")]
532 use super::{ParseAmountError, ParseAmountErrorInner, ParseErrorInner};
533 #[cfg(feature = "alloc")]
534 use crate::amount::{Amount, Denomination, ParseDenominationError, ParseError};
535
536 #[test]
537 #[cfg(feature = "alloc")]
538 #[allow(clippy::too_many_lines)] fn error_display_is_non_empty() {
540 macro_rules! assert_amount_err {
542 ($e:expr, $enum_arm:ident, $err_msg:expr) => {
543 assert!(!$e.to_string().is_empty());
544 let ParseError(ParseErrorInner::Amount(err)) = $e else { panic!($err_msg) };
545 assert!(!err.to_string().is_empty());
546 #[cfg(feature = "std")]
547 assert!(err.source().is_some());
548
549 let ParseAmountError(ParseAmountErrorInner::$enum_arm(err)) = err else {
550 panic!($err_msg)
551 };
552 assert!(!err.to_string().is_empty());
553 #[cfg(feature = "std")]
555 assert!(err.source().is_none());
556 };
557 }
558
559 let long_input = alloc::format!("{} BTC", "1".repeat(51));
562 let e = Amount::from_str(&long_input).unwrap_err();
563 assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
564 let long_input = alloc::format!("{} BTC", "1".repeat(52));
566 let e = Amount::from_str(&long_input).unwrap_err();
567 assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
568
569 let e = Amount::from_str("12x34 BTC").unwrap_err();
572 assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
573 let e = Amount::from_str("12.3.4 BTC").unwrap_err();
575 assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
576 let e = Amount::from_str("--1234 BTC").unwrap_err();
578 assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
579
580 let e = Amount::from_str("BTC").unwrap_err();
583 assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
584 let e = Amount::from_str("- BTC").unwrap_err();
586 assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
587
588 let e = Amount::from_str("21000001 BTC").unwrap_err();
591 assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
592 let e = Amount::from_str("-10 BTC").unwrap_err();
594 assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
595
596 let e = Amount::from_str("0.000000001 BTC").unwrap_err();
598 assert_amount_err!(e, TooPrecise, "error should be TooPreciseError");
599
600 let e = Amount::from_str("_123 BTC").unwrap_err();
603 assert_amount_err!(e, BadPosition, "error should be BadPositionError");
604 let e = Amount::from_str("-_123 BTC").unwrap_err();
606 assert_amount_err!(e, BadPosition, "error should be BadPositionError");
607 let e = Amount::from_str("1__23 BTC").unwrap_err();
609 assert_amount_err!(e, BadPosition, "error should be BadPositionError");
610
611 let e = Amount::from_str_in("invalid", Denomination::Bitcoin).unwrap_err();
613 assert!(!e.to_string().is_empty());
614 #[cfg(feature = "std")]
615 assert!(e.source().is_some());
616
617 let e = Denomination::from_str("XYZ").unwrap_err();
619 #[cfg(feature = "std")]
620 assert!(e.source().is_some());
621 let ParseDenominationError::Unknown(e) = e else {
622 panic!("error should be UnknownDenominationError")
623 };
624 assert!(!e.to_string().is_empty());
625 #[cfg(feature = "std")]
626 assert!(e.source().is_none());
627
628 let e = Denomination::from_str("MBTC").unwrap_err();
630 #[cfg(feature = "std")]
631 assert!(e.source().is_some());
632 let ParseDenominationError::PossiblyConfusing(e) = e else {
633 panic!("error should be PossiblyConfusingDenominationError")
634 };
635 assert!(!e.to_string().is_empty());
636 #[cfg(feature = "std")]
637 assert!(e.source().is_none());
638
639 let e = Denomination::from_str("UNKNOWN").unwrap_err();
642 assert!(!e.to_string().is_empty());
643 #[cfg(feature = "std")]
644 assert!(e.source().is_some());
645 let e = Denomination::from_str("MBTC").unwrap_err();
647 assert!(!e.to_string().is_empty());
648 #[cfg(feature = "std")]
649 assert!(e.source().is_some());
650
651 let e = "invalid BTC".parse::<Amount>().unwrap_err();
654 assert!(!e.to_string().is_empty());
655 #[cfg(feature = "std")]
656 assert!(e.source().is_some());
657 let e = "123 GBTC".parse::<Amount>().unwrap_err();
659 assert!(!e.to_string().is_empty());
660 #[cfg(feature = "std")]
661 assert!(e.source().is_some());
662 let e = "123".parse::<Amount>().unwrap_err();
664 assert!(!e.to_string().is_empty());
665 #[cfg(feature = "std")]
666 assert!(e.source().is_some());
667
668 #[cfg(feature = "encoding")]
669 {
670 let mut decoder = Amount::decoder();
673 let _ = decoder.push_bytes(&mut [0u8; 3].as_slice());
674 let e = decoder.end().unwrap_err();
675 assert!(!e.to_string().is_empty());
676 #[cfg(feature = "std")]
677 assert!(e.source().is_some());
678
679 let mut decoder = Amount::decoder();
681 let _ =
682 decoder.push_bytes(&mut (21_000_001 * 100_000_000_u64).to_le_bytes().as_slice());
683 let e = decoder.end().unwrap_err();
684 assert!(!e.to_string().is_empty());
685 #[cfg(feature = "std")]
686 assert!(e.source().is_some());
687 }
688 }
689}