use core::convert::Infallible;
use core::fmt;
use internals::error::InputString;
#[cfg(feature = "encoding")]
use internals::write_err;
use super::{Height, MedianTimePast, LOCK_TIME_THRESHOLD};
use crate::parse_int::{ParseIntError, PrefixedHexError, UnprefixedHexError};
#[cfg(feature = "encoding")]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LockTimeDecoderError(pub(super) encoding::UnexpectedEofError);
#[cfg(feature = "encoding")]
impl From<Infallible> for LockTimeDecoderError {
fn from(never: Infallible) -> Self { match never {} }
}
#[cfg(feature = "encoding")]
impl fmt::Display for LockTimeDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write_err!(f, "lock time decoder error"; self.0)
}
}
#[cfg(feature = "encoding")]
#[cfg(feature = "std")]
impl std::error::Error for LockTimeDecoderError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.0) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncompatibleHeightError {
pub(super) lock: MedianTimePast,
pub(super) incompatible: Height,
}
impl IncompatibleHeightError {
#[inline]
pub fn lock(&self) -> MedianTimePast { self.lock }
#[inline]
pub fn incompatible(&self) -> Height { self.incompatible }
}
impl From<Infallible> for IncompatibleHeightError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for IncompatibleHeightError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"tried to satisfy a lock-by-time lock {} with height: {}",
self.lock, self.incompatible
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for IncompatibleHeightError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IncompatibleTimeError {
pub(super) lock: Height,
pub(super) incompatible: MedianTimePast,
}
impl IncompatibleTimeError {
#[inline]
pub fn lock(&self) -> Height { self.lock }
#[inline]
pub fn incompatible(&self) -> MedianTimePast { self.incompatible }
}
impl From<Infallible> for IncompatibleTimeError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for IncompatibleTimeError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"tried to satisfy a lock-by-height lock {} with MTP: {}",
self.lock, self.incompatible
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for IncompatibleTimeError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParseHeightError(ParseError);
impl From<Infallible> for ParseHeightError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for ParseHeightError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.display(f, "block height", 0, LOCK_TIME_THRESHOLD - 1)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseHeightError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
}
impl From<ParseError> for ParseHeightError {
#[inline]
fn from(value: ParseError) -> Self { Self(value) }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ParseTimeError(ParseError);
impl From<Infallible> for ParseTimeError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for ParseTimeError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.display(f, "block time", LOCK_TIME_THRESHOLD, u32::MAX)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseTimeError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { self.0.source() }
}
impl From<ParseError> for ParseTimeError {
#[inline]
fn from(value: ParseError) -> Self { Self(value) }
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub(super) enum ParseError {
PrefixedHex(PrefixedHexError),
UnprefixedHex(UnprefixedHexError),
ParseInt(ParseIntError),
Conversion(i64),
}
impl ParseError {
#[inline]
pub(super) fn invalid_int<S: Into<InputString>>(
s: S,
) -> impl FnOnce(core::num::ParseIntError) -> Self {
move |source| {
Self::ParseInt(ParseIntError { input: s.into(), bits: 32, is_signed: true, source })
}
}
pub(super) fn display(
&self,
f: &mut fmt::Formatter<'_>,
subject: &str,
lower_bound: u32,
upper_bound: u32,
) -> fmt::Result {
use core::num::IntErrorKind;
match self {
Self::PrefixedHex(ref err) => fmt::Display::fmt(err, f),
Self::UnprefixedHex(ref err) => fmt::Display::fmt(err, f),
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
if *source.kind() == IntErrorKind::PosOverflow =>
{
write!(
f,
"{} ({} is above limit {})",
input.display_cannot_parse("absolute Height/MedianTimePast"),
subject,
upper_bound
)
}
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source })
if *source.kind() == IntErrorKind::NegOverflow =>
{
write!(
f,
"{} ({} is below limit {})",
input.display_cannot_parse("absolute Height/MedianTimePast"),
subject,
lower_bound
)
}
Self::ParseInt(ParseIntError { input, bits: _, is_signed: _, source: _ }) => {
write!(
f,
"{} ({})",
input.display_cannot_parse("absolute Height/MedianTimePast"),
subject
)
}
Self::Conversion(value) if *value < i64::from(lower_bound) => {
write!(f, "{} {} is below limit {}", subject, value, lower_bound)
}
Self::Conversion(value) => {
write!(f, "{} {} is above limit {}", subject, value, upper_bound)
}
}
}
#[inline]
#[cfg(feature = "std")]
pub(super) fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use core::num::IntErrorKind;
match self {
Self::PrefixedHex(ref err) => Some(err),
Self::UnprefixedHex(ref err) => Some(err),
Self::ParseInt(ParseIntError { source, .. })
if *source.kind() == IntErrorKind::PosOverflow =>
None,
Self::ParseInt(ParseIntError { source, .. })
if *source.kind() == IntErrorKind::NegOverflow =>
None,
Self::ParseInt(ParseIntError { source, .. }) => Some(source),
Self::Conversion(_) => None,
}
}
}
impl From<Infallible> for ParseError {
fn from(never: Infallible) -> Self { match never {} }
}
impl From<ConversionError> for ParseError {
#[inline]
fn from(value: ConversionError) -> Self { Self::Conversion(value.input.into()) }
}
impl From<PrefixedHexError> for ParseError {
#[inline]
fn from(value: PrefixedHexError) -> Self { Self::PrefixedHex(value) }
}
impl From<UnprefixedHexError> for ParseError {
#[inline]
fn from(value: UnprefixedHexError) -> Self { Self::UnprefixedHex(value) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ConversionError {
unit: LockTimeUnit,
input: u32,
}
impl ConversionError {
#[inline]
pub(super) const fn invalid_height(n: u32) -> Self {
Self { unit: LockTimeUnit::Blocks, input: n }
}
#[inline]
pub(super) const fn invalid_time(n: u32) -> Self {
Self { unit: LockTimeUnit::Seconds, input: n }
}
}
impl From<Infallible> for ConversionError {
fn from(never: Infallible) -> Self { match never {} }
}
impl fmt::Display for ConversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid lock time value {}, {}", self.input, self.unit)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ConversionError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum LockTimeUnit {
Blocks,
Seconds,
}
impl fmt::Display for LockTimeUnit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Blocks =>
write!(f, "expected lock-by-height (must be < {})", LOCK_TIME_THRESHOLD),
Self::Seconds =>
write!(f, "expected lock-by-time (must be >= {})", LOCK_TIME_THRESHOLD),
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::{format, string::ToString};
#[cfg(feature = "alloc")]
use core::str::FromStr;
#[cfg(feature = "std")]
use std::error::Error;
#[cfg(feature = "alloc")]
#[cfg(feature = "encoding")]
use encoding::{Decode as _, Decoder as _};
#[cfg(feature = "alloc")]
use super::LockTimeUnit;
#[cfg(feature = "alloc")]
use crate::{
locktime::absolute::{Height, LockTime, MedianTimePast},
BlockHeight,
};
#[test]
#[cfg(feature = "alloc")]
fn locktime_unit_display() {
let blocks = LockTimeUnit::Blocks;
let seconds = LockTimeUnit::Seconds;
assert_eq!(format!("{}", blocks), "expected lock-by-height (must be < 500000000)");
assert_eq!(format!("{}", seconds), "expected lock-by-time (must be >= 500000000)");
}
#[test]
#[cfg(feature = "alloc")]
fn error_display_is_non_empty() {
let too_big = BlockHeight::from_u32(u32::MAX);
let e = Height::try_from(too_big).unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_none());
let time_lock = LockTime::from_mtp(MedianTimePast::MIN.to_u32()).unwrap();
let e = time_lock.is_satisfied_by_height(Height::MIN).unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_none());
let height_lock = LockTime::from_height(Height::MIN.to_u32()).unwrap();
let e = height_lock.is_satisfied_by_time(MedianTimePast::MIN).unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_none());
let e = Height::from_str("invalid").unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
let e = MedianTimePast::from_str("invalid").unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
#[cfg(feature = "encoding")]
{
let mut decoder = LockTime::decoder();
let _ = decoder.push_bytes(&mut [0u8; 3].as_slice());
let e = decoder.end().unwrap_err();
assert!(!e.to_string().is_empty());
#[cfg(feature = "std")]
assert!(e.source().is_some());
}
}
}