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;
12
13/// Error returned when parsing an amount with denomination fails.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct ParseError(pub(crate) ParseErrorInner);
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub(crate) enum ParseErrorInner {
19    /// Invalid amount.
20    Amount(ParseAmountError),
21    /// Invalid denomination.
22    Denomination(ParseDenominationError),
23    /// The denomination was not identified.
24    MissingDenomination(MissingDenominationError),
25}
26
27impl From<Infallible> for ParseError {
28    fn from(never: Infallible) -> Self { match never {} }
29}
30
31impl From<Infallible> for ParseErrorInner {
32    fn from(never: Infallible) -> Self { match never {} }
33}
34
35impl From<ParseAmountError> for ParseError {
36    fn from(e: ParseAmountError) -> Self { Self(ParseErrorInner::Amount(e)) }
37}
38
39impl From<ParseDenominationError> for ParseError {
40    fn from(e: ParseDenominationError) -> Self { Self(ParseErrorInner::Denomination(e)) }
41}
42
43impl From<OutOfRangeError> for ParseError {
44    fn from(e: OutOfRangeError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
45}
46
47impl From<TooPreciseError> for ParseError {
48    fn from(e: TooPreciseError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
49}
50
51impl From<MissingDigitsError> for ParseError {
52    fn from(e: MissingDigitsError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
53}
54
55impl From<InputTooLargeError> for ParseError {
56    fn from(e: InputTooLargeError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
57}
58
59impl From<InvalidCharacterError> for ParseError {
60    fn from(e: InvalidCharacterError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
61}
62
63impl From<BadPositionError> for ParseError {
64    fn from(e: BadPositionError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
65}
66
67impl fmt::Display for ParseError {
68    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
69        match self.0 {
70            ParseErrorInner::Amount(ref e) => write_err!(f, "invalid amount"; e),
71            ParseErrorInner::Denomination(ref e) => write_err!(f, "invalid denomination"; e),
72            // We consider this to not be a source because it currently doesn't contain useful info.
73            ParseErrorInner::MissingDenomination(_) =>
74                f.write_str("the input doesn't contain a denomination"),
75        }
76    }
77}
78
79#[cfg(feature = "std")]
80impl std::error::Error for ParseError {
81    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
82        match self.0 {
83            ParseErrorInner::Amount(ref e) => Some(e),
84            ParseErrorInner::Denomination(ref e) => Some(e),
85            // We consider this to not be a source because it currently doesn't contain useful info.
86            ParseErrorInner::MissingDenomination(_) => None,
87        }
88    }
89}
90
91/// Error returned when parsing an amount fails.
92#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct ParseAmountError(pub(crate) ParseAmountErrorInner);
94
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub(crate) enum ParseAmountErrorInner {
97    /// The amount is too big or too small.
98    OutOfRange(OutOfRangeError),
99    /// Amount has higher precision than supported by the type.
100    TooPrecise(TooPreciseError),
101    /// A digit was expected but not found.
102    MissingDigits(MissingDigitsError),
103    /// Input string was too large.
104    InputTooLarge(InputTooLargeError),
105    /// Invalid character in input.
106    InvalidCharacter(InvalidCharacterError),
107    /// A valid character is in an invalid position.
108    BadPosition(BadPositionError),
109}
110
111impl From<TooPreciseError> for ParseAmountError {
112    fn from(value: TooPreciseError) -> Self { Self(ParseAmountErrorInner::TooPrecise(value)) }
113}
114
115impl From<MissingDigitsError> for ParseAmountError {
116    fn from(value: MissingDigitsError) -> Self { Self(ParseAmountErrorInner::MissingDigits(value)) }
117}
118
119impl From<InputTooLargeError> for ParseAmountError {
120    fn from(value: InputTooLargeError) -> Self { Self(ParseAmountErrorInner::InputTooLarge(value)) }
121}
122
123impl From<InvalidCharacterError> for ParseAmountError {
124    fn from(value: InvalidCharacterError) -> Self {
125        Self(ParseAmountErrorInner::InvalidCharacter(value))
126    }
127}
128
129impl From<BadPositionError> for ParseAmountError {
130    fn from(value: BadPositionError) -> Self { Self(ParseAmountErrorInner::BadPosition(value)) }
131}
132
133impl From<Infallible> for ParseAmountError {
134    fn from(never: Infallible) -> Self { match never {} }
135}
136
137impl From<Infallible> for ParseAmountErrorInner {
138    fn from(never: Infallible) -> Self { match never {} }
139}
140
141impl fmt::Display for ParseAmountError {
142    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143        use ParseAmountErrorInner as E;
144
145        match self.0 {
146            E::OutOfRange(ref error) => write_err!(f, "amount out of range"; error),
147            E::TooPrecise(ref error) => write_err!(f, "amount has a too high precision"; error),
148            E::MissingDigits(ref error) => write_err!(f, "the input has too few digits"; error),
149            E::InputTooLarge(ref error) => write_err!(f, "the input is too large"; error),
150            E::InvalidCharacter(ref error) => {
151                write_err!(f, "invalid character in the input"; error)
152            }
153            E::BadPosition(ref error) => write_err!(f, "valid character in bad position"; error),
154        }
155    }
156}
157
158#[cfg(feature = "std")]
159impl std::error::Error for ParseAmountError {
160    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
161        use ParseAmountErrorInner as E;
162
163        match self.0 {
164            E::TooPrecise(ref error) => Some(error),
165            E::InputTooLarge(ref error) => Some(error),
166            E::OutOfRange(ref error) => Some(error),
167            E::MissingDigits(ref error) => Some(error),
168            E::InvalidCharacter(ref error) => Some(error),
169            E::BadPosition(ref error) => Some(error),
170        }
171    }
172}
173
174/// Error returned when a parsed amount is too big or too small.
175#[derive(Debug, Copy, Clone, Eq, PartialEq)]
176pub struct OutOfRangeError {
177    pub(super) is_signed: bool,
178    pub(super) is_greater_than_max: bool,
179}
180
181impl OutOfRangeError {
182    /// Returns the minimum and maximum allowed values for the type that was parsed.
183    ///
184    /// This can be used to give a hint to the user which values are allowed.
185    pub fn valid_range(self) -> (i64, u64) {
186        match self.is_signed {
187            true => (i64::MIN, i64::MAX as u64),
188            false => (0, u64::MAX),
189        }
190    }
191
192    /// Returns true if the input value was larger than the maximum allowed value.
193    pub fn is_above_max(self) -> bool { self.is_greater_than_max }
194
195    /// Returns true if the input value was smaller than the minimum allowed value.
196    pub fn is_below_min(self) -> bool { !self.is_greater_than_max }
197
198    #[cfg(test)]
199    pub(crate) fn too_big(is_signed: bool) -> Self { Self { is_signed, is_greater_than_max: true } }
200
201    #[cfg(test)]
202    pub(crate) fn too_small() -> Self {
203        Self {
204            // implied - negative() is used for the other
205            is_signed: true,
206            is_greater_than_max: false,
207        }
208    }
209
210    pub(crate) fn negative() -> Self {
211        Self {
212            // implied - too_small() is used for the other
213            is_signed: false,
214            is_greater_than_max: false,
215        }
216    }
217}
218
219impl fmt::Display for OutOfRangeError {
220    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
221        if self.is_greater_than_max {
222            write!(f, "the amount is greater than {}", self.valid_range().1)
223        } else {
224            write!(f, "the amount is less than {}", self.valid_range().0)
225        }
226    }
227}
228
229#[cfg(feature = "std")]
230impl std::error::Error for OutOfRangeError {}
231
232impl From<OutOfRangeError> for ParseAmountError {
233    fn from(value: OutOfRangeError) -> Self { Self(ParseAmountErrorInner::OutOfRange(value)) }
234}
235
236/// Error returned when the input string has higher precision than satoshis.
237#[derive(Debug, Clone, Eq, PartialEq)]
238pub struct TooPreciseError {
239    pub(super) position: usize,
240}
241
242impl fmt::Display for TooPreciseError {
243    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244        match self.position {
245            0 => f.write_str("the amount is less than 1 satoshi but it's not zero"),
246            pos => write!(
247                f,
248                "the digits starting from position {} represent a sub-satoshi amount",
249                pos
250            ),
251        }
252    }
253}
254
255#[cfg(feature = "std")]
256impl std::error::Error for TooPreciseError {}
257
258/// Error returned when the input string is too large.
259#[derive(Debug, Clone, Eq, PartialEq)]
260pub struct InputTooLargeError {
261    pub(super) len: usize,
262}
263
264impl fmt::Display for InputTooLargeError {
265    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
266        match self.len - INPUT_STRING_LEN_LIMIT {
267            1 => write!(
268                f,
269                "the input is one character longer than the maximum allowed length ({})",
270                INPUT_STRING_LEN_LIMIT
271            ),
272            n => write!(
273                f,
274                "the input is {} characters longer than the maximum allowed length ({})",
275                n, INPUT_STRING_LEN_LIMIT
276            ),
277        }
278    }
279}
280
281#[cfg(feature = "std")]
282impl std::error::Error for InputTooLargeError {}
283
284/// Error returned when digits were expected in the input but there were none.
285///
286/// In particular, this is currently returned when the string is empty or only contains the minus sign.
287#[derive(Debug, Clone, Eq, PartialEq)]
288pub struct MissingDigitsError {
289    pub(super) kind: MissingDigitsKind,
290}
291
292impl fmt::Display for MissingDigitsError {
293    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
294        match self.kind {
295            MissingDigitsKind::Empty => f.write_str("the input is empty"),
296            MissingDigitsKind::OnlyMinusSign =>
297                f.write_str("there are no digits following the minus (-) sign"),
298        }
299    }
300}
301
302#[cfg(feature = "std")]
303impl std::error::Error for MissingDigitsError {}
304
305#[derive(Debug, Clone, Eq, PartialEq)]
306pub(super) enum MissingDigitsKind {
307    Empty,
308    OnlyMinusSign,
309}
310
311/// Error returned when the input contains an invalid character.
312#[derive(Debug, Clone, PartialEq, Eq)]
313pub struct InvalidCharacterError {
314    pub(super) invalid_char: char,
315    pub(super) position: usize,
316}
317
318impl fmt::Display for InvalidCharacterError {
319    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
320        match self.invalid_char {
321            '.' => f.write_str("there is more than one decimal separator (dot) in the input"),
322            '-' => f.write_str("there is more than one minus sign (-) in the input"),
323            c => write!(
324                f,
325                "the character '{}' at position {} is not a valid digit",
326                c, self.position
327            ),
328        }
329    }
330}
331
332#[cfg(feature = "std")]
333impl std::error::Error for InvalidCharacterError {}
334
335/// Error returned when a valid character (e.g. '_') is in an invalid/bad position.
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct BadPositionError {
338    pub(super) char: char,
339    pub(super) position: usize,
340}
341
342impl fmt::Display for BadPositionError {
343    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
344        match self.char {
345            '_' => match self.position {
346                0 => f.write_str("the input amount is prefixed with an underscore (_)"),
347                // Position 1 can only occur when the amount begins with a minus (-), since an underscore
348                // at position 0 will cause an error with position 0, and this error marks the position of
349                // the second underscore.
350                1 => f.write_str("the input amount is prefixed with an underscore (_)"),
351                _ => f.write_str("there are consecutive underscores (_) in the input"),
352            },
353            c => write!(f, "The character '{}' is at a bad position: {}", c, self.position),
354        }
355    }
356}
357
358#[cfg(feature = "std")]
359impl std::error::Error for BadPositionError {}
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    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
387        match *self {
388            Self::Unknown(_) | Self::PossiblyConfusing(_) => None,
389        }
390    }
391}
392
393/// Error returned when the denomination is empty.
394#[derive(Debug, Clone, PartialEq, Eq)]
395#[non_exhaustive]
396pub struct MissingDenominationError;
397
398/// Error returned when parsing an unknown denomination.
399#[derive(Debug, Clone, PartialEq, Eq)]
400#[non_exhaustive]
401pub struct UnknownDenominationError(pub(super) InputString);
402
403impl fmt::Display for UnknownDenominationError {
404    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
405        self.0.unknown_variant("bitcoin denomination", f)
406    }
407}
408
409#[cfg(feature = "std")]
410impl std::error::Error for UnknownDenominationError {
411    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
412}
413
414/// Error returned when parsing a possibly confusing denomination.
415#[derive(Debug, Clone, PartialEq, Eq)]
416#[non_exhaustive]
417pub struct PossiblyConfusingDenominationError(pub(super) InputString);
418
419impl fmt::Display for PossiblyConfusingDenominationError {
420    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
421        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"))
422    }
423}
424
425#[cfg(feature = "std")]
426impl std::error::Error for PossiblyConfusingDenominationError {
427    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
428}
429
430/// An error consensus decoding an `Amount`.
431#[cfg(feature = "encoding")]
432#[derive(Debug, Clone, PartialEq, Eq)]
433pub struct AmountDecoderError(pub(super) AmountDecoderErrorInner);
434
435#[cfg(feature = "encoding")]
436impl AmountDecoderError {
437    /// Constructs an EOF error.
438    pub(super) fn eof(e: encoding::UnexpectedEofError) -> Self {
439        Self(AmountDecoderErrorInner::UnexpectedEof(e))
440    }
441
442    /// Constructs an out of range (`Amount::from_sat`) error.
443    pub(super) fn out_of_range(e: OutOfRangeError) -> Self {
444        Self(AmountDecoderErrorInner::OutOfRange(e))
445    }
446}
447
448#[cfg(feature = "encoding")]
449#[derive(Debug, Clone, PartialEq, Eq)]
450pub(super) enum AmountDecoderErrorInner {
451    /// Not enough bytes given to decoder.
452    UnexpectedEof(encoding::UnexpectedEofError),
453    /// Decoded amount is too big.
454    OutOfRange(OutOfRangeError),
455}
456
457#[cfg(feature = "encoding")]
458impl From<Infallible> for AmountDecoderError {
459    fn from(never: Infallible) -> Self { match never {} }
460}
461
462#[cfg(feature = "encoding")]
463impl fmt::Display for AmountDecoderError {
464    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
465        use AmountDecoderErrorInner as E;
466
467        match self.0 {
468            E::UnexpectedEof(ref e) => write_err!(f, "decode error"; e),
469            E::OutOfRange(ref e) => write_err!(f, "decode error"; e),
470        }
471    }
472}
473
474#[cfg(all(feature = "std", feature = "encoding"))]
475impl std::error::Error for AmountDecoderError {
476    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
477        use AmountDecoderErrorInner as E;
478
479        match self.0 {
480            E::UnexpectedEof(ref e) => Some(e),
481            E::OutOfRange(ref e) => Some(e),
482        }
483    }
484}