Skip to content

Commit ad3f686

Browse files
authored
Propagate ParseError when retrieving ohttp parameter (payjoin#428)
This PR updates the `ohttp()` getter to propagate the parse errors as part of payjoin#399.
2 parents 52d19ae + accdaca commit ad3f686

File tree

4 files changed

+64
-26
lines changed

4 files changed

+64
-26
lines changed

payjoin/src/send/error.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use bitcoin::transaction::Version;
55
use bitcoin::{AddressType, Sequence};
66

77
#[cfg(feature = "v2")]
8-
use crate::uri::error::ParseReceiverPubkeyError;
8+
use crate::uri::error::ParseReceiverPubkeyParamError;
99

1010
/// Error that may occur when the response from receiver is malformed.
1111
///
@@ -203,7 +203,7 @@ pub(crate) enum InternalCreateRequestError {
203203
#[cfg(feature = "v2")]
204204
OhttpEncapsulation(crate::ohttp::OhttpEncapsulationError),
205205
#[cfg(feature = "v2")]
206-
ParseReceiverPubkey(ParseReceiverPubkeyError),
206+
ParseReceiverPubkey(ParseReceiverPubkeyParamError),
207207
#[cfg(feature = "v2")]
208208
MissingOhttpConfig,
209209
#[cfg(feature = "v2")]
@@ -287,8 +287,8 @@ impl From<crate::psbt::AddressTypeError> for CreateRequestError {
287287
}
288288

289289
#[cfg(feature = "v2")]
290-
impl From<ParseReceiverPubkeyError> for CreateRequestError {
291-
fn from(value: ParseReceiverPubkeyError) -> Self {
290+
impl From<ParseReceiverPubkeyParamError> for CreateRequestError {
291+
fn from(value: ParseReceiverPubkeyParamError) -> Self {
292292
CreateRequestError(InternalCreateRequestError::ParseReceiverPubkey(value))
293293
}
294294
}

payjoin/src/send/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ impl Sender {
306306
)
307307
.map_err(InternalCreateRequestError::Hpke)?;
308308
let mut ohttp =
309-
self.endpoint.ohttp().ok_or(InternalCreateRequestError::MissingOhttpConfig)?;
309+
self.endpoint.ohttp().map_err(|_| InternalCreateRequestError::MissingOhttpConfig)?;
310310
let (body, ohttp_ctx) = ohttp_encapsulate(&mut ohttp, "POST", url.as_str(), Some(&body))
311311
.map_err(InternalCreateRequestError::OhttpEncapsulation)?;
312312
log::debug!("ohttp_relay_url: {:?}", ohttp_relay);
@@ -331,7 +331,7 @@ impl Sender {
331331
#[cfg(feature = "v2")]
332332
fn extract_rs_pubkey(
333333
&self,
334-
) -> Result<HpkePublicKey, crate::uri::error::ParseReceiverPubkeyError> {
334+
) -> Result<HpkePublicKey, crate::uri::error::ParseReceiverPubkeyParamError> {
335335
use crate::uri::UrlExt;
336336
self.endpoint.receiver_pubkey()
337337
}
@@ -418,7 +418,7 @@ impl V2GetContext {
418418
)
419419
.map_err(InternalCreateRequestError::Hpke)?;
420420
let mut ohttp =
421-
self.endpoint.ohttp().ok_or(InternalCreateRequestError::MissingOhttpConfig)?;
421+
self.endpoint.ohttp().map_err(|_| InternalCreateRequestError::MissingOhttpConfig)?;
422422
let (body, ohttp_ctx) = ohttp_encapsulate(&mut ohttp, "GET", url.as_str(), Some(&body))
423423
.map_err(InternalCreateRequestError::OhttpEncapsulation)?;
424424

payjoin/src/uri/error.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,36 @@ pub(crate) enum InternalPjParseError {
1313

1414
#[cfg(feature = "v2")]
1515
#[derive(Debug)]
16-
pub(crate) enum ParseReceiverPubkeyError {
16+
pub(crate) enum ParseOhttpKeysParamError {
17+
MissingOhttpKeys,
18+
InvalidOhttpKeys(crate::ohttp::ParseOhttpKeysError),
19+
}
20+
21+
#[cfg(feature = "v2")]
22+
impl std::fmt::Display for ParseOhttpKeysParamError {
23+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24+
use ParseOhttpKeysParamError::*;
25+
26+
match &self {
27+
MissingOhttpKeys => write!(f, "ohttp keys are missing"),
28+
InvalidOhttpKeys(o) => write!(f, "invalid ohttp keys: {}", o),
29+
}
30+
}
31+
}
32+
33+
#[cfg(feature = "v2")]
34+
#[derive(Debug)]
35+
pub(crate) enum ParseReceiverPubkeyParamError {
1736
MissingPubkey,
1837
InvalidHrp(bitcoin::bech32::Hrp),
1938
DecodeBech32(bitcoin::bech32::primitives::decode::CheckedHrpstringError),
2039
InvalidPubkey(crate::hpke::HpkeError),
2140
}
2241

2342
#[cfg(feature = "v2")]
24-
impl std::fmt::Display for ParseReceiverPubkeyError {
43+
impl std::fmt::Display for ParseReceiverPubkeyParamError {
2544
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
26-
use ParseReceiverPubkeyError::*;
45+
use ParseReceiverPubkeyParamError::*;
2746

2847
match &self {
2948
MissingPubkey => write!(f, "receiver public key is missing"),
@@ -36,9 +55,9 @@ impl std::fmt::Display for ParseReceiverPubkeyError {
3655
}
3756

3857
#[cfg(feature = "v2")]
39-
impl std::error::Error for ParseReceiverPubkeyError {
58+
impl std::error::Error for ParseReceiverPubkeyParamError {
4059
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
41-
use ParseReceiverPubkeyError::*;
60+
use ParseReceiverPubkeyParamError::*;
4261

4362
match &self {
4463
MissingPubkey => None,

payjoin/src/uri/url_ext.rs

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,36 @@ use bitcoin::consensus::encode::Decodable;
55
use bitcoin::consensus::Encodable;
66
use url::Url;
77

8-
use super::error::ParseReceiverPubkeyError;
8+
use super::error::{ParseOhttpKeysParamError, ParseReceiverPubkeyParamError};
99
use crate::hpke::HpkePublicKey;
10-
use crate::OhttpKeys;
10+
use crate::ohttp::OhttpKeys;
1111

1212
/// Parse and set fragment parameters from `&pj=` URI parameter URLs
1313
pub(crate) trait UrlExt {
14-
fn receiver_pubkey(&self) -> Result<HpkePublicKey, ParseReceiverPubkeyError>;
14+
fn receiver_pubkey(&self) -> Result<HpkePublicKey, ParseReceiverPubkeyParamError>;
1515
fn set_receiver_pubkey(&mut self, exp: HpkePublicKey);
16-
fn ohttp(&self) -> Option<OhttpKeys>;
16+
fn ohttp(&self) -> Result<OhttpKeys, ParseOhttpKeysParamError>;
1717
fn set_ohttp(&mut self, ohttp: OhttpKeys);
1818
fn exp(&self) -> Option<std::time::SystemTime>;
1919
fn set_exp(&mut self, exp: std::time::SystemTime);
2020
}
2121

2222
impl UrlExt for Url {
2323
/// Retrieve the receiver's public key from the URL fragment
24-
fn receiver_pubkey(&self) -> Result<HpkePublicKey, ParseReceiverPubkeyError> {
24+
fn receiver_pubkey(&self) -> Result<HpkePublicKey, ParseReceiverPubkeyParamError> {
2525
let value = get_param(self, "RK1", |v| Some(v.to_owned()))
26-
.ok_or(ParseReceiverPubkeyError::MissingPubkey)?;
26+
.ok_or(ParseReceiverPubkeyParamError::MissingPubkey)?;
2727

2828
let (hrp, bytes) = crate::bech32::nochecksum::decode(&value)
29-
.map_err(ParseReceiverPubkeyError::DecodeBech32)?;
29+
.map_err(ParseReceiverPubkeyParamError::DecodeBech32)?;
3030

3131
let rk_hrp: Hrp = Hrp::parse("RK").unwrap();
3232
if hrp != rk_hrp {
33-
return Err(ParseReceiverPubkeyError::InvalidHrp(hrp));
33+
return Err(ParseReceiverPubkeyParamError::InvalidHrp(hrp));
3434
}
3535

3636
HpkePublicKey::from_compressed_bytes(&bytes[..])
37-
.map_err(ParseReceiverPubkeyError::InvalidPubkey)
37+
.map_err(ParseReceiverPubkeyParamError::InvalidPubkey)
3838
}
3939

4040
/// Set the receiver's public key in the URL fragment
@@ -50,8 +50,10 @@ impl UrlExt for Url {
5050
}
5151

5252
/// Retrieve the ohttp parameter from the URL fragment
53-
fn ohttp(&self) -> Option<OhttpKeys> {
54-
get_param(self, "OH1", |value| OhttpKeys::from_str(value).ok())
53+
fn ohttp(&self) -> Result<OhttpKeys, ParseOhttpKeysParamError> {
54+
let value = get_param(self, "OH1", |v| Some(v.to_owned()))
55+
.ok_or(ParseOhttpKeysParamError::MissingOhttpKeys)?;
56+
OhttpKeys::from_str(&value).map_err(ParseOhttpKeysParamError::InvalidOhttpKeys)
5557
}
5658

5759
/// Set the ohttp parameter in the URL fragment
@@ -142,7 +144,24 @@ mod tests {
142144
url.set_ohttp(ohttp_keys.clone());
143145

144146
assert_eq!(url.fragment(), Some(serialized));
145-
assert_eq!(url.ohttp(), Some(ohttp_keys));
147+
assert_eq!(url.ohttp().unwrap(), ohttp_keys);
148+
}
149+
150+
#[test]
151+
fn test_errors_when_parsing_ohttp() {
152+
let missing_ohttp_url = Url::parse("https://example.com").unwrap();
153+
assert!(matches!(
154+
missing_ohttp_url.ohttp(),
155+
Err(ParseOhttpKeysParamError::MissingOhttpKeys)
156+
));
157+
158+
let invalid_ohttp_url =
159+
Url::parse("https://example.com?pj=https://test-payjoin-url#OH1invalid_bech_32")
160+
.unwrap();
161+
assert!(matches!(
162+
invalid_ohttp_url.ohttp(),
163+
Err(ParseOhttpKeysParamError::InvalidOhttpKeys(_))
164+
));
146165
}
147166

148167
#[test]
@@ -163,7 +182,7 @@ mod tests {
163182
&pjos=0&pj=HTTPS://EXAMPLE.COM/\
164183
%23OH1QYPM5JXYNS754Y4R45QWE336QFX6ZR8DQGVQCULVZTV20TFVEYDMFQC";
165184
let pjuri = Uri::try_from(uri).unwrap().assume_checked().check_pj_supported().unwrap();
166-
assert!(pjuri.extras.endpoint().ohttp().is_some());
185+
assert!(pjuri.extras.endpoint().ohttp().is_ok());
167186
assert_eq!(format!("{}", pjuri), uri);
168187

169188
let reordered = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=0.01\
@@ -172,7 +191,7 @@ mod tests {
172191
&pjos=0";
173192
let pjuri =
174193
Uri::try_from(reordered).unwrap().assume_checked().check_pj_supported().unwrap();
175-
assert!(pjuri.extras.endpoint().ohttp().is_some());
194+
assert!(pjuri.extras.endpoint().ohttp().is_ok());
176195
assert_eq!(format!("{}", pjuri), uri);
177196
}
178197
}

0 commit comments

Comments
 (0)