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
96 changes: 96 additions & 0 deletions payjoin-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,99 @@ impl From<uniffi::UnexpectedUniFFICallbackError> for ForeignError {
Self::InternalError("Unexpected Uniffi callback error".to_string())
}
}

#[derive(Debug, Clone, thiserror::Error, uniffi::Object)]
#[error("PSBT input #{index} validation failed: {message}")]
pub struct PsbtInputsError {
index: usize,
message: String,
}

impl PsbtInputsError {
/// Create a new PsbtInputsError
pub fn new(index: usize, message: String) -> Self { Self { index, message } }
}

#[uniffi::export]
impl PsbtInputsError {
pub fn input_index(&self) -> u64 { self.index as u64 }

pub fn error_message(&self) -> String { self.message.clone() }
}

impl From<payjoin::psbt::PsbtInputsError> for PsbtInputsError {
fn from(value: payjoin::psbt::PsbtInputsError) -> Self {
let index = value.index();
let message = if let Some(source) = std::error::Error::source(&value) {
source.to_string()
} else {
value.to_string()
};

PsbtInputsError { index, message }
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_psbt_inputs_error_preserves_index() {
let error = PsbtInputsError::new(5, "Missing UTXO information".to_string());

assert_eq!(error.input_index(), 5);
assert_eq!(error.error_message(), "Missing UTXO information");

let error_string = error.to_string();
assert!(error_string.contains("input #5"));
assert!(error_string.contains("Missing UTXO"));
}

#[test]
fn test_psbt_inputs_error_index_zero() {
let error = PsbtInputsError::new(0, "Invalid previous transaction output".to_string());

assert_eq!(error.input_index(), 0);
assert!(error.error_message().contains("Invalid previous transaction output"));
}

#[test]
fn test_psbt_inputs_error_large_index() {
let error = PsbtInputsError::new(999, "Test error".to_string());

assert_eq!(error.input_index(), 999);
assert_eq!(error.error_message(), "Test error");
}

#[test]
fn test_error_display_format() {
let error = PsbtInputsError::new(7, "missing UTXO information".to_string());
let display = format!("{}", error);

assert!(display.contains("#7"));
assert!(display.contains("missing UTXO information"));
}

#[test]
fn test_error_clone() {
let error = PsbtInputsError::new(2, "Test message".to_string());
let cloned = error.clone();

assert_eq!(error.input_index(), cloned.input_index());
assert_eq!(error.error_message(), cloned.error_message());
}

#[test]
fn test_multiple_errors_different_indices() {
let errors = [
PsbtInputsError::new(0, "Error at input 0".to_string()),
PsbtInputsError::new(1, "Error at input 1".to_string()),
PsbtInputsError::new(2, "Error at input 2".to_string()),
];

for (expected_index, error) in errors.iter().enumerate() {
assert_eq!(error.input_index(), expected_index as u64);
}
}
}
11 changes: 10 additions & 1 deletion payjoin-ffi/src/receive/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::sync::Arc;

use payjoin::receive;

use crate::error::ImplementationError;
use crate::error::{ImplementationError, PsbtInputsError};
use crate::uri::error::IntoUrlError;

/// The top-level error type for the payjoin receiver
Expand All @@ -20,11 +20,20 @@ pub enum ReceiverError {
/// Error that may occur when converting a some type to a URL
#[error("IntoUrl error: {0}")]
IntoUrl(Arc<IntoUrlError>),

#[error("PSBT input validation failed")]
InvalidInput(Arc<PsbtInputsError>),
/// Catch-all for unhandled error variants
#[error("An unexpected error occurred")]
Unexpected,
}

impl From<payjoin::psbt::PsbtInputsError> for ReceiverError {
fn from(value: payjoin::psbt::PsbtInputsError) -> Self {
ReceiverError::InvalidInput(Arc::new(value.into()))
}
}

impl From<receive::Error> for ReceiverError {
fn from(value: receive::Error) -> Self {
use ReceiverError::*;
Expand Down
34 changes: 25 additions & 9 deletions payjoin-ffi/src/send/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,35 @@ use std::sync::Arc;
use payjoin::bitcoin::psbt::PsbtParseError;
use payjoin::send;

use crate::error::ImplementationError;
use crate::error::{ImplementationError, PsbtInputsError};

/// Error building a Sender from a SenderBuilder.
///
/// This error is unrecoverable.
#[derive(Debug, PartialEq, Eq, thiserror::Error, uniffi::Object)]
#[error("Error initializing the sender: {msg}")]
pub struct BuildSenderError {
msg: String,
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum BuildSenderError {
#[error("Error initializing the sender: {0}")]
Generic(String),

#[error("PSBT input validation failed")]
InvalidInput(Arc<PsbtInputsError>),

#[error("PSBT parse error: {0}")]
PsbtParse(String),
}

impl From<payjoin::psbt::PsbtInputsError> for BuildSenderError {
fn from(value: payjoin::psbt::PsbtInputsError) -> Self {
BuildSenderError::InvalidInput(Arc::new(value.into()))
}
}

impl From<PsbtParseError> for BuildSenderError {
fn from(value: PsbtParseError) -> Self { BuildSenderError { msg: value.to_string() } }
fn from(value: PsbtParseError) -> Self { BuildSenderError::PsbtParse(value.to_string()) }
}

impl From<send::BuildSenderError> for BuildSenderError {
fn from(value: send::BuildSenderError) -> Self { BuildSenderError { msg: value.to_string() } }
fn from(value: send::BuildSenderError) -> Self { BuildSenderError::Generic(value.to_string()) }
}

/// Error returned when request could not be created.
Expand Down Expand Up @@ -101,7 +113,7 @@ pub enum SenderPersistedError {
ResponseError(ResponseError),
/// Sender Build error
#[error(transparent)]
BuildSenderError(Arc<BuildSenderError>),
BuildSenderError(BuildSenderError),
/// Storage error that could occur at application storage layer
#[error(transparent)]
Storage(Arc<ImplementationError>),
Expand All @@ -110,6 +122,10 @@ pub enum SenderPersistedError {
Unexpected,
}

impl From<BuildSenderError> for SenderPersistedError {
fn from(value: BuildSenderError) -> Self { SenderPersistedError::BuildSenderError(value) }
}

impl From<ImplementationError> for SenderPersistedError {
fn from(value: ImplementationError) -> Self { SenderPersistedError::Storage(Arc::new(value)) }
}
Expand Down Expand Up @@ -163,7 +179,7 @@ where
return SenderPersistedError::Unexpected;
}
if let Some(api_err) = err.api_error() {
return SenderPersistedError::BuildSenderError(Arc::new(api_err.into()));
return SenderPersistedError::BuildSenderError(api_err.into());
}
SenderPersistedError::Unexpected
}
Expand Down
2 changes: 1 addition & 1 deletion payjoin/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub mod error;
pub use error::ImplementationError;
pub mod version;
pub use version::Version;
pub(crate) mod psbt;
pub mod psbt;
pub mod receive;
mod request;
pub mod send;
Expand Down
6 changes: 5 additions & 1 deletion payjoin/src/core/psbt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ impl InternalInputPair<'_> {
}

#[derive(Debug, PartialEq, Eq)]
pub(crate) enum PrevTxOutError {
pub enum PrevTxOutError {
MissingUtxoInformation,
IndexOutOfBounds { output_count: usize, index: u32 },
}
Expand Down Expand Up @@ -347,6 +347,10 @@ pub struct PsbtInputsError {
error: InternalPsbtInputError,
}

impl PsbtInputsError {
pub fn index(&self) -> usize { self.index }
}

impl fmt::Display for PsbtInputsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid PSBT input #{}", self.index)
Expand Down
Loading