Skip to main content

bitcoin_units/amount/
error.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Error types for bitcoin amounts.
4
5use 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/// Error returned when parsing an amount with denomination fails.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ParseError(pub(crate) ParseErrorInner);
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub(crate) enum ParseErrorInner {
20    /// Invalid amount.
21    Amount(ParseAmountError),
22    /// Invalid denomination.
23    Denomination(ParseDenominationError),
24    /// The denomination was not identified.
25    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            // We consider this to not be a source because it currently doesn't contain useful info.
42            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            // We consider this to not be a source because it currently doesn't contain useful info.
54            ParseErrorInner::MissingDenomination(ref e) => Some(e),
55        }
56    }
57}
58
59/// Error returned when parsing an amount fails.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct ParseAmountError(pub(crate) ParseAmountErrorInner);
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub(crate) enum ParseAmountErrorInner {
65    /// The amount is too big or too small.
66    OutOfRange(OutOfRangeError),
67    /// Amount has higher precision than supported by the type.
68    TooPrecise(TooPreciseError),
69    /// A digit was expected but not found.
70    MissingDigits(MissingDigitsError),
71    /// Input string was too large.
72    InputTooLarge(InputTooLargeError),
73    /// Invalid character in input.
74    InvalidCharacter(InvalidCharacterError),
75    /// A valid character is in an invalid position.
76    BadPosition(BadPositionError),
77    /// An error parsing a prefixed hex amount.
78    PrefixedHex(PrefixedHexError),
79    /// An error parsing an unprefixed hex amount.
80    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/// Error returned when a parsed amount is too big or too small.
130#[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    /// Returns the minimum and maximum allowed values for the type that was parsed.
138    ///
139    /// This can be used to give a hint to the user which values are allowed.
140    #[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    /// Returns true if the input value was larger than the maximum allowed value.
149    #[inline]
150    pub fn is_above_max(self) -> bool { self.is_greater_than_max }
151
152    /// Returns true if the input value was smaller than the minimum allowed value.
153    #[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            // implied - negative() is used for the other
165            is_signed: true,
166            is_greater_than_max: false,
167        }
168    }
169
170    #[inline]
171    pub(crate) fn negative() -> Self {
172        Self {
173            // implied - too_small() is used for the other
174            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/// Error returned when the input string has higher precision than satoshis.
201#[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/// Error returned when the input string is too large.
230#[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/// Error returned when digits were expected in the input but there were none.
263///
264/// In particular, this is currently returned when the string is empty or only contains the minus sign.
265#[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/// Error returned when the input contains an invalid character.
298#[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/// Error returned when a valid character (e.g. '_') is in an invalid/bad position.
329#[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                // Position 1 can only occur when the amount begins with a minus (-), since an underscore
345                // at position 0 will cause an error with position 0, and this error marks the position of
346                // the second underscore.
347                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/// An error during amount parsing.
362#[derive(Debug, Clone, PartialEq, Eq)]
363#[non_exhaustive]
364pub enum ParseDenominationError {
365    /// The denomination was unknown.
366    Unknown(UnknownDenominationError),
367    /// The denomination has multiple possible interpretations.
368    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/// Error returned when the denomination is empty.
396#[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/// Error returned when parsing an unknown denomination.
417#[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/// Error returned when parsing a possibly confusing denomination.
439#[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/// An error consensus decoding an `Amount`.
460#[cfg(feature = "encoding")]
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct AmountDecoderError(pub(super) AmountDecoderErrorInner);
463
464#[cfg(feature = "encoding")]
465impl AmountDecoderError {
466    /// Constructs an EOF error.
467    #[inline]
468    pub(super) fn eof(e: encoding::UnexpectedEofError) -> Self {
469        Self(AmountDecoderErrorInner::UnexpectedEof(e))
470    }
471
472    /// Constructs an out of range (`Amount::from_sat`) error.
473    #[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    /// Not enough bytes given to decoder.
483    UnexpectedEof(encoding::UnexpectedEofError),
484    /// Decoded amount is too big.
485    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)] // Test could be refactored ...
539    fn error_display_is_non_empty() {
540        // A helper macro to break out a ParseAmountErrorInner type and assert display down the chain.
541        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                // The inner-most types have no source
554                #[cfg(feature = "std")]
555                assert!(err.source().is_none());
556            };
557        }
558
559        // InputTooLargeError
560        // one char too long
561        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        // n chars too long
565        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        // InvalidCharacterError
570        // invalid character in amount string
571        let e = Amount::from_str("12x34 BTC").unwrap_err();
572        assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
573        // too many decimal points
574        let e = Amount::from_str("12.3.4 BTC").unwrap_err();
575        assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
576        // too many minus signs
577        let e = Amount::from_str("--1234 BTC").unwrap_err();
578        assert_amount_err!(e, InvalidCharacter, "error should be InvalidCharacterError");
579
580        // MissingDigitsError
581        // no numeric value
582        let e = Amount::from_str("BTC").unwrap_err();
583        assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
584        // Only a minus sign
585        let e = Amount::from_str("- BTC").unwrap_err();
586        assert_amount_err!(e, MissingDigits, "error should be MissingDigitsError");
587
588        // OutOfRangeError
589        // amount too large
590        let e = Amount::from_str("21000001 BTC").unwrap_err();
591        assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
592        // less than 0
593        let e = Amount::from_str("-10 BTC").unwrap_err();
594        assert_amount_err!(e, OutOfRange, "error should be OutOfRangeError");
595
596        // TooPreciseError - sub-satoshi precision
597        let e = Amount::from_str("0.000000001 BTC").unwrap_err();
598        assert_amount_err!(e, TooPrecise, "error should be TooPreciseError");
599
600        // BadPositionError
601        // underscore in bad position
602        let e = Amount::from_str("_123 BTC").unwrap_err();
603        assert_amount_err!(e, BadPosition, "error should be BadPositionError");
604        // underscore in bad position (negative)
605        let e = Amount::from_str("-_123 BTC").unwrap_err();
606        assert_amount_err!(e, BadPosition, "error should be BadPositionError");
607        // consecutive underscores
608        let e = Amount::from_str("1__23 BTC").unwrap_err();
609        assert_amount_err!(e, BadPosition, "error should be BadPositionError");
610
611        // ParseAmountError - parent type for the errors above
612        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        // UnknownDenominationError - amount with unknown denomination string
618        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        // PossiblyConfusingDenominationError - confusing denomination like "MBTC"
629        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        // ParseDenominationError - parent error for the above *DenominationError types
640        // Unknown type
641        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        // Possibly confusing type
646        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        // ParseError - parent type for all of the above
652        // Amount type
653        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        // bad denomination type
658        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        // missing denomination type
663        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            // AmountDecoderError
671            // EOF type
672            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            // Out of range type
680            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}