bitcoin_units/locktime/absolute/
error.rs1use core::convert::Infallible;
6use core::fmt;
7
8use internals::error::InputString;
9#[cfg(feature = "encoding")]
10use internals::write_err;
11
12use super::{Height, MedianTimePast, LOCK_TIME_THRESHOLD};
13use crate::parse_int::{ParseIntError, PrefixedHexError, UnprefixedHexError};
14
15#[cfg(feature = "encoding")]
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct LockTimeDecoderError(pub(super) encoding::UnexpectedEofError);
19
20#[cfg(feature = "encoding")]
21impl From<Infallible> for LockTimeDecoderError {
22 fn from(never: Infallible) -> Self { match never {} }
23}
24
25#[cfg(feature = "encoding")]
26impl fmt::Display for LockTimeDecoderError {
27 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
28 write_err!(f, "lock time decoder error"; self.0)
29 }
30}
31
32#[cfg(feature = "encoding")]
33#[cfg(feature = "std")]
34impl std::error::Error for LockTimeDecoderError {
35 #[inline]
36 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct IncompatibleHeightError {
42 pub(super) lock: MedianTimePast,
44 pub(super) incompatible: Height,
46}
47
48impl IncompatibleHeightError {
49 #[inline]
51 pub fn lock(&self) -> MedianTimePast { self.lock }
52
53 #[inline]
55 pub fn incompatible(&self) -> Height { self.incompatible }
56}
57
58impl From<Infallible> for IncompatibleHeightError {
59 fn from(never: Infallible) -> Self { match never {} }
60}
61
62impl fmt::Display for IncompatibleHeightError {
63 #[inline]
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 write!(
66 f,
67 "tried to satisfy a lock-by-time lock {} with height: {}",
68 self.lock, self.incompatible
69 )
70 }
71}
72
73#[cfg(feature = "std")]
74impl std::error::Error for IncompatibleHeightError {
75 #[inline]
76 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct IncompatibleTimeError {
82 pub(super) lock: Height,
84 pub(super) incompatible: MedianTimePast,
86}
87
88impl IncompatibleTimeError {
89 #[inline]
91 pub fn lock(&self) -> Height { self.lock }
92
93 #[inline]
95 pub fn incompatible(&self) -> MedianTimePast { self.incompatible }
96}
97
98impl From<Infallible> for IncompatibleTimeError {
99 fn from(never: Infallible) -> Self { match never {} }
100}
101
102impl fmt::Display for IncompatibleTimeError {
103 #[inline]
104 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 write!(
106 f,
107 "tried to satisfy a lock-by-height lock {} with MTP: {}",
108 self.lock, self.incompatible
109 )
110 }
111}
112
113#[cfg(feature = "std")]
114impl std::error::Error for IncompatibleTimeError {
115 #[inline]
116 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
117}
118
119#[derive(Debug, Clone, Eq, PartialEq)]
121pub struct ParseHeightError(ParseError);
122
123impl From<Infallible> for ParseHeightError {
124 fn from(never: Infallible) -> Self { match never {} }
125}
126
127impl fmt::Display for ParseHeightError {
128 #[inline]
129 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130 self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
131 }
132}
133
134#[cfg(feature = "std")]
135impl std::error::Error for ParseHeightError {
136 #[inline]
138 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
139}
140
141impl From<ParseError> for ParseHeightError {
142 #[inline]
143 fn from(value: ParseError) -> Self { Self(value) }
144}
145
146#[derive(Debug, Clone, Eq, PartialEq)]
148pub struct ParseTimeError(ParseError);
149
150impl From<Infallible> for ParseTimeError {
151 fn from(never: Infallible) -> Self { match never {} }
152}
153
154impl fmt::Display for ParseTimeError {
155 #[inline]
156 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157 self.0.display(f, "block time", LOCK_TIME_THRESHOLD, u32::MAX)
158 }
159}
160
161#[cfg(feature = "std")]
162impl std::error::Error for ParseTimeError {
163 #[inline]
165 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
166}
167
168impl From<ParseError> for ParseTimeError {
169 #[inline]
170 fn from(value: ParseError) -> Self { Self(value) }
171}
172
173#[derive(Debug, Clone, Eq, PartialEq)]
175pub(super) enum ParseError {
176 PrefixedHex(PrefixedHexError),
178 UnprefixedHex(UnprefixedHexError),
180 ParseInt(ParseIntError),
182 Conversion(i64),
185}
186
187impl ParseError {
188 #[inline]
189 pub(super) fn invalid_int<S: Into<InputString>>(
190 s: S,
191 ) -> impl FnOnce(core::num::ParseIntError) -> Self {
192 move |source| {
193 Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true, source })
194 }
195 }
196
197 pub(super) fn display(
198 &self,
199 f: &mut fmt::Formatter<'_>,
200 subject: &str,
201 lower_bound: u32,
202 upper_bound: u32,
203 ) -> fmt::Result {
204 use core::num::IntErrorKind;
205
206 match self {
207 Self::PrefixedHex(ref err) => fmt::Display::fmt(err, f),
208 Self::UnprefixedHex(ref err) => fmt::Display::fmt(err, f),
209 Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
210 if *source.kind() == IntErrorKind::PosOverflow =>
211 {
212 write!(
214 f,
215 "{} ({} is above limit {})",
216 input.display_cannot_parse("absolute Height/MedianTimePast"),
217 subject,
218 upper_bound
219 )
220 }
221 Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
222 if *source.kind() == IntErrorKind::NegOverflow =>
223 {
224 write!(
226 f,
227 "{} ({} is below limit {})",
228 input.display_cannot_parse("absolute Height/MedianTimePast"),
229 subject,
230 lower_bound
231 )
232 }
233 Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => {
234 write!(
235 f,
236 "{} ({})",
237 input.display_cannot_parse("absolute Height/MedianTimePast"),
238 subject
239 )
240 }
241 Self::Conversion(value) if *value < i64::from(lower_bound) => {
242 write!(f, "{} {} is below limit {}", subject, value, lower_bound)
243 }
244 Self::Conversion(value) => {
245 write!(f, "{} {} is above limit {}", subject, value, upper_bound)
246 }
247 }
248 }
249
250 #[inline]
252 #[cfg(feature = "std")]
253 pub(super) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
254 use core::num::IntErrorKind;
255
256 match self {
257 Self::PrefixedHex(ref err) => Some(err),
258 Self::UnprefixedHex(ref err) => Some(err),
259 Self::ParseInt(ParseIntError { source, .. })
260 if *source.kind() == IntErrorKind::PosOverflow =>
261 None,
262 Self::ParseInt(ParseIntError { source, .. })
263 if *source.kind() == IntErrorKind::NegOverflow =>
264 None,
265 Self::ParseInt(ParseIntError { source, .. }) => Some(source),
266 Self::Conversion(_) => None,
267 }
268 }
269}
270
271impl From<Infallible> for ParseError {
272 fn from(never: Infallible) -> Self { match never {} }
273}
274
275impl From<ConversionError> for ParseError {
276 #[inline]
277 fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
278}
279
280impl From<PrefixedHexError> for ParseError {
281 #[inline]
282 fn from(value: PrefixedHexError) -> Self { Self::PrefixedHex(value) }
283}
284
285impl From<UnprefixedHexError> for ParseError {
286 #[inline]
287 fn from(value: UnprefixedHexError) -> Self { Self::UnprefixedHex(value) }
288}
289
290#[derive(Debug, Clone, PartialEq, Eq)]
292#[non_exhaustive]
293pub struct ConversionError {
294 unit: LockTimeUnit,
296 input: u32,
298}
299
300impl ConversionError {
301 #[inline]
303 pub(super) const fn invalid_height(n: u32) -> Self {
304 Self { unit: LockTimeUnit::Blocks, input: n }
305 }
306
307 #[inline]
309 pub(super) const fn invalid_time(n: u32) -> Self {
310 Self { unit: LockTimeUnit::Seconds, input: n }
311 }
312}
313
314impl From<Infallible> for ConversionError {
315 fn from(never: Infallible) -> Self { match never {} }
316}
317
318impl fmt::Display for ConversionError {
319 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
320 write!(f, "invalid lock time value {}, {}", self.input, self.unit)
321 }
322}
323
324#[cfg(feature = "std")]
325impl std::error::Error for ConversionError {
326 #[inline]
327 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
328}
329
330#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
332enum LockTimeUnit {
333 Blocks,
335 Seconds,
337}
338
339impl fmt::Display for LockTimeUnit {
340 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
341 match *self {
342 Self::Blocks =>
343 write!(f, "expected lock-by-height (must be < {})", LOCK_TIME_THRESHOLD),
344 Self::Seconds =>
345 write!(f, "expected lock-by-time (must be >= {})", LOCK_TIME_THRESHOLD),
346 }
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 #[cfg(feature = "alloc")]
353 use alloc::{format, string::ToString};
354 #[cfg(feature = "alloc")]
355 use core::str::FromStr;
356 #[cfg(feature = "std")]
357 use std::error::Error;
358
359 #[cfg(feature = "alloc")]
360 #[cfg(feature = "encoding")]
361 use encoding::{Decode as _, Decoder as _};
362
363 #[cfg(feature = "alloc")]
364 use super::LockTimeUnit;
365 #[cfg(feature = "alloc")]
366 use crate::{
367 locktime::absolute::{Height, LockTime, MedianTimePast},
368 BlockHeight,
369 };
370
371 #[test]
372 #[cfg(feature = "alloc")]
373 fn locktime_unit_display() {
374 let blocks = LockTimeUnit::Blocks;
375 let seconds = LockTimeUnit::Seconds;
376
377 assert_eq!(format!("{}", blocks), "expected lock-by-height (must be < 500000000)");
378 assert_eq!(format!("{}", seconds), "expected lock-by-time (must be >= 500000000)");
379 }
380
381 #[test]
382 #[cfg(feature = "alloc")]
383 fn error_display_is_non_empty() {
384 let too_big = BlockHeight::from_u32(u32::MAX);
386 let e = Height::try_from(too_big).unwrap_err();
387 assert!(!e.to_string().is_empty());
388 #[cfg(feature = "std")]
389 assert!(e.source().is_none());
390
391 let time_lock = LockTime::from_mtp(MedianTimePast::MIN.to_u32()).unwrap();
393 let e = time_lock.is_satisfied_by_height(Height::MIN).unwrap_err();
394 assert!(!e.to_string().is_empty());
395 #[cfg(feature = "std")]
396 assert!(e.source().is_none());
397
398 let height_lock = LockTime::from_height(Height::MIN.to_u32()).unwrap();
400 let e = height_lock.is_satisfied_by_time(MedianTimePast::MIN).unwrap_err();
401 assert!(!e.to_string().is_empty());
402 #[cfg(feature = "std")]
403 assert!(e.source().is_none());
404
405 let e = Height::from_str("invalid").unwrap_err();
407 assert!(!e.to_string().is_empty());
408 #[cfg(feature = "std")]
409 assert!(e.source().is_some());
410
411 let e = MedianTimePast::from_str("invalid").unwrap_err();
413 assert!(!e.to_string().is_empty());
414 #[cfg(feature = "std")]
415 assert!(e.source().is_some());
416
417 #[cfg(feature = "encoding")]
418 {
419 let mut decoder = LockTime::decoder();
421 let _ = decoder.push_bytes(&mut [0u8; 3].as_slice());
422 let e = decoder.end().unwrap_err();
423 assert!(!e.to_string().is_empty());
424 #[cfg(feature = "std")]
425 assert!(e.source().is_some());
426 }
427 }
428}