Skip to content

Commit d20e586

Browse files
committed
Introduce error modules to locktime
In order to de-clutter the public API introduce public `error` modules to both locktime modules and move all error types there (in `units` crate). As is now policy, re-export error types that appear in directly the public API but don't doc inline them. This is a bit confusing because _all_ the error types except `ParseError` appear directly in the API.
1 parent fe62307 commit d20e586

File tree

7 files changed

+307
-261
lines changed

7 files changed

+307
-261
lines changed

primitives/src/locktime/absolute.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ use crate::absolute;
1616

1717
#[rustfmt::skip] // Keep public re-exports separate.
1818
#[doc(inline)]
19-
pub use units::locktime::absolute::{ConversionError, Height, ParseHeightError, ParseTimeError, MedianTimePast, LOCK_TIME_THRESHOLD};
19+
pub use units::locktime::absolute::{
20+
Height, MedianTimePast, LOCK_TIME_THRESHOLD
21+
};
22+
#[doc(inline)]
23+
pub use units::locktime::absolute::error::{ConversionError, ParseHeightError, ParseTimeError};
2024

2125
#[deprecated(since = "TBD", note = "use `MedianTimePast` instead")]
2226
#[doc(hidden)]

primitives/src/locktime/relative.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use crate::relative;
1919

2020
#[rustfmt::skip] // Keep public re-exports separate.
2121
#[doc(inline)]
22-
pub use units::locktime::relative::{NumberOfBlocks, NumberOf512Seconds, TimeOverflowError, InvalidHeightError, InvalidTimeError};
22+
pub use units::locktime::relative::{NumberOfBlocks, NumberOf512Seconds};
23+
#[doc(no_inline)]
24+
pub use units::locktime::relative::error::{TimeOverflowError, InvalidHeightError, InvalidTimeError};
2325

2426
#[deprecated(since = "TBD", note = "use `NumberOfBlocks` instead")]
2527
#[doc(hidden)]
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! Error types for the absolute locktime module.
4+
5+
use core::convert::Infallible;
6+
use core::fmt;
7+
8+
use internals::error::InputString;
9+
10+
use super::LOCK_TIME_THRESHOLD;
11+
use crate::parse::ParseIntError;
12+
13+
/// Error returned when parsing block height fails.
14+
#[derive(Debug, Clone, Eq, PartialEq)]
15+
pub struct ParseHeightError(ParseError);
16+
17+
impl fmt::Display for ParseHeightError {
18+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
19+
self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
20+
}
21+
}
22+
23+
#[cfg(feature = "std")]
24+
impl std::error::Error for ParseHeightError {
25+
// To be consistent with `write_err` we need to **not** return source if overflow occurred
26+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
27+
}
28+
29+
impl From<ParseError> for ParseHeightError {
30+
fn from(value: ParseError) -> Self { Self(value) }
31+
}
32+
33+
/// Error returned when parsing block time fails.
34+
#[derive(Debug, Clone, Eq, PartialEq)]
35+
pub struct ParseTimeError(ParseError);
36+
37+
impl fmt::Display for ParseTimeError {
38+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39+
self.0.display(f, "block time", LOCK_TIME_THRESHOLD, u32::MAX)
40+
}
41+
}
42+
43+
#[cfg(feature = "std")]
44+
impl std::error::Error for ParseTimeError {
45+
// To be consistent with `write_err` we need to **not** return source if overflow occurred
46+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
47+
}
48+
49+
impl From<ParseError> for ParseTimeError {
50+
fn from(value: ParseError) -> Self { Self(value) }
51+
}
52+
53+
/// Internal - common representation for height and time.
54+
#[derive(Debug, Clone, Eq, PartialEq)]
55+
pub(super) enum ParseError {
56+
ParseInt(ParseIntError),
57+
// unit implied by outer type
58+
// we use i64 to have nicer messages for negative values
59+
Conversion(i64),
60+
}
61+
62+
impl From<Infallible> for ParseError {
63+
fn from(never: Infallible) -> Self { match never {} }
64+
}
65+
66+
impl ParseError {
67+
pub(super) fn invalid_int<S: Into<InputString>>(
68+
s: S,
69+
) -> impl FnOnce(core::num::ParseIntError) -> Self {
70+
move |source| {
71+
Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true, source })
72+
}
73+
}
74+
75+
pub(super) fn display(
76+
&self,
77+
f: &mut fmt::Formatter<'_>,
78+
subject: &str,
79+
lower_bound: u32,
80+
upper_bound: u32,
81+
) -> fmt::Result {
82+
use core::num::IntErrorKind;
83+
84+
match self {
85+
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
86+
if *source.kind() == IntErrorKind::PosOverflow =>
87+
{
88+
// Outputs "failed to parse <input_string> as absolute Height/MedianTimePast (<subject> is above limit <upper_bound>)"
89+
write!(
90+
f,
91+
"{} ({} is above limit {})",
92+
input.display_cannot_parse("absolute Height/MedianTimePast"),
93+
subject,
94+
upper_bound
95+
)
96+
}
97+
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
98+
if *source.kind() == IntErrorKind::NegOverflow =>
99+
{
100+
// Outputs "failed to parse <input_string> as absolute Height/MedianTimePast (<subject> is below limit <lower_bound>)"
101+
write!(
102+
f,
103+
"{} ({} is below limit {})",
104+
input.display_cannot_parse("absolute Height/MedianTimePast"),
105+
subject,
106+
lower_bound
107+
)
108+
}
109+
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => {
110+
write!(
111+
f,
112+
"{} ({})",
113+
input.display_cannot_parse("absolute Height/MedianTimePast"),
114+
subject
115+
)
116+
}
117+
Self::Conversion(value) if *value < i64::from(lower_bound) => {
118+
write!(f, "{} {} is below limit {}", subject, value, lower_bound)
119+
}
120+
Self::Conversion(value) => {
121+
write!(f, "{} {} is above limit {}", subject, value, upper_bound)
122+
}
123+
}
124+
}
125+
126+
// To be consistent with `write_err` we need to **not** return source if overflow occurred
127+
#[cfg(feature = "std")]
128+
pub(super) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
129+
use core::num::IntErrorKind;
130+
131+
match self {
132+
Self::ParseInt(ParseIntError { source, .. })
133+
if *source.kind() == IntErrorKind::PosOverflow =>
134+
None,
135+
Self::ParseInt(ParseIntError { source, .. })
136+
if *source.kind() == IntErrorKind::NegOverflow =>
137+
None,
138+
Self::ParseInt(ParseIntError { source, .. }) => Some(source),
139+
Self::Conversion(_) => None,
140+
}
141+
}
142+
}
143+
144+
impl From<ConversionError> for ParseError {
145+
fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
146+
}
147+
148+
/// Error returned when converting a `u32` to a lock time variant fails.
149+
#[derive(Debug, Clone, PartialEq, Eq)]
150+
#[non_exhaustive]
151+
pub struct ConversionError {
152+
/// The expected timelock unit, height (blocks) or time (seconds).
153+
unit: LockTimeUnit,
154+
/// The invalid input value.
155+
input: u32,
156+
}
157+
158+
impl ConversionError {
159+
/// Constructs a new `ConversionError` from an invalid `n` when expecting a height value.
160+
pub(super) const fn invalid_height(n: u32) -> Self {
161+
Self { unit: LockTimeUnit::Blocks, input: n }
162+
}
163+
164+
/// Constructs a new `ConversionError` from an invalid `n` when expecting a time value.
165+
pub(super) const fn invalid_time(n: u32) -> Self {
166+
Self { unit: LockTimeUnit::Seconds, input: n }
167+
}
168+
}
169+
170+
impl fmt::Display for ConversionError {
171+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172+
write!(f, "invalid lock time value {}, {}", self.input, self.unit)
173+
}
174+
}
175+
176+
#[cfg(feature = "std")]
177+
impl std::error::Error for ConversionError {
178+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
179+
}
180+
181+
/// Describes the two types of locking, lock-by-height and lock-by-time.
182+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
183+
enum LockTimeUnit {
184+
/// Lock by blockheight.
185+
Blocks,
186+
/// Lock by blocktime.
187+
Seconds,
188+
}
189+
190+
impl fmt::Display for LockTimeUnit {
191+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
192+
use LockTimeUnit as L;
193+
194+
match *self {
195+
L::Blocks => write!(f, "expected lock-by-height (must be < {})", LOCK_TIME_THRESHOLD),
196+
L::Seconds => write!(f, "expected lock-by-time (must be >= {})", LOCK_TIME_THRESHOLD),
197+
}
198+
}
199+
}
200+
201+
#[cfg(test)]
202+
mod tests {
203+
use super::*;
204+
205+
#[test]
206+
#[cfg(feature = "alloc")]
207+
fn locktime_unit_display() {
208+
use alloc::format;
209+
let blocks = LockTimeUnit::Blocks;
210+
let seconds = LockTimeUnit::Seconds;
211+
212+
assert_eq!(format!("{}", blocks), "expected lock-by-height (must be < 500000000)");
213+
assert_eq!(format!("{}", seconds), "expected lock-by-time (must be >= 500000000)");
214+
}
215+
}

0 commit comments

Comments
 (0)