zcash_address: Replace `FromAddress` with `TryFromAddress`

This enables the user-defined conversions to be fallible, which they
will almost always want to be (as address data needs to be validated
before it can be used).
This commit is contained in:
Jack Grigg 2022-06-07 12:26:41 +00:00
parent 73d9395c9d
commit 16938d1c4f
3 changed files with 90 additions and 22 deletions

View File

@ -6,6 +6,12 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `zcash_address::ConversionError`
- `zcash_address::TryFromAddress`
### Removed
- `zcash_address::FromAddress` (use `TryFromAddress` instead).
## [0.1.0] - 2022-05-11
Initial release.

View File

@ -12,7 +12,39 @@ impl fmt::Display for UnsupportedAddress {
}
}
/// An error encountered while converting a parsed [`ZcashAddress`] into another type.
#[derive(Debug)]
pub enum ConversionError<E> {
/// The address type is not supported by the target type.
Unsupported(UnsupportedAddress),
/// A conversion error returned by the target type.
User(E),
}
impl<E> From<E> for ConversionError<E> {
fn from(e: E) -> Self {
ConversionError::User(e)
}
}
impl<E: fmt::Display> fmt::Display for ConversionError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unsupported(e) => e.fmt(f),
Self::User(e) => e.fmt(f),
}
}
}
impl Error for UnsupportedAddress {}
impl<E: Error + 'static> Error for ConversionError<E> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
ConversionError::Unsupported(_) => None,
ConversionError::User(e) => Some(e),
}
}
}
/// A helper trait for converting a [`ZcashAddress`] into another type.
///
@ -21,15 +53,22 @@ impl Error for UnsupportedAddress {}
/// # Examples
///
/// ```
/// use zcash_address::{FromAddress, Network, UnsupportedAddress, ZcashAddress};
/// use zcash_address::{ConversionError, Network, TryFromAddress, UnsupportedAddress, ZcashAddress};
///
/// #[derive(Debug)]
/// struct MySapling([u8; 43]);
///
/// // Implement the FromAddress trait, overriding whichever conversion methods match your
/// // requirements for the resulting type.
/// impl FromAddress for MySapling {
/// fn from_sapling(net: Network, data: [u8; 43]) -> Result<Self, UnsupportedAddress> {
/// impl TryFromAddress for MySapling {
/// // In this example we aren't checking the validity of the inner Sapling address,
/// // but your code should do so!
/// type Error = &'static str;
///
/// fn try_from_sapling(
/// net: Network,
/// data: [u8; 43],
/// ) -> Result<Self, ConversionError<Self::Error>> {
/// Ok(MySapling(data))
/// }
/// }
@ -48,30 +87,53 @@ impl Error for UnsupportedAddress {}
/// "Zcash transparent P2PKH addresses are not supported",
/// );
/// ```
pub trait FromAddress: Sized {
fn from_sprout(net: Network, data: sprout::Data) -> Result<Self, UnsupportedAddress> {
pub trait TryFromAddress: Sized {
/// Conversion errors for the user type (e.g. failing to parse the data passed to
/// [`Self::try_from_sapling`] as a valid Sapling address).
type Error;
fn try_from_sprout(
net: Network,
data: sprout::Data,
) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(UnsupportedAddress("Sprout"))
Err(ConversionError::Unsupported(UnsupportedAddress("Sprout")))
}
fn from_sapling(net: Network, data: sapling::Data) -> Result<Self, UnsupportedAddress> {
fn try_from_sapling(
net: Network,
data: sapling::Data,
) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(UnsupportedAddress("Sapling"))
Err(ConversionError::Unsupported(UnsupportedAddress("Sapling")))
}
fn from_unified(net: Network, data: unified::Address) -> Result<Self, UnsupportedAddress> {
fn try_from_unified(
net: Network,
data: unified::Address,
) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(UnsupportedAddress("Unified"))
Err(ConversionError::Unsupported(UnsupportedAddress("Unified")))
}
fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Result<Self, UnsupportedAddress> {
fn try_from_transparent_p2pkh(
net: Network,
data: p2pkh::Data,
) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(UnsupportedAddress("transparent P2PKH"))
Err(ConversionError::Unsupported(UnsupportedAddress(
"transparent P2PKH",
)))
}
fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Result<Self, UnsupportedAddress> {
fn try_from_transparent_p2sh(
net: Network,
data: p2sh::Data,
) -> Result<Self, ConversionError<Self::Error>> {
let _ = (net, data);
Err(UnsupportedAddress("transparent P2SH"))
Err(ConversionError::Unsupported(UnsupportedAddress(
"transparent P2SH",
)))
}
}

View File

@ -5,7 +5,7 @@ mod kind;
#[cfg(test)]
mod test_vectors;
pub use convert::{FromAddress, ToAddress, UnsupportedAddress};
pub use convert::{ConversionError, ToAddress, TryFromAddress, UnsupportedAddress};
pub use encoding::ParseError;
pub use kind::unified;
@ -84,7 +84,7 @@ impl ZcashAddress {
/// Converts this address into another type.
///
/// `convert` can convert into any type that implements the [`FromAddress`] trait.
/// `convert` can convert into any type that implements the [`TryFromAddress`] trait.
/// This enables `ZcashAddress` to be used as a common parsing and serialization
/// interface for Zcash addresses, while delegating operations on those addresses
/// (such as constructing transactions) to downstream crates.
@ -95,13 +95,13 @@ impl ZcashAddress {
/// [`encode`]: Self::encode
/// [`Display` implementation]: std::fmt::Display
/// [`address.to_string()`]: std::string::ToString
pub fn convert<T: FromAddress>(self) -> Result<T, UnsupportedAddress> {
pub fn convert<T: TryFromAddress>(self) -> Result<T, ConversionError<T::Error>> {
match self.kind {
AddressKind::Sprout(data) => T::from_sprout(self.net, data),
AddressKind::Sapling(data) => T::from_sapling(self.net, data),
AddressKind::Unified(data) => T::from_unified(self.net, data),
AddressKind::P2pkh(data) => T::from_transparent_p2pkh(self.net, data),
AddressKind::P2sh(data) => T::from_transparent_p2sh(self.net, data),
AddressKind::Sprout(data) => T::try_from_sprout(self.net, data),
AddressKind::Sapling(data) => T::try_from_sapling(self.net, data),
AddressKind::Unified(data) => T::try_from_unified(self.net, data),
AddressKind::P2pkh(data) => T::try_from_transparent_p2pkh(self.net, data),
AddressKind::P2sh(data) => T::try_from_transparent_p2sh(self.net, data),
}
}
}