1+ use std:: str:: FromStr ;
12use std:: { error, fmt} ;
23
4+ use serde:: Deserialize ;
5+
36use crate :: error_codes:: ErrorCode :: {
47 self , NotEnoughMoney , OriginalPsbtRejected , Unavailable , VersionUnsupported ,
58} ;
@@ -93,6 +96,47 @@ impl JsonReply {
9396 Self { error_code, message : message. to_string ( ) , extra : serde_json:: Map :: new ( ) }
9497 }
9598
99+ /// Parse a reply from the BIP78 wire JSON shape.
100+ pub fn from_json ( json : serde_json:: Value ) -> Result < Self , serde_json:: Error > {
101+ #[ derive( serde:: Deserialize ) ]
102+ struct WireJsonReply {
103+ #[ serde( rename = "errorCode" ) ]
104+ error_code : String ,
105+ message : String ,
106+ #[ serde( default , deserialize_with = "deserialize_supported_versions" ) ]
107+ supported : Option < Vec < u64 > > ,
108+ #[ serde( flatten) ]
109+ extra : serde_json:: Map < String , serde_json:: Value > ,
110+ }
111+
112+ fn deserialize_supported_versions < ' de , D > (
113+ deserializer : D ,
114+ ) -> Result < Option < Vec < u64 > > , D :: Error >
115+ where
116+ D : serde:: Deserializer < ' de > ,
117+ {
118+ use serde:: de:: Error as _;
119+
120+ let supported = Option :: < serde_json:: Value > :: deserialize ( deserializer) ?;
121+ parse_supported_versions_value ( supported. as_ref ( ) ) . map_err ( D :: Error :: custom)
122+ }
123+
124+ let wire: WireJsonReply = serde_json:: from_value ( json) ?;
125+ let error_code = ErrorCode :: from_str ( & wire. error_code ) . map_err ( |( ) | {
126+ <serde_json:: Error as serde:: de:: Error >:: custom ( format ! (
127+ "invalid errorCode: {}" ,
128+ wire. error_code
129+ ) )
130+ } ) ?;
131+
132+ let mut reply = Self :: new ( error_code, wire. message ) ;
133+ if let Some ( supported) = wire. supported {
134+ reply = reply. with_extra ( "supported" , serde_json:: to_value ( supported) ?) ;
135+ }
136+ reply. extra . extend ( wire. extra ) ;
137+ Ok ( reply)
138+ }
139+
96140 /// Add an additional field to the JSON response
97141 pub fn with_extra ( mut self , key : & str , value : impl Into < serde_json:: Value > ) -> Self {
98142 self . extra . insert ( key. to_string ( ) , value. into ( ) ) ;
@@ -119,6 +163,45 @@ impl JsonReply {
119163 }
120164 . as_u16 ( )
121165 }
166+
167+ /// Return the wire-format error code, such as `version-unsupported`.
168+ pub fn error_code ( & self ) -> String { self . error_code . to_string ( ) }
169+
170+ /// Return the human-readable message.
171+ pub fn message ( & self ) -> & str { & self . message }
172+
173+ /// Return the supported versions when present.
174+ pub fn supported_versions ( & self ) -> Option < Vec < u64 > > {
175+ self . extra
176+ . get ( "supported" )
177+ . and_then ( |value| parse_supported_versions_value ( Some ( value) ) . ok ( ) . flatten ( ) )
178+ }
179+ }
180+
181+ fn parse_supported_versions_value (
182+ value : Option < & serde_json:: Value > ,
183+ ) -> Result < Option < Vec < u64 > > , String > {
184+ use serde_json:: Value ;
185+
186+ let Some ( value) = value else {
187+ return Ok ( None ) ;
188+ } ;
189+
190+ match value {
191+ Value :: Array ( items) => items
192+ . iter ( )
193+ . map ( |item| {
194+ item. as_u64 ( )
195+ . ok_or_else ( || "supported versions must be an array of u64 values" . to_string ( ) )
196+ } )
197+ . collect :: < Result < Vec < _ > , _ > > ( )
198+ . map ( Some ) ,
199+ // Backward compatibility for the old broken wire shape where `supported`
200+ // was serialized as a JSON string containing the array.
201+ Value :: String ( json) =>
202+ serde_json:: from_str :: < Vec < u64 > > ( json) . map ( Some ) . map_err ( |err| err. to_string ( ) ) ,
203+ other => Err ( format ! ( "unsupported versions must be an array or JSON string, got {other}" ) ) ,
204+ }
122205}
123206
124207impl From < & ProtocolError > for JsonReply {
@@ -235,12 +318,12 @@ impl From<&PayloadError> for JsonReply {
235318 FeeTooHigh ( _, _) => JsonReply :: new ( NotEnoughMoney , e) ,
236319
237320 SenderParams ( e) => match e {
238- super :: optional_parameters:: Error :: UnknownVersion { supported_versions } => {
239- let supported_versions_json =
240- serde_json:: to_string ( supported_versions) . unwrap_or_default ( ) ;
321+ super :: optional_parameters:: Error :: UnknownVersion { supported_versions } =>
241322 JsonReply :: new ( VersionUnsupported , "This version of payjoin is not supported." )
242- . with_extra ( "supported" , supported_versions_json)
243- }
323+ . with_extra (
324+ "supported" ,
325+ serde_json:: to_value ( supported_versions) . unwrap_or_default ( ) ,
326+ ) ,
244327 super :: optional_parameters:: Error :: FeeRate =>
245328 JsonReply :: new ( OriginalPsbtRejected , e) ,
246329 } ,
@@ -504,4 +587,43 @@ mod tests {
504587 assert_eq ! ( json[ "errorCode" ] , "original-psbt-rejected" ) ;
505588 assert_eq ! ( json[ "message" ] , "Missing payment." ) ;
506589 }
590+
591+ #[ test]
592+ fn test_json_reply_supported_versions_are_arrays ( ) {
593+ let supported_versions = & [ crate :: Version :: One , crate :: Version :: Two ] ;
594+ let reply = JsonReply :: new ( ErrorCode :: VersionUnsupported , "unsupported" )
595+ . with_extra ( "supported" , serde_json:: to_value ( supported_versions) . unwrap ( ) ) ;
596+
597+ assert_eq ! ( reply. supported_versions( ) , Some ( vec![ 1 , 2 ] ) ) ;
598+ assert_eq ! (
599+ reply. to_json( ) ,
600+ serde_json:: json!( {
601+ "errorCode" : "version-unsupported" ,
602+ "message" : "unsupported" ,
603+ "supported" : [ 1 , 2 ] ,
604+ } )
605+ ) ;
606+ }
607+
608+ #[ test]
609+ fn test_json_reply_from_json_accepts_legacy_supported_string ( ) {
610+ let reply = JsonReply :: from_json ( serde_json:: json!( {
611+ "errorCode" : "version-unsupported" ,
612+ "message" : "unsupported" ,
613+ "supported" : "[1,2]" ,
614+ } ) )
615+ . expect ( "legacy supported string should parse" ) ;
616+
617+ assert_eq ! ( reply. error_code( ) , "version-unsupported" ) ;
618+ assert_eq ! ( reply. message( ) , "unsupported" ) ;
619+ assert_eq ! ( reply. supported_versions( ) , Some ( vec![ 1 , 2 ] ) ) ;
620+ assert_eq ! (
621+ reply. to_json( ) ,
622+ serde_json:: json!( {
623+ "errorCode" : "version-unsupported" ,
624+ "message" : "unsupported" ,
625+ "supported" : [ 1 , 2 ] ,
626+ } )
627+ ) ;
628+ }
507629}
0 commit comments