diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index 50453e6be..51a02c56f 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -22,6 +22,22 @@ //! let address = Address::p2pkh(&public_key, Network::Bitcoin); //! } //! ``` +//! +//! ### Using an `Address` as a struct field. +//! +//! ```rust +//! # #[cfg(feature = "serde")] { +//! # use serde::{self, Deserialize, Serialize}; +//! use bitcoin::address::{Address, NetworkValidation, NetworkValidationUnchecked}; +//! #[derive(Serialize, Deserialize)] +//! struct Foo +//! where V: NetworkValidation, +//! { +//! #[serde(bound(deserialize = "V: NetworkValidationUnchecked"))] +//! address: Address, +//! } +//! # } +//! ``` pub mod error; pub mod script_pubkey; @@ -107,6 +123,9 @@ mod sealed { pub trait NetworkValidation {} impl NetworkValidation for super::NetworkChecked {} impl NetworkValidation for super::NetworkUnchecked {} + + pub trait NetworkValidationUnchecked {} + impl NetworkValidationUnchecked for super::NetworkUnchecked {} } /// Marker of status of address's network validation. See section [*Parsing addresses*](Address#parsing-addresses) @@ -116,14 +135,25 @@ pub trait NetworkValidation: sealed::NetworkValidation + Sync + Send + Sized + U const IS_CHECKED: bool; } +/// Marker trait for `FromStr` and `serde::Deserialize`. +/// +/// This allows users to use `V: NetworkValidation` in conjunction with derives. Is only ever +/// implemented for `NetworkUnchecked`. +pub trait NetworkValidationUnchecked: + NetworkValidation + sealed::NetworkValidationUnchecked + Sync + Send + Sized + Unpin +{ +} + /// Marker that address's network has been successfully validated. See section [*Parsing addresses*](Address#parsing-addresses) /// on [`Address`] for details. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NetworkChecked {} /// Marker that address's network has not yet been validated. See section [*Parsing addresses*](Address#parsing-addresses) /// on [`Address`] for details. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum NetworkUnchecked {} impl NetworkValidation for NetworkChecked { @@ -133,6 +163,8 @@ impl NetworkValidation for NetworkUnchecked { const IS_CHECKED: bool = false; } +impl NetworkValidationUnchecked for NetworkUnchecked {} + /// The inner representation of an address, without the network validation tag. /// /// This struct represents the inner representation of an address without the network validation @@ -361,10 +393,41 @@ impl fmt::Display for DisplayUnchecked<'_, N> { } #[cfg(feature = "serde")] -internals::serde_string_deserialize_impl!(Address, "a Bitcoin address"); +impl<'de, U: NetworkValidationUnchecked> serde::Deserialize<'de> for Address { + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: serde::de::Deserializer<'de>, + { + use core::fmt::Formatter; + + struct Visitor(PhantomData); + impl serde::de::Visitor<'_> for Visitor + where + U: NetworkValidationUnchecked + NetworkValidation, + Address: FromStr, + { + type Value = Address; + + fn expecting(&self, f: &mut Formatter) -> core::fmt::Result { + f.write_str("A Bitcoin address") + } + + fn visit_str(self, v: &str) -> core::result::Result + where + E: serde::de::Error, + { + // We know that `U` is only ever `NetworkUnchecked` but the compiler does not. + let address = v.parse::>().map_err(E::custom)?; + Ok(Address(address.0, PhantomData::)) + } + } + + deserializer.deserialize_str(Visitor(PhantomData::)) + } +} #[cfg(feature = "serde")] -impl serde::Serialize for Address { +impl serde::Serialize for Address { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -880,15 +943,18 @@ impl fmt::Debug for Address { /// not a valid base58 address. /// /// - [`UnknownHrpError`] if the address does not begin with one of the above SegWit or -/// legacy prifixes. -impl FromStr for Address { +/// legacy prefixes. +impl FromStr for Address { type Err = ParseError; - fn from_str(s: &str) -> Result, ParseError> { + fn from_str(s: &str) -> Result { if ["bc1", "bcrt1", "tb1"].iter().any(|&prefix| s.to_lowercase().starts_with(prefix)) { - Ok(Address::from_bech32_str(s)?) + let address = Address::from_bech32_str(s)?; + // We know that `U` is only ever `NetworkUnchecked` but the compiler does not. + Ok(Address(address.0, PhantomData::)) } else if ["1", "2", "3", "m", "n"].iter().any(|&prefix| s.starts_with(prefix)) { - Ok(Address::from_base58_str(s)?) + let address = Address::from_base58_str(s)?; + Ok(Address(address.0, PhantomData::)) } else { let hrp = match s.rfind('1') { Some(pos) => &s[..pos], @@ -1427,4 +1493,37 @@ mod tests { } } } + + #[test] + #[cfg(feature = "serde")] + fn serde_address_usage_in_struct() { + use serde::{self, Deserialize, Serialize}; + + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + struct Foo + where + V: NetworkValidation, + { + #[serde(bound(deserialize = "V: NetworkValidationUnchecked"))] + address: Address, + } + + let addr_str = "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k"; + let unchecked = addr_str.parse::>().unwrap(); + + // Serialize with an unchecked address. + let foo_unchecked = Foo { address: unchecked.clone() }; + let ser = serde_json::to_string(&foo_unchecked).expect("failed to serialize"); + let rinsed: Foo = + serde_json::from_str(&ser).expect("failed to deserialize"); + assert_eq!(rinsed, foo_unchecked); + + // Serialize with a checked address. + let foo_checked = Foo { address: unchecked.assume_checked() }; + let ser = serde_json::to_string(&foo_checked).expect("failed to serialize"); + let rinsed: Foo = + serde_json::from_str(&ser).expect("failed to deserialize"); + assert_eq!(&rinsed.address, foo_checked.address.as_unchecked()); + assert_eq!(rinsed, foo_unchecked); + } }