• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

payjoin / rust-payjoin / 12778584398

14 Jan 2025 11:59PM UTC coverage: 60.688% (-0.07%) from 60.758%
12778584398

Pull #482

github

web-flow
Merge d28133e80 into 34ee78d44
Pull Request #482: Initate receive v1,v2 error separation

45 of 165 new or added lines in 7 files covered. (27.27%)

3 existing lines in 2 files now uncovered.

2930 of 4828 relevant lines covered (60.69%)

958.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

22.3
/payjoin/src/receive/error.rs
1
use std::{error, fmt};
2

3
use crate::receive::v1;
4
#[cfg(feature = "v2")]
5
use crate::receive::v2;
6

7
#[derive(Debug)]
8
pub enum Error {
9
    /// Error arising from the payjoin state machine
10
    Validation(ValidationError),
11
    /// Error arising due to the specific receiver implementation
12
    External(Box<dyn error::Error + Send + Sync>),
13
}
14

15
impl Error {
16
    pub fn to_json(&self) -> String {
3✔
17
        match self {
3✔
18
            Self::Validation(e) => e.to_string(),
1✔
19
            Self::External(_) =>
20
                "{{ \"errorCode\": \"server-error\", \"message\": \"Internal server error\" }}"
2✔
21
                    .to_string(),
2✔
22
        }
23
    }
3✔
24
}
25

26
impl fmt::Display for Error {
27
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1✔
28
        match &self {
1✔
29
            Self::Validation(e) => e.fmt(f),
1✔
NEW
30
            Self::External(e) => write!(f, "Internal Server Error: {}", e),
×
31
        }
32
    }
1✔
33
}
34

35
impl error::Error for Error {
36
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
×
37
        match &self {
×
NEW
38
            Self::Validation(e) => e.source(),
×
NEW
39
            Self::External(e) => Some(e.as_ref()),
×
40
        }
41
    }
×
42
}
43

44
impl From<InternalPayloadError> for Error {
NEW
45
    fn from(e: InternalPayloadError) -> Self {
×
NEW
46
        Error::Validation(ValidationError::Payload(e.into()))
×
NEW
47
    }
×
48
}
49

50
impl From<v1::InternalRequestError> for Error {
NEW
51
    fn from(e: v1::InternalRequestError) -> Self { Error::Validation(e.into()) }
×
52
}
53

54
#[derive(Debug)]
55
pub enum ValidationError {
56
    Payload(PayloadError),
57
    V1(v1::RequestError),
58
    #[cfg(feature = "v2")]
59
    V2(v2::SessionError),
60
}
61

62
impl From<InternalPayloadError> for ValidationError {
63
    fn from(e: InternalPayloadError) -> Self { ValidationError::Payload(e.into()) }
1✔
64
}
65

66
impl From<v1::InternalRequestError> for ValidationError {
NEW
67
    fn from(e: v1::InternalRequestError) -> Self { ValidationError::V1(e.into()) }
×
68
}
69

70
#[cfg(feature = "v2")]
71
impl From<v2::InternalSessionError> for ValidationError {
72
    fn from(e: v2::InternalSessionError) -> Self { ValidationError::V2(e.into()) }
1✔
73
}
74

75
impl fmt::Display for ValidationError {
76
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2✔
77
        match self {
2✔
78
            ValidationError::Payload(e) => write!(f, "{}", e),
1✔
NEW
79
            ValidationError::V1(e) => write!(f, "{}", e),
×
80
            #[cfg(feature = "v2")]
81
            ValidationError::V2(e) => write!(f, "{}", e),
1✔
82
        }
83
    }
2✔
84
}
85

86
impl std::error::Error for ValidationError {
NEW
87
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
×
NEW
88
        match self {
×
NEW
89
            ValidationError::Payload(e) => Some(e),
×
NEW
90
            ValidationError::V1(e) => Some(e),
×
91
            #[cfg(feature = "v2")]
NEW
92
            ValidationError::V2(e) => Some(e),
×
93
        }
NEW
94
    }
×
95
}
96

97
#[derive(Debug)]
98
pub struct PayloadError(pub(crate) InternalPayloadError);
99

100
impl From<InternalPayloadError> for PayloadError {
101
    fn from(value: InternalPayloadError) -> Self { PayloadError(value) }
1✔
102
}
103

104
#[derive(Debug)]
105
pub(crate) enum InternalPayloadError {
106
    /// The payload is not valid utf-8
107
    Utf8(std::string::FromUtf8Error),
108
    /// The payload is not a valid PSBT
109
    ParsePsbt(bitcoin::psbt::PsbtParseError),
110
    /// Invalid sender parameters
111
    SenderParams(super::optional_parameters::Error),
112
    /// The raw PSBT fails bip78-specific validation.
113
    InconsistentPsbt(crate::psbt::InconsistentPsbt),
114
    /// The prevtxout is missing
115
    PrevTxOut(crate::psbt::PrevTxOutError),
116
    /// The Original PSBT has no output for the receiver.
117
    MissingPayment,
118
    /// The original PSBT transaction fails the broadcast check
119
    OriginalPsbtNotBroadcastable,
120
    #[allow(dead_code)]
121
    /// The sender is trying to spend the receiver input
122
    InputOwned(bitcoin::ScriptBuf),
123
    /// The expected input weight cannot be determined
124
    InputWeight(crate::psbt::InputWeightError),
125
    #[allow(dead_code)]
126
    /// Original PSBT input has been seen before. Only automatic receivers, aka "interactive" in the spec
127
    /// look out for these to prevent probing attacks.
128
    InputSeen(bitcoin::OutPoint),
129
    /// Original PSBT fee rate is below minimum fee rate set by the receiver.
130
    ///
131
    /// First argument is the calculated fee rate of the original PSBT.
132
    ///
133
    /// Second argument is the minimum fee rate optionaly set by the receiver.
134
    PsbtBelowFeeRate(bitcoin::FeeRate, bitcoin::FeeRate),
135
    /// Effective receiver feerate exceeds maximum allowed feerate
136
    FeeTooHigh(bitcoin::FeeRate, bitcoin::FeeRate),
137
}
138

139
impl fmt::Display for PayloadError {
140
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1✔
141
        use InternalPayloadError::*;
142

143
        fn write_error(
1✔
144
            f: &mut fmt::Formatter,
1✔
145
            code: &str,
1✔
146
            message: impl fmt::Display,
1✔
147
        ) -> fmt::Result {
1✔
148
            write!(f, r#"{{ "errorCode": "{}", "message": "{}" }}"#, code, message)
1✔
149
        }
1✔
150

151
        match &self.0 {
1✔
NEW
152
            Utf8(e) => write_error(f, "original-psbt-rejected", e),
×
NEW
153
            ParsePsbt(e) => write_error(f, "original-psbt-rejected", e),
×
NEW
154
            SenderParams(e) => match e {
×
155
                super::optional_parameters::Error::UnknownVersion { supported_versions } => {
×
156
                    write!(
×
157
                        f,
×
158
                        r#"{{
×
159
                            "errorCode": "version-unsupported",
×
160
                            "supported": "{}",
×
161
                            "message": "This version of payjoin is not supported."
×
162
                        }}"#,
×
163
                        serde_json::to_string(supported_versions).map_err(|_| fmt::Error)?
×
164
                    )
165
                }
166
                _ => write_error(f, "sender-params-error", e),
×
167
            },
NEW
168
            InconsistentPsbt(e) => write_error(f, "original-psbt-rejected", e),
×
NEW
169
            PrevTxOut(e) =>
×
UNCOV
170
                write_error(f, "original-psbt-rejected", format!("PrevTxOut Error: {}", e)),
×
171
            MissingPayment => write_error(f, "original-psbt-rejected", "Missing payment."),
1✔
NEW
172
            OriginalPsbtNotBroadcastable => write_error(
×
173
                f,
×
174
                "original-psbt-rejected",
×
175
                "Can't broadcast. PSBT rejected by mempool.",
×
176
            ),
×
177
            InputOwned(_) =>
178
                write_error(f, "original-psbt-rejected", "The receiver rejected the original PSBT."),
×
NEW
179
            InputWeight(e) =>
×
180
                write_error(f, "original-psbt-rejected", format!("InputWeight Error: {}", e)),
×
181
            InputSeen(_) =>
182
                write_error(f, "original-psbt-rejected", "The receiver rejected the original PSBT."),
×
NEW
183
            PsbtBelowFeeRate(original_psbt_fee_rate, receiver_min_fee_rate) => write_error(
×
184
                f,
×
185
                "original-psbt-rejected",
×
186
                format!(
×
187
                    "Original PSBT fee rate too low: {} < {}.",
×
188
                    original_psbt_fee_rate, receiver_min_fee_rate
×
189
                ),
×
190
            ),
×
NEW
191
            FeeTooHigh(proposed_feerate, max_feerate) => write_error(
×
192
                f,
×
193
                "original-psbt-rejected",
×
194
                format!(
×
195
                    "Effective receiver feerate exceeds maximum allowed feerate: {} > {}",
×
196
                    proposed_feerate, max_feerate
×
197
                ),
×
198
            ),
×
199
        }
200
    }
1✔
201
}
202

203
impl std::error::Error for PayloadError {
204
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
×
205
        use InternalPayloadError::*;
206
        match &self.0 {
×
NEW
207
            Utf8(e) => Some(e),
×
NEW
208
            ParsePsbt(e) => Some(e),
×
NEW
209
            SenderParams(e) => Some(e),
×
NEW
210
            InconsistentPsbt(e) => Some(e),
×
NEW
211
            PrevTxOut(e) => Some(e),
×
NEW
212
            InputWeight(e) => Some(e),
×
NEW
213
            PsbtBelowFeeRate(_, _) => None,
×
NEW
214
            FeeTooHigh(_, _) => None,
×
UNCOV
215
            _ => None,
×
216
        }
217
    }
×
218
}
219

220
/// Error that may occur when output substitution fails.
221
///
222
/// This is currently opaque type because we aren't sure which variants will stay.
223
/// You can only display it.
224
#[derive(Debug)]
225
pub struct OutputSubstitutionError(InternalOutputSubstitutionError);
226

227
#[derive(Debug)]
228
pub(crate) enum InternalOutputSubstitutionError {
229
    /// Output substitution is disabled
230
    OutputSubstitutionDisabled(&'static str),
231
    /// Current output substitution implementation doesn't support reducing the number of outputs
232
    NotEnoughOutputs,
233
    /// The provided drain script could not be identified in the provided replacement outputs
234
    InvalidDrainScript,
235
}
236

237
impl fmt::Display for OutputSubstitutionError {
238
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
239
        match &self.0 {
×
240
            InternalOutputSubstitutionError::OutputSubstitutionDisabled(reason) => write!(f, "{}", &format!("Output substitution is disabled: {}", reason)),
×
241
            InternalOutputSubstitutionError::NotEnoughOutputs => write!(
×
242
                f,
×
243
                "Current output substitution implementation doesn't support reducing the number of outputs"
×
244
            ),
×
245
            InternalOutputSubstitutionError::InvalidDrainScript =>
246
                write!(f, "The provided drain script could not be identified in the provided replacement outputs"),
×
247
        }
248
    }
×
249
}
250

251
impl From<InternalOutputSubstitutionError> for OutputSubstitutionError {
252
    fn from(value: InternalOutputSubstitutionError) -> Self { OutputSubstitutionError(value) }
×
253
}
254

255
impl std::error::Error for OutputSubstitutionError {
256
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
×
257
        match &self.0 {
×
258
            InternalOutputSubstitutionError::OutputSubstitutionDisabled(_) => None,
×
259
            InternalOutputSubstitutionError::NotEnoughOutputs => None,
×
260
            InternalOutputSubstitutionError::InvalidDrainScript => None,
×
261
        }
262
    }
×
263
}
264

265
/// Error that may occur when coin selection fails.
266
///
267
/// This is currently opaque type because we aren't sure which variants will stay.
268
/// You can only display it.
269
#[derive(Debug)]
270
pub struct SelectionError(InternalSelectionError);
271

272
#[derive(Debug)]
273
pub(crate) enum InternalSelectionError {
274
    /// No candidates available for selection
275
    Empty,
276
    /// Current privacy selection implementation only supports 2-output transactions
277
    TooManyOutputs,
278
    /// No selection candidates improve privacy
279
    NotFound,
280
}
281

282
impl fmt::Display for SelectionError {
283
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
284
        match &self.0 {
×
285
            InternalSelectionError::Empty => write!(f, "No candidates available for selection"),
×
286
            InternalSelectionError::TooManyOutputs => write!(
×
287
                f,
×
288
                "Current privacy selection implementation only supports 2-output transactions"
×
289
            ),
×
290
            InternalSelectionError::NotFound =>
291
                write!(f, "No selection candidates improve privacy"),
×
292
        }
293
    }
×
294
}
295

296
impl From<InternalSelectionError> for SelectionError {
297
    fn from(value: InternalSelectionError) -> Self { SelectionError(value) }
1✔
298
}
299

300
/// Error that may occur when input contribution fails.
301
///
302
/// This is currently opaque type because we aren't sure which variants will stay.
303
/// You can only display it.
304
#[derive(Debug)]
305
pub struct InputContributionError(InternalInputContributionError);
306

307
#[derive(Debug)]
308
pub(crate) enum InternalInputContributionError {
309
    /// The address type could not be determined
310
    AddressType(crate::psbt::AddressTypeError),
311
    /// The original PSBT has no inputs
312
    NoSenderInputs,
313
    /// The proposed receiver inputs would introduce mixed input script types
314
    MixedInputScripts(bitcoin::AddressType, bitcoin::AddressType),
315
    /// Total input value is not enough to cover additional output value
316
    ValueTooLow,
317
}
318

319
impl fmt::Display for InputContributionError {
320
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
321
        match &self.0 {
×
322
            InternalInputContributionError::AddressType(e) =>
×
323
                write!(f, "The address type could not be determined: {}", e),
×
324
            InternalInputContributionError::NoSenderInputs =>
325
                write!(f, "The original PSBT has no inputs"),
×
326
            InternalInputContributionError::MixedInputScripts(type_a, type_b) => write!(
×
327
                f,
×
328
                "The proposed receiver inputs would introduce mixed input script types: {}; {}.",
×
329
                type_a, type_b
×
330
            ),
×
331
            InternalInputContributionError::ValueTooLow =>
332
                write!(f, "Total input value is not enough to cover additional output value"),
×
333
        }
334
    }
×
335
}
336

337
impl From<InternalInputContributionError> for InputContributionError {
338
    fn from(value: InternalInputContributionError) -> Self { InputContributionError(value) }
2✔
339
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc