Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .cargo/mutants.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
additional_cargo_args = ["--all-features"]
examine_globs = ["payjoin/src/uri/*.rs", "payjoin/src/receive/**/*.rs", "payjoin/src/send/**/*.rs"]
examine_globs = [
"payjoin/src/lib.rs",
"payjoin/src/uri/*.rs",
"payjoin/src/receive/**/*.rs",
"payjoin/src/send/**/*.rs"
]
exclude_globs = []
exclude_re = [
"impl Debug",
"impl Display",
"deserialize",
"Iterator",
".*Error",

# ".*Error",

# ---------------------Crate-specific exculsions---------------------
# Receive
# src/receive/v1/mod.rs
"interleave_shuffle", # Replacing index += 1 with index *= 1 in a loop causes a timeout due to an infinite loop

# URI Error exclusions (specific patterns to avoid infinite loops while testing error logic)
"PayjoinUriError::new", # Constructor methods that are straightforward
]
1 change: 1 addition & 0 deletions payjoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ohttp = { package = "bitcoin-ohttp", version = "0.6.0", optional = true }
serde = { version = "1.0.186", default-features = false, optional = true }
reqwest = { version = "0.12", default-features = false, optional = true }
rustls = { version = "0.22.4", optional = true }
thiserror = "1.0"
url = { version = "2.2.2", optional = true }
serde_json = { version = "1.0.108", optional = true }

Expand Down
2 changes: 1 addition & 1 deletion payjoin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ mod uri;
#[cfg(feature = "_core")]
pub use into_url::{Error as IntoUrlError, IntoUrl};
#[cfg(feature = "_core")]
pub use uri::{PjParseError, PjUri, Uri, UriExt};
pub use uri::{PayjoinUri, PayjoinUriError, PjParseError, PjUri, Uri, UriExt, ValidatedPayjoinUri};
#[cfg(feature = "_core")]
pub use url::{ParseError, Url};
#[cfg(feature = "_core")]
Expand Down
1 change: 1 addition & 0 deletions payjoin/src/receive/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ fn subdir_path_from_pubkey(pubkey: &HpkePublicKey) -> ShortId {
/// Each variant wraps a `Receiver` with a specific state type, except for [`ReceiverTypeState::TerminalFailure`] which
/// indicates the session has ended or is invalid.
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum ReceiverTypeState {
Uninitialized(Receiver<UninitializedReceiver>),
Initialized(Receiver<Initialized>),
Expand Down
1 change: 1 addition & 0 deletions payjoin/src/receive/v2/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ impl SessionHistory {
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
/// Represents a piece of information that the receiver has obtained from the session
/// Each event can be used to transition the receiver state machine to a new state
pub enum SessionEvent {
Expand Down
108 changes: 93 additions & 15 deletions payjoin/src/uri/error.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::error::Error;

use thiserror::Error;
use url::ParseError;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct PjParseError(pub(crate) InternalPjParseError);

#[derive(Debug)]
#[derive(Debug, Clone)]
pub(crate) enum InternalPjParseError {
BadPjOs,
DuplicateParams(&'static str),
Expand All @@ -13,24 +16,15 @@ pub(crate) enum InternalPjParseError {
UnsecureEndpoint,
}

#[derive(Debug)]
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum BadEndpointError {
UrlParse(ParseError),
#[error("Invalid URL: {0}")]
UrlParse(#[from] ParseError),
#[cfg(feature = "v2")]
#[error("URL fragment contains lowercase characters")]
LowercaseFragment,
}

impl std::fmt::Display for BadEndpointError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BadEndpointError::UrlParse(e) => write!(f, "Invalid URL: {e:?}"),
#[cfg(feature = "v2")]
BadEndpointError::LowercaseFragment =>
write!(f, "Some or all of the fragment is lowercase"),
}
}
}

impl From<InternalPjParseError> for PjParseError {
fn from(value: InternalPjParseError) -> Self { PjParseError(value) }
}
Expand All @@ -52,3 +46,87 @@ impl std::fmt::Display for PjParseError {
}
}
}

impl Error for PjParseError {}

#[derive(Debug, Error)]
pub enum PayjoinUriError {
#[error("Bitcoin URI parse error: {0}")]
Parse(bitcoin_uri::de::Error<PjParseError>),

#[error("URI does not support Payjoin (missing 'pj' parameter)")]
UnsupportedUri,

#[error("Invalid pjos parameter")]
BadPjOs,

#[error("Duplicate parameter '{param}' in URI")]
DuplicateParams { param: &'static str },

#[error("Missing payjoin endpoint")]
MissingEndpoint,

#[error("Parameter contains invalid UTF-8")]
NotUtf8,

#[error("Invalid payjoin endpoint: {0}")]
BadEndpoint(BadEndpointError),

#[error("Endpoint scheme is not secure (must be https or onion)")]
UnsecureEndpoint,
}

impl PartialEq for PayjoinUriError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(PayjoinUriError::Parse(_), PayjoinUriError::Parse(_)) => false,
(PayjoinUriError::UnsupportedUri, PayjoinUriError::UnsupportedUri) => true,
(PayjoinUriError::BadPjOs, PayjoinUriError::BadPjOs) => true,
(
PayjoinUriError::DuplicateParams { param: a },
PayjoinUriError::DuplicateParams { param: b },
) => a == b,
(PayjoinUriError::MissingEndpoint, PayjoinUriError::MissingEndpoint) => true,
(PayjoinUriError::NotUtf8, PayjoinUriError::NotUtf8) => true,
(PayjoinUriError::BadEndpoint(a), PayjoinUriError::BadEndpoint(b)) => a == b,
(PayjoinUriError::UnsecureEndpoint, PayjoinUriError::UnsecureEndpoint) => true,
_ => false,
}
}
}

impl Eq for PayjoinUriError {}

impl PayjoinUriError {
pub fn unsupported_uri() -> Self { PayjoinUriError::UnsupportedUri }

pub fn bad_pj_os() -> Self { PayjoinUriError::BadPjOs }

pub fn duplicate_params(param: &'static str) -> Self {
PayjoinUriError::DuplicateParams { param }
}

pub fn missing_endpoint() -> Self { PayjoinUriError::MissingEndpoint }

pub fn not_utf8() -> Self { PayjoinUriError::NotUtf8 }

pub fn unsecure_endpoint() -> Self { PayjoinUriError::UnsecureEndpoint }
}

impl From<bitcoin_uri::de::Error<PjParseError>> for PayjoinUriError {
fn from(error: bitcoin_uri::de::Error<PjParseError>) -> Self { PayjoinUriError::Parse(error) }
}

impl From<InternalPjParseError> for PayjoinUriError {
fn from(error: InternalPjParseError) -> Self {
match error {
InternalPjParseError::BadPjOs => PayjoinUriError::BadPjOs,
InternalPjParseError::DuplicateParams(param) =>
PayjoinUriError::DuplicateParams { param },
InternalPjParseError::MissingEndpoint => PayjoinUriError::MissingEndpoint,
InternalPjParseError::NotUtf8 => PayjoinUriError::NotUtf8,
InternalPjParseError::BadEndpoint(e) => PayjoinUriError::BadEndpoint(e),
InternalPjParseError::UnsecureEndpoint => PayjoinUriError::UnsecureEndpoint,
}
}
}
Loading
Loading