Merge rust-bitcoin/rust-bitcoin#3858: Bound `Address` parsing on `NetworkValidationUnchecked`
29a71de928
Bound Address parsing on NetworkValidationUnchecked (Tobin C. Harding)cf455d3a06
Fix typo in prifixes (Tobin C. Harding) Pull request description: Currently it is not possible for downstream to use a generic on the `Address` type in structs in conjuncture with derives (`serde::Deserialize` and `Display`) because our impls are only done for `NetworkUnchecked` (as they should be). However, as observed by dpc, if we add a secondary marker trait and use it to bound the impls, implementing the new marker for `NetworkUnchecked` then downstream can use derives by way of ``` #[derive(Serialize, Deserialize)] struct Foo<V> where V: NetworkValidation, { #[serde(bound(deserialize = "V: NetworkValidationUnchecked"))] address: Address<V>, } ``` This is cool as hell because the `Address` type is currently a royal PITA. Patch 1 is trivial cleanup. To get past a build error in `FromStr` I used this little trick ```rust // We know that `U` is only ever `NetworkUnchecked` but the compiler does not. Ok(Address(address.0, PhantomData::<U>)) ``` Resolve: #3760 and Close: #3856 ACKs for top commit: apoelstra: ACK 29a71de92817bccd49b42b1055cc570832e6b959; successfully ran local tests Tree-SHA512: 7c158dddb9fdbaaa1e48204bbf915b18ced56f5d82ce82630db6c0b52161bcf43b3ac413fa990a23975743c56917985b2666a74f9067221f003f2dcf080f827e
This commit is contained in:
commit
9a8e61393c
|
@ -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<V>
|
||||
//! where V: NetworkValidation,
|
||||
//! {
|
||||
//! #[serde(bound(deserialize = "V: NetworkValidationUnchecked"))]
|
||||
//! address: Address<V>,
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
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<N: NetworkValidation> fmt::Display for DisplayUnchecked<'_, N> {
|
|||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
internals::serde_string_deserialize_impl!(Address<NetworkUnchecked>, "a Bitcoin address");
|
||||
impl<'de, U: NetworkValidationUnchecked> serde::Deserialize<'de> for Address<U> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Address<U>, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
use core::fmt::Formatter;
|
||||
|
||||
struct Visitor<U>(PhantomData<U>);
|
||||
impl<U> serde::de::Visitor<'_> for Visitor<U>
|
||||
where
|
||||
U: NetworkValidationUnchecked + NetworkValidation,
|
||||
Address<U>: FromStr,
|
||||
{
|
||||
type Value = Address<U>;
|
||||
|
||||
fn expecting(&self, f: &mut Formatter) -> core::fmt::Result {
|
||||
f.write_str("A Bitcoin address")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> core::result::Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
// We know that `U` is only ever `NetworkUnchecked` but the compiler does not.
|
||||
let address = v.parse::<Address<NetworkUnchecked>>().map_err(E::custom)?;
|
||||
Ok(Address(address.0, PhantomData::<U>))
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(Visitor(PhantomData::<U>))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<N: NetworkValidation> serde::Serialize for Address<N> {
|
||||
impl<V: NetworkValidation> serde::Serialize for Address<V> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
|
@ -880,15 +943,18 @@ impl<V: NetworkValidation> fmt::Debug for Address<V> {
|
|||
/// 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<NetworkUnchecked> {
|
||||
/// legacy prefixes.
|
||||
impl<U: NetworkValidationUnchecked> FromStr for Address<U> {
|
||||
type Err = ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
|
||||
fn from_str(s: &str) -> Result<Self, ParseError> {
|
||||
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::<U>))
|
||||
} 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::<U>))
|
||||
} 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<V>
|
||||
where
|
||||
V: NetworkValidation,
|
||||
{
|
||||
#[serde(bound(deserialize = "V: NetworkValidationUnchecked"))]
|
||||
address: Address<V>,
|
||||
}
|
||||
|
||||
let addr_str = "33iFwdLuRpW1uK1RTRqsoi8rR4NpDzk66k";
|
||||
let unchecked = addr_str.parse::<Address<_>>().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<NetworkUnchecked> =
|
||||
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<NetworkUnchecked> =
|
||||
serde_json::from_str(&ser).expect("failed to deserialize");
|
||||
assert_eq!(&rinsed.address, foo_checked.address.as_unchecked());
|
||||
assert_eq!(rinsed, foo_unchecked);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue