From d5c52618a9ab90c97500475c8683c4b24cc2c8a9 Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 19 Mar 2024 09:08:54 +1100 Subject: [PATCH 1/2] Move NetworkValidationError within file Done in preparation for adding the `NetworkValidationError` as a variant of `ParseError`. Move the `NetworkValidationError` type to beneath `ParseError`. Code move only, no other changes. --- bitcoin/src/address/error.rs | 40 ++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/bitcoin/src/address/error.rs b/bitcoin/src/address/error.rs index b1757da6..14cac051 100644 --- a/bitcoin/src/address/error.rs +++ b/bitcoin/src/address/error.rs @@ -9,26 +9,6 @@ use crate::blockdata::script::{witness_program, witness_version}; use crate::prelude::*; use crate::Network; -/// Address's network differs from required one. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct NetworkValidationError { - /// Network that was required. - pub(crate) required: Network, - /// The address itself. - pub(crate) address: Address, -} - -impl fmt::Display for NetworkValidationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "address ")?; - fmt::Display::fmt(&self.address.0, f)?; - write!(f, " is not valid on {}", self.required) - } -} - -#[cfg(feature = "std")] -impl std::error::Error for NetworkValidationError {} - /// Error while generating address from script. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] @@ -228,6 +208,26 @@ impl std::error::Error for UnknownHrpError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } +/// Address's network differs from required one. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NetworkValidationError { + /// Network that was required. + pub(crate) required: Network, + /// The address itself. + pub(crate) address: Address, +} + +impl fmt::Display for NetworkValidationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "address ")?; + fmt::Display::fmt(&self.address.0, f)?; + write!(f, " is not valid on {}", self.required) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for NetworkValidationError {} + /// Decoded base58 data was an invalid length. #[derive(Debug, Clone, PartialEq, Eq)] pub struct InvalidBase58PayloadLengthError { From a7a99e06bb5d53b50343ca5c1c852dd8caace2bf Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 19 Mar 2024 09:10:11 +1100 Subject: [PATCH 2/2] Add a validation variant to `ParseError` `require_network` is typically called as part of parsing, often in the same line of code. Counter to our normal errors, it makes `require_network` more ergonomic to use if we just return a `ParseError` variant. Close: #2507 --- bitcoin/src/address/error.rs | 8 +++++++ bitcoin/src/address/mod.rs | 42 ++++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/bitcoin/src/address/error.rs b/bitcoin/src/address/error.rs index 14cac051..be0a573d 100644 --- a/bitcoin/src/address/error.rs +++ b/bitcoin/src/address/error.rs @@ -123,6 +123,8 @@ pub enum ParseError { InvalidBase58PayloadLength(InvalidBase58PayloadLengthError), /// Invalid legacy address prefix in base58 data payload. InvalidLegacyPrefix(InvalidLegacyPrefixError), + /// Address's network differs from required one. + NetworkValidation(NetworkValidationError), } internals::impl_from_infallible!(ParseError); @@ -140,6 +142,7 @@ impl fmt::Display for ParseError { LegacyAddressTooLong(ref e) => write_err!(f, "legacy address base58 string"; e), InvalidBase58PayloadLength(ref e) => write_err!(f, "legacy address base58 data"; e), InvalidLegacyPrefix(ref e) => write_err!(f, "legacy address base58 prefix"; e), + NetworkValidation(ref e) => write_err!(f, "validation error"; e), } } } @@ -158,6 +161,7 @@ impl std::error::Error for ParseError { LegacyAddressTooLong(ref e) => Some(e), InvalidBase58PayloadLength(ref e) => Some(e), InvalidLegacyPrefix(ref e) => Some(e), + NetworkValidation(ref e) => Some(e), } } } @@ -194,6 +198,10 @@ impl From for ParseError { fn from(e: InvalidLegacyPrefixError) -> Self { Self::InvalidLegacyPrefix(e) } } +impl From for ParseError { + fn from(e: NetworkValidationError) -> Self { Self::NetworkValidation(e) } +} + /// Unknown HRP error. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index df1c7906..1af1580c 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -667,12 +667,50 @@ impl Address { /// /// For details about this mechanism, see section [*Parsing addresses*](Address#parsing-addresses) /// on [`Address`]. + /// + /// # Errors + /// + /// This function only ever returns the [`ParseError::NetworkValidation`] variant of + /// `ParseError`. This is not how we normally implement errors in this library but + /// `require_network` is not a typical function, it is conceptually part of string parsing. + /// + /// # Examples + /// + /// ``` + /// use bitcoin::address::{NetworkChecked, NetworkUnchecked, ParseError}; + /// use bitcoin::{Address, Network}; + /// + /// const ADDR: &str = "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs"; + /// + /// fn parse_and_validate_address(network: Network) -> Result { + /// let address = ADDR.parse::>()? + /// .require_network(network)?; + /// Ok(address) + /// } + /// + /// fn parse_and_validate_address_combinator(network: Network) -> Result { + /// let address = ADDR.parse::>() + /// .and_then(|a| a.require_network(network))?; + /// Ok(address) + /// } + /// + /// fn parse_and_validate_address_show_types(network: Network) -> Result { + /// let address: Address = ADDR.parse::>()? + /// .require_network(network)?; + /// Ok(address) + /// } + /// + /// let network = Network::Bitcoin; // Don't hard code network in applications. + /// let _ = parse_and_validate_address(network).unwrap(); + /// let _ = parse_and_validate_address_combinator(network).unwrap(); + /// let _ = parse_and_validate_address_show_types(network).unwrap(); + /// ``` #[inline] - pub fn require_network(self, required: Network) -> Result { + pub fn require_network(self, required: Network) -> Result { if self.is_valid_for_network(required) { Ok(self.assume_checked()) } else { - Err(NetworkValidationError { required, address: self }) + Err(NetworkValidationError { required, address: self }.into()) } }