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)> {
198 let Self { is_signed: _, is_greater_than_max: _ } = self;
199 None
200 }
201}
202
203#[derive(Debug, Clone, Eq, PartialEq)]
205pub struct TooPreciseError {
206 pub(super) position: usize,
207}
208
209impl From<Infallible> for TooPreciseError {
210 fn from(never: Infallible) -> Self { match never {} }
211}
212
213impl fmt::Display for TooPreciseError {
214 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215 match self.position {
216 0 => f.write_str("the amount is less than 1 satoshi but it's not zero"),
217 pos => write!(
218 f,
219 "the digits starting from position {} represent a sub-satoshi amount",
220 pos
221 ),
222 }
223 }
224}
225
226#[cfg(feature = "std")]
227impl std::error::Error for TooPreciseError {
228 #[inline]
229 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
230 let Self { position: _ } = self;
231 None
232 }
233}
234
235#[derive(Debug, Clone, Eq, PartialEq)]
237pub struct InputTooLargeError {
238 pub(super) len: usize,
239}
240
241impl From<Infallible> for InputTooLargeError {
242 fn from(never: Infallible) -> Self { match never {} }
243}
244
245impl fmt::Display for InputTooLargeError {
246 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
247 match self.len - INPUT_STRING_LEN_LIMIT {
248 1 => write!(
249 f,
250 "the input is one character longer than the maximum allowed length ({})",
251 INPUT_STRING_LEN_LIMIT
252 ),
253 n => write!(
254 f,
255 "the input is {} characters longer than the maximum allowed length ({})",
256 n, INPUT_STRING_LEN_LIMIT
257 ),
258 }
259 }
260}
261
262#[cfg(feature = "std")]
263impl std::error::Error for InputTooLargeError {
264 #[inline]
265 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
266 let Self { len: _ } = self;
267 None
268 }
269}
270
271#[derive(Debug, Clone, Eq, PartialEq)]
275pub struct MissingDigitsError {
276 pub(super) kind: MissingDigitsKind,
277}
278
279impl From<Infallible> for MissingDigitsError {
280 fn from(never: Infallible) -> Self { match never {} }
281}
282
283impl fmt::Display for MissingDigitsError {
284 #[inline]
285 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
286 match self.kind {
287 MissingDigitsKind::Empty => f.write_str("the input is empty"),
288 MissingDigitsKind::OnlyMinusSign =>
289 f.write_str("there are no digits following the minus (-) sign"),
290 }
291 }
292}
293
294#[cfg(feature = "std")]
295impl std::error::Error for MissingDigitsError {
296 #[inline]
297 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
298 let Self { kind: _ } = self;
299 None
300 }
301}
302
303#[derive(Debug, Clone, Eq, PartialEq)]
304pub(super) enum MissingDigitsKind {
305 Empty,
306 OnlyMinusSign,
307}
308
309#[derive(Debug, Clone, PartialEq, Eq)]
311pub struct InvalidCharacterError {
312 pub(super) invalid_char: char,
313 pub(super) position: usize,
314}
315
316impl From<Infallible> for InvalidCharacterError {
317 fn from(never: Infallible) -> Self { match never {} }
318}
319
320impl fmt::Display for InvalidCharacterError {
321 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
322 match self.invalid_char {
323 '.' => f.write_str("there is more than one decimal separator (dot) in the input"),
324 '-' => f.write_str("there is more than one minus sign (-) in the input"),
325 c => write!(
326 f,
327 "the character '{}' at position {} is not a valid digit",
328 c, self.position
329 ),
330 }
331 }
332}
333
334#[cfg(feature = "std")]
335impl std::error::Error for InvalidCharacterError {
336 #[inline]
337 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
338 let Self { invalid_char: _, position: _ } = self;
339 None
340 }
341}
342
343#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct BadPositionError {
346 pub(super) char: char,
347 pub(super) position: usize,
348}
349
350impl From<Infallible> for BadPositionError {
351 fn from(never: Infallible) -> Self { match never {} }
352}
353
354impl fmt::Display for BadPositionError {
355 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
356 match self.char {
357 '_' => match self.position {
358 0 => f.write_str("the input amount is prefixed with an underscore (_)"),
359 1 => f.write_str("the input amount is prefixed with an underscore (_)"),
363 _ => f.write_str("there are consecutive underscores (_) in the input"),
364 },
365 c => write!(f, "The character '{}' is at a bad position: {}", c, self.position),
366 }
367 }
368}
369
370#[cfg(feature = "std")]
371impl std::error::Error for BadPositionError {
372 #[inline]
373 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
374 let Self { char: _, position: _ } = self;
375 None
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq)]
381#[non_exhaustive]
382pub enum ParseDenominationError {
383 Unknown(UnknownDenominationError),
385 PossiblyConfusing(PossiblyConfusingDenominationError),
387}
388
389impl From<Infallible> for ParseDenominationError {
390 fn from(never: Infallible) -> Self { match never {} }
391}
392
393impl fmt::Display for ParseDenominationError {
394 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
395 match *self {
396 Self::Unknown(ref e) => write_err!(f, "denomination parse error"; e),
397 Self::PossiblyConfusing(ref e) => write_err!(f, "denomination parse error"; e),
398 }
399 }
400}
401
402#[cfg(feature = "std")]
403impl std::error::Error for ParseDenominationError {
404 #[inline]
405 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
406 match *self {
407 Self::Unknown(ref e) => Some(e),
408 Self::PossiblyConfusing(ref e) => Some(e),
409 }
410 }
411}
412
413#[derive(Debug, Clone, PartialEq, Eq)]
415#[non_exhaustive]
416pub struct MissingDenominationError;
417
418impl From<Infallible> for MissingDenominationError {
419 fn from(never: Infallible) -> Self { match never {} }
420}
421
422impl fmt::Display for MissingDenominationError {
423 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
424 write!(f, "the input does not contain a denomination")
425 }
426}
427
428#[cfg(feature = "std")]
429impl std::error::Error for MissingDenominationError {
430 #[inline]
431 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
432 let Self {} = self;
433 None
434 }
435}
436
437#[derive(Debug, Clone, PartialEq, Eq)]
439#[non_exhaustive]
440pub struct UnknownDenominationError(pub(super) InputString);
441
442impl From<Infallible> for UnknownDenominationError {
443 fn from(never: Infallible) -> Self { match never {} }
444}
445
446impl fmt::Display for UnknownDenominationError {
447 #[inline]
448 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
449 self.0.unknown_variant("bitcoin denomination", f)
450 }
451}
452
453#[cfg(feature = "std")]
454impl std::error::Error for UnknownDenominationError {
455 #[inline]
456 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
457 let Self(_) = self;
458 None
459 }
460}
461
462#[derive(Debug, Clone, PartialEq, Eq)]
464#[non_exhaustive]
465pub struct PossiblyConfusingDenominationError(pub(super) InputString);
466
467impl From<Infallible> for PossiblyConfusingDenominationError {
468 fn from(never: Infallible) -> Self { match never {} }
469}
470
471impl fmt::Display for PossiblyConfusingDenominationError {
472 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
473 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"))
474 }
475}
476
477#[cfg(feature = "std")]
478impl std::error::Error for PossiblyConfusingDenominationError {
479 #[inline]
480 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
481 let Self(_) = self;
482 None
483 }
484}
485
486#[cfg(feature = "encoding")]
488#[derive(Debug, Clone, PartialEq, Eq)]
489pub struct AmountDecoderError(pub(super) AmountDecoderErrorInner);
490
491#[cfg(feature = "encoding")]
492impl AmountDecoderError {
493 #[inline]
495 pub(super) fn eof(e: encoding::UnexpectedEofError) -> Self {
496 Self(AmountDecoderErrorInner::UnexpectedEof(e))
497 }
498
499 #[inline]
501 pub(super) fn out_of_range(e: OutOfRangeError) -> Self {
502 Self(AmountDecoderErrorInner::OutOfRange(e))
503 }
504}
505
506#[cfg(feature = "encoding")]
507#[derive(Debug, Clone, PartialEq, Eq)]
508pub(super) enum AmountDecoderErrorInner {
509 UnexpectedEof(encoding::UnexpectedEofError),
511 OutOfRange(OutOfRangeError),
513}
514
515#[cfg(feature = "encoding")]
516impl From<Infallible> for AmountDecoderError {
517 fn from(never: Infallible) -> Self { match never {} }
518}
519
520#[cfg(feature = "encoding")]
521impl fmt::Display for AmountDecoderError {
522 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
523 use AmountDecoderErrorInner as E;
524
525 match self.0 {
526 E::UnexpectedEof(ref e) => write_err!(f, "decode error"; e),
527 E::OutOfRange(ref e) => write_err!(f, "decode error"; e),
528 }
529 }
530}
531
532#[cfg(feature = "encoding")]
533#[cfg(feature = "std")]
534impl std::error::Error for AmountDecoderError {
535 #[inline]
536 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
537 use AmountDecoderErrorInner as E;
538
539 match self.0 {
540 E::UnexpectedEof(ref e) => Some(e),
541 E::OutOfRange(ref e) => Some(e),
542 }
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 #[cfg(feature = "alloc")]
549 use alloc::string::ToString;
550 #[cfg(feature = "alloc")]
551 use core::str::FromStr;
552 #[cfg(feature = "std")]
553 use std::error::Error;
554
555 #[cfg(feature = "encoding")]
556 use encoding::{Decode as _, Decoder as _};
557
558 #[cfg(feature = "alloc")]
559 use super::{ParseAmountError, ParseAmountErrorInner, ParseErrorInner};
560 #[cfg(feature = "alloc")]
561 use crate::amount::{Amount, Denomination, ParseDenominationError, ParseError};
562
563 #[test]
564 #[cfg(feature = "alloc")]
565 #[allow(clippy::too_many_lines)] fn error_display_is_non_empty() {
567 macro_rules! assert_amount_err {
569 ($e:expr, $enum_arm:ident, $err_msg:expr) => {
570 assert!(!$e.to_string().is_empty());
571 let ParseError(ParseErrorInner::Amount(err)) = $e else { panic!($err_msg) };
572 assert!(!err.to_string().is_empty());
573 #[cfg(feature = "std")]
574 assert!(err.source().is_some());
575
576 let ParseAmountError(ParseAmountErrorInner::$enum_arm(err)) = err else {
577 panic!($err_msg)
578 };
579 assert!(!err.to_string().is_empty());
580 #[cfg(feature = "std")]
582 assert!(err.source().is_none());
583 };
584 }
585
586 let long_input = alloc::format!("{} BTC", "1".repeat(51));
589 let e = Amount::from_str(&long_input).unwrap_err();
590 assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
591 let long_input = alloc::format!("{} BTC", "1".repeat(52));
593 let e = Amount::from_str(&long_input).unwrap_err();
594 assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
595
596 let e = Amount::from_str("12x34 BTC").unwrap_err();
599 assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
600 let e = Amount::from_str("12.3.4 BTC").unwrap_err();
602 assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
603 let e = Amount::from_str("--1234 BTC").unwrap_err();
605 assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
606
607 let e = Amount::from_str("BTC").unwrap_err();
610 assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
611 let e = Amount::from_str("- BTC").unwrap_err();
613 assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
614
615 let e = Amount::from_str("21000001 BTC").unwrap_err();
618 assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
619 let e = Amount::from_str("-10 BTC").unwrap_err();
621 assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
622
623 let e = Amount::from_str("0.000000001 BTC").unwrap_err();
625 assert_amount_err!(e, TooPrecise, "error should be TooPreciseError");
626
627 let e = Amount::from_str("_123 BTC").unwrap_err();
630 assert_amount_err!(e, BadPosition, "error should be BadPositionError");
631 let e = Amount::from_str("-_123 BTC").unwrap_err();
633 assert_amount_err!(e, BadPosition, "error should be BadPositionError");
634 let e = Amount::from_str("1__23 BTC").unwrap_err();
636 assert_amount_err!(e, BadPosition, "error should be BadPositionError");
637
638 let e = Amount::from_str_in("invalid", Denomination::Bitcoin).unwrap_err();
640 assert!(!e.to_string().is_empty());
641 #[cfg(feature = "std")]
642 assert!(e.source().is_some());
643
644 let e = Denomination::from_str("XYZ").unwrap_err();
646 #[cfg(feature = "std")]
647 assert!(e.source().is_some());
648 let ParseDenominationError::Unknown(e) = e else {
649 panic!("error should be UnknownDenominationError")
650 };
651 assert!(!e.to_string().is_empty());
652 #[cfg(feature = "std")]
653 assert!(e.source().is_none());
654
655 let e = Denomination::from_str("MBTC").unwrap_err();
657 #[cfg(feature = "std")]
658 assert!(e.source().is_some());
659 let ParseDenominationError::PossiblyConfusing(e) = e else {
660 panic!("error should be PossiblyConfusingDenominationError")
661 };
662 assert!(!e.to_string().is_empty());
663 #[cfg(feature = "std")]
664 assert!(e.source().is_none());
665
666 let e = Denomination::from_str("UNKNOWN").unwrap_err();
669 assert!(!e.to_string().is_empty());
670 #[cfg(feature = "std")]
671 assert!(e.source().is_some());
672 let e = Denomination::from_str("MBTC").unwrap_err();
674 assert!(!e.to_string().is_empty());
675 #[cfg(feature = "std")]
676 assert!(e.source().is_some());
677
678 let e = "invalid BTC".parse::<Amount>().unwrap_err();
681 assert!(!e.to_string().is_empty());
682 #[cfg(feature = "std")]
683 assert!(e.source().is_some());
684 let e = "123 GBTC".parse::<Amount>().unwrap_err();
686 assert!(!e.to_string().is_empty());
687 #[cfg(feature = "std")]
688 assert!(e.source().is_some());
689 let e = "123".parse::<Amount>().unwrap_err();
691 assert!(!e.to_string().is_empty());
692 #[cfg(feature = "std")]
693 assert!(e.source().is_some());
694
695 #[cfg(feature = "encoding")]
696 {
697 let mut decoder = Amount::decoder();
700 let _ = decoder.push_bytes(&mut [0u8; 3].as_slice());
701 let e = decoder.end().unwrap_err();
702 assert!(!e.to_string().is_empty());
703 #[cfg(feature = "std")]
704 assert!(e.source().is_some());
705
706 let mut decoder = Amount::decoder();
708 let _ =
709 decoder.push_bytes(&mut (21_000_001 * 100_000_000_u64).to_le_bytes().as_slice());
710 let e = decoder.end().unwrap_err();
711 assert!(!e.to_string().is_empty());
712 #[cfg(feature = "std")]
713 assert!(e.source().is_some());
714 }
715 }
716}