diff --git a/crates/chain/src/rusqlite_impl.rs b/crates/chain/src/rusqlite_impl.rs index 7e9e2f6f9..b6f7a54bd 100644 --- a/crates/chain/src/rusqlite_impl.rs +++ b/crates/chain/src/rusqlite_impl.rs @@ -96,6 +96,20 @@ impl ToSql for Impl { } } +impl FromSql for Impl { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + BlockId::from_str(value.as_str()?) + .map(Self) + .map_err(from_sql_error) + } +} + +impl ToSql for Impl { + fn to_sql(&self) -> rusqlite::Result> { + Ok(self.to_string().into()) + } +} + #[cfg(feature = "miniscript")] impl FromSql for Impl { fn column_result(value: ValueRef<'_>) -> FromSqlResult { diff --git a/crates/core/src/block_id.rs b/crates/core/src/block_id.rs index 2e64c9cb2..e578b1dfa 100644 --- a/crates/core/src/block_id.rs +++ b/crates/core/src/block_id.rs @@ -1,3 +1,6 @@ +use core::fmt::{self, Display}; +use core::str::FromStr; + use bitcoin::{hashes::Hash, BlockHash}; /// A reference to a block in the canonical chain. @@ -40,6 +43,57 @@ impl From<(&u32, &BlockHash)> for BlockId { } } +impl Display for BlockId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}:{}", self.height, self.hash) + } +} + +impl FromStr for BlockId { + type Err = ParseBlockIdError; + + fn from_str(s: &str) -> Result { + let (height_str, hash_str) = s.split_once(':').ok_or(ParseBlockIdError::InvalidFormat)?; + + let height = height_str + .parse::() + .map_err(|_| ParseBlockIdError::InvalidHeight)?; + + let hash = hash_str + .parse::() + .map_err(|_| ParseBlockIdError::InvalidBlockhash)?; + + Ok(BlockId { height, hash }) + } +} + +/// [`BlockId`] parsing errors. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ParseBlockIdError { + /// Invalid [`BlockId`] representation (missing `:` separator). + InvalidFormat, + /// Invalid block height (failed to parse into a `u32`). + InvalidHeight, + /// Invalid block hash (failed to parse into a [`Blockhash`]). + InvalidBlockhash, +} + +impl Display for ParseBlockIdError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidFormat => write!( + f, + "Failed to parse string into `BlockId`, expected `:`" + ), + Self::InvalidHeight => write!(f, "Failed to parse height into a u32."), + Self::InvalidBlockhash => write!(f, "Failed to parse hash into a `Blockhash`."), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseBlockIdError {} + /// Represents the confirmation block and time of a transaction. #[derive(Debug, Default, Clone, PartialEq, Eq, Copy, PartialOrd, Ord, core::hash::Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]