11use std:: { error, fmt} ;
22
3- use crate :: error_codes:: {
4- NOT_ENOUGH_MONEY , ORIGINAL_PSBT_REJECTED , UNAVAILABLE , VERSION_UNSUPPORTED ,
3+ use crate :: error_codes:: ErrorCode :: {
4+ self , NotEnoughMoney , OriginalPsbtRejected , Unavailable , VersionUnsupported ,
55} ;
66
77pub type ImplementationError = Box < dyn error:: Error + Send + Sync > ;
@@ -48,7 +48,8 @@ impl error::Error for Error {
4848/// 1. Provide structured error responses for protocol-level failures
4949/// 2. Hide implementation details of external errors for security
5050/// 3. Support proper error propagation through the receiver stack
51- /// 4. Provide errors according to BIP-78 JSON error specifications for return using [`JsonError::to_json`]
51+ /// 4. Provide errors according to BIP-78 JSON error specifications for return
52+ /// after conversion into [`JsonReply`]
5253#[ derive( Debug ) ]
5354pub enum ReplyableError {
5455 /// Error arising from validation of the original PSBT payload
@@ -62,41 +63,57 @@ pub enum ReplyableError {
6263 Implementation ( ImplementationError ) ,
6364}
6465
65- /// A trait for errors that can be serialized to JSON in a standardized format .
66+ /// The standard format for errors that can be replied as JSON.
6667///
67- /// The JSON output follows the structure :
68+ /// The JSON output includes the following fields :
6869/// ```json
6970/// {
7071/// "errorCode": "specific-error-code",
7172/// "message": "Human readable error message"
7273/// }
7374/// ```
74- pub trait JsonError {
75- /// Converts the error into a JSON string representation.
76- fn to_json ( & self ) -> String ;
75+ pub struct JsonReply {
76+ /// The error code
77+ error_code : ErrorCode ,
78+ /// The error message to be displayed only in debug logs
79+ message : String ,
80+ /// Additional fields to be included in the JSON response
81+ extra : serde_json:: Map < String , serde_json:: Value > ,
7782}
7883
79- impl JsonError for ReplyableError {
80- fn to_json ( & self ) -> String {
81- match self {
82- Self :: Payload ( e) => e. to_json ( ) ,
83- #[ cfg( feature = "v1" ) ]
84- Self :: V1 ( e) => e. to_json ( ) ,
85- Self :: Implementation ( _) => serialize_json_error ( UNAVAILABLE , "Receiver error" ) ,
86- }
84+ impl JsonReply {
85+ /// Create a new Reply
86+ pub fn new ( error_code : ErrorCode , message : impl fmt:: Display ) -> Self {
87+ Self { error_code, message : message. to_string ( ) , extra : serde_json:: Map :: new ( ) }
8788 }
88- }
8989
90- pub ( crate ) fn serialize_json_error ( code : & str , message : impl fmt:: Display ) -> String {
91- format ! ( r#"{{ "errorCode": "{}", "message": "{}" }}"# , code, message)
90+ /// Add an additional field to the JSON response
91+ pub fn with_extra ( mut self , key : & str , value : impl Into < serde_json:: Value > ) -> Self {
92+ self . extra . insert ( key. to_string ( ) , value. into ( ) ) ;
93+ self
94+ }
95+
96+ /// Serialize the Reply to a JSON string
97+ pub fn to_json ( & self ) -> serde_json:: Value {
98+ let mut map = serde_json:: Map :: new ( ) ;
99+ map. insert ( "errorCode" . to_string ( ) , self . error_code . to_string ( ) . into ( ) ) ;
100+ map. insert ( "message" . to_string ( ) , self . message . clone ( ) . into ( ) ) ;
101+ map. extend ( self . extra . clone ( ) ) ;
102+
103+ serde_json:: Value :: Object ( map)
104+ }
92105}
93106
94- pub ( crate ) fn serialize_json_plus_fields (
95- code : & str ,
96- message : impl fmt:: Display ,
97- additional_fields : & str ,
98- ) -> String {
99- format ! ( r#"{{ "errorCode": "{}", "message": "{}", {} }}"# , code, message, additional_fields)
107+ impl From < ReplyableError > for JsonReply {
108+ fn from ( e : ReplyableError ) -> Self {
109+ use ReplyableError :: * ;
110+ match e {
111+ Payload ( e) => e. into ( ) ,
112+ #[ cfg( feature = "v1" ) ]
113+ V1 ( e) => e. into ( ) ,
114+ Implementation ( _) => JsonReply :: new ( Unavailable , "Receiver error" ) ,
115+ }
116+ }
100117}
101118
102119impl fmt:: Display for ReplyableError {
@@ -180,34 +197,34 @@ pub(crate) enum InternalPayloadError {
180197 FeeTooHigh ( bitcoin:: FeeRate , bitcoin:: FeeRate ) ,
181198}
182199
183- impl JsonError for PayloadError {
184- fn to_json ( & self ) -> String {
200+ impl From < PayloadError > for JsonReply {
201+ fn from ( e : PayloadError ) -> Self {
185202 use InternalPayloadError :: * ;
186203
187- match & self . 0 {
188- Utf8 ( _) => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
189- ParsePsbt ( _) => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
204+ match & e. 0 {
205+ Utf8 ( _)
206+ | ParsePsbt ( _)
207+ | InconsistentPsbt ( _)
208+ | PrevTxOut ( _)
209+ | MissingPayment
210+ | OriginalPsbtNotBroadcastable
211+ | InputOwned ( _)
212+ | InputWeight ( _)
213+ | InputSeen ( _)
214+ | PsbtBelowFeeRate ( _, _) => JsonReply :: new ( OriginalPsbtRejected , e) ,
215+
216+ FeeTooHigh ( _, _) => JsonReply :: new ( NotEnoughMoney , e) ,
217+
190218 SenderParams ( e) => match e {
191219 super :: optional_parameters:: Error :: UnknownVersion { supported_versions } => {
192220 let supported_versions_json =
193221 serde_json:: to_string ( supported_versions) . unwrap_or_default ( ) ;
194- serialize_json_plus_fields (
195- VERSION_UNSUPPORTED ,
196- "This version of payjoin is not supported." ,
197- & format ! ( r#""supported": {}"# , supported_versions_json) ,
198- )
222+ JsonReply :: new ( VersionUnsupported , "This version of payjoin is not supported." )
223+ . with_extra ( "supported" , supported_versions_json)
199224 }
200- _ => serialize_json_error ( "sender-params-error" , self ) ,
225+ super :: optional_parameters:: Error :: FeeRate =>
226+ JsonReply :: new ( OriginalPsbtRejected , e) ,
201227 } ,
202- InconsistentPsbt ( _) => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
203- PrevTxOut ( _) => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
204- MissingPayment => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
205- OriginalPsbtNotBroadcastable => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
206- InputOwned ( _) => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
207- InputWeight ( _) => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
208- InputSeen ( _) => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
209- PsbtBelowFeeRate ( _, _) => serialize_json_error ( ORIGINAL_PSBT_REJECTED , self ) ,
210- FeeTooHigh ( _, _) => serialize_json_error ( NOT_ENOUGH_MONEY , self ) ,
211228 }
212229 }
213230}
0 commit comments