Skip to content

Commit

Permalink
errorCodes without serde
Browse files Browse the repository at this point in the history
  • Loading branch information
jbesraa committed Dec 12, 2023
1 parent 406a523 commit 7b8cba0
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 17 deletions.
7 changes: 6 additions & 1 deletion payjoin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ bhttp = { version = "0.4.0", optional = true }
rand = { version = "0.8.4", optional = true }
serde = { version = "1.0.186", default-features = false }
url = "2.2.2"
bitcoind = { version = "0.31.1", features = ["0_21_2"] }

[dev-dependencies]
bitcoind = { version = "0.31.1", features = ["0_21_2"] }
env_logger = "0.9.0"
rustls = "0.21.9"
testcontainers = "0.15.0"
Expand All @@ -42,3 +42,8 @@ ureq = "2.8.0"

[package.metadata.docs.rs]
features = ["send", "receive", "base64"]
env_logger = "0.9.0"


[lib]
doctest = false
2 changes: 1 addition & 1 deletion payjoin/src/receive/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::fmt::{self, Display};
pub enum Error {
/// To be returned as HTTP 400
BadRequest(RequestError),
// To be returned as HTTP 500
/// To be returned as HTTP 500
Server(Box<dyn error::Error>),
// V2 d/encapsulation failed
#[cfg(feature = "v2")]
Expand Down
71 changes: 63 additions & 8 deletions payjoin/src/send/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::str::FromStr;

use bitcoin::locktime::absolute::LockTime;
use bitcoin::Sequence;
use bitcoind::bitcoincore_rpc::jsonrpc::serde_json::Value;

use crate::input_type::{InputType, InputTypeError};

Expand Down Expand Up @@ -238,18 +239,19 @@ impl From<InternalCreateRequestError> for CreateRequestError {
}

pub enum ResponseError {
/// `WellKnown` errors following the BIP78 spec
/// https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki#user-content-Receivers_well_known_errors
/// `WellKnown` errors following the BIP78 spec.
///
/// These errors are displayed to end users.
///
/// The `WellKnownError` represents `errorCode` and `message`.
/// The `String` is a custom message that can be used for debug logs.
/// `WellKnownError` represents `errorCode` and `message`.
/// `String` is a custom message that can be used for debug logs.
WellKnown(WellKnownError, Option<String>),
/// `Unrecognized` errors are errors that are not well known and are only displayed in debug logs.
/// `Unrecognized` errors are errors that are not
/// mentioned in the spec and are only displayed in debug logs.
/// They are not displayed to end users.
///
/// The first `String` is `errorCode`
/// The second `String` is `message`.
/// First `String` is `errorCode`
/// Second `String` is `message`.
Unrecognized(String, String),
/// `Validation` errors are errors that are caused by malformed responses.
/// They are only displayed in debug logs.
Expand Down Expand Up @@ -281,7 +283,8 @@ impl Debug for ResponseError {
f,
r#"Well known error: {{ "errorCode": "{}",
"message": "{}" }}"#,
e, msg
e,
msg.clone().unwrap_or("".to_string())
),
Self::Unrecognized(code, msg) => write!(
f,
Expand All @@ -293,6 +296,8 @@ impl Debug for ResponseError {
}
}

impl std::error::Error for ResponseError {}

pub enum WellKnownError {
Unavailable,
NotEnoughMoney,
Expand Down Expand Up @@ -335,4 +340,54 @@ impl WellKnownError {
Self::OriginalPsbtRejected => "The receiver rejected the original PSBT.",
}
}

pub fn error_code_from_json(json: Value) -> Result<Self, ResponseError> {
let error_code = json
.as_object()
.and_then(|v| v.get("errorCode"))
.and_then(|v| v.as_str())
.ok_or(InternalValidationError::Parse)?;
WellKnownError::from_str(error_code).map_err(|_| {
let message = match json.get("message") {
Some(v) => v.to_string(),
None => {
log::debug!("Unrecognized Error detected, {}", json);
"Unrecognized Error detected".to_string()
}
};
ResponseError::Unrecognized(error_code.to_string(), message)
})
}

pub fn error_code_from_str(error_code: &str) -> Result<Self, ResponseError> {
Self::error_code_from_json(Value::from_str(&error_code).map_err(|e| {
log::debug!("Invalid json detected, {}", e);
InternalValidationError::Parse
})?)
}
}

#[cfg(test)]
mod tests {
use bitcoind::bitcoincore_rpc::jsonrpc::serde_json::json;

use super::*;

#[test]
fn test_parse_json() {
let json_error = json!({
"errorCode": "version-unsupported",
"message": "This version of payjoin is not supported."
});
assert_eq!(
WellKnownError::error_code_from_json(json_error).unwrap().to_string(),
WellKnownError::VersionUnsupported.to_string()
);
let str_error = "{\"errorCode\":\"version-unsupported\",
\"message\":\"This version of payjoin is not supported.\"}";
assert_eq!(
WellKnownError::error_code_from_str(str_error).unwrap().to_string(),
WellKnownError::VersionUnsupported.to_string()
);
}
}
16 changes: 9 additions & 7 deletions payjoin/src/send/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,19 +155,17 @@ use crate::PjUri;
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
compile_error!("This crate currently only supports 32 bit and 64 bit architectures");

mod error;
pub mod error;

type InternalResult<T> = Result<T, InternalValidationError>;

#[derive(Debug, Clone)]
pub struct RequestBuilder<'a> {
psbt: Psbt,
uri: PjUri<'a>,
disable_output_substitution: bool,
fee_contribution: Option<(bitcoin::Amount, Option<usize>)>,
clamp_fee_contribution: bool,
min_fee_rate: FeeRate,
version: usize,
}

impl<'a> RequestBuilder<'a> {
Expand All @@ -179,7 +177,6 @@ impl<'a> RequestBuilder<'a> {
pub fn from_psbt_and_uri(
psbt: Psbt,
uri: crate::Uri<'a, NetworkChecked>,
version: usize,
) -> Result<Self, CreateRequestError> {
let uri = uri
.check_pj_supported()
Expand All @@ -191,7 +188,6 @@ impl<'a> RequestBuilder<'a> {
uri,
// Sender's optional parameters
disable_output_substitution,
version,
fee_contribution: None,
clamp_fee_contribution: false,
min_fee_rate: FeeRate::ZERO,
Expand Down Expand Up @@ -366,7 +362,7 @@ impl<'a> RequestContext<'a> {
self.disable_output_substitution,
self.fee_contribution,
self.min_fee_rate,
self.version,
1,
)
.map_err(InternalCreateRequestError::Url)?;
let body = self.psbt.to_string().as_bytes().to_vec();
Expand Down Expand Up @@ -543,7 +539,7 @@ impl ContextV1 {
let proposal = Psbt::from_str(&res_str).or_else(|_| {
// That wasn't a valid PSBT. Maybe it's a valid error response?
match WellKnownError::error_code_from_str(&res_str) {
Ok(well_known) => return Err(ResponseError::WellKnown(well_known, "".to_string())),
Ok(well_known) => return Err(ResponseError::WellKnown(well_known, None)),
Err(e) => Err(e),
}
})?;
Expand Down Expand Up @@ -914,6 +910,7 @@ fn serialize_v2_body(
disable_output_substitution,
fee_contribution,
min_feerate,
2,
)
.map_err(InternalCreateRequestError::Url)?;
let query_params = placeholder_url.query().unwrap_or_default();
Expand Down Expand Up @@ -946,6 +943,11 @@ fn serialize_url(
Ok(url)
}

pub fn serialize_psbt(psbt: &Psbt) -> Vec<u8> {
let bytes = psbt.serialize();
bitcoin::base64::encode(bytes).into_bytes()
}

#[cfg(test)]
mod tests {
#[test]
Expand Down

0 comments on commit 7b8cba0

Please sign in to comment.