@@ -4,7 +4,7 @@ use headers::Header;
44use http:: response:: Parts ;
55use std:: { future:: Future , pin:: Pin } ;
66
7- use crate :: { ClientRequest , ClientResponse } ;
7+ use crate :: { ClientRequest , ClientResponse , ErrorPayload } ;
88
99/// The `IntoRequest` trait allows types to be used as the body of a request to a HTTP endpoint or server function.
1010///
5454{
5555 fn from_response ( res : ClientResponse ) -> impl Future < Output = Result < Self , ServerFnError > > {
5656 async move {
57- let ( parts, _body) = res. into_parts ( ) ;
58- let mut parts = parts;
59- A :: from_response_parts ( & mut parts)
57+ let status = res. status ( ) ;
58+
59+ if status. is_success ( ) {
60+ let ( parts, _body) = res. into_parts ( ) ;
61+ let mut parts = parts;
62+ A :: from_response_parts ( & mut parts)
63+ } else {
64+ let ErrorPayload :: < serde_json:: Value > {
65+ message,
66+ code,
67+ data,
68+ } = res. json ( ) . await ?;
69+ Err ( ServerFnError :: ServerError {
70+ message,
71+ code,
72+ details : data,
73+ } )
74+ }
6075 }
6176 }
6277}
@@ -202,3 +217,80 @@ pub fn assert_can_encode(_t: impl AssertCanEncode) {}
202217
203218#[ doc( hidden) ]
204219pub fn assert_can_decode ( _t : impl AssertCanDecode ) { }
220+
221+ #[ cfg( test) ]
222+ mod test {
223+ use http:: Extensions ;
224+
225+ use super :: * ;
226+
227+ #[ derive( Debug ) ]
228+ struct TestFromResponse ;
229+
230+ impl FromResponseParts for TestFromResponse {
231+ fn from_response_parts ( _parts : & mut Parts ) -> Result < Self , ServerFnError > {
232+ Ok ( Self )
233+ }
234+ }
235+
236+ fn build_response ( status : u16 , body : String ) -> ClientResponse {
237+ let http_response = http:: Response :: builder ( )
238+ . status ( status)
239+ . body ( body. into_bytes ( ) )
240+ . unwrap ( ) ;
241+ let reqwest_response = reqwest:: Response :: from ( http_response) ;
242+ ClientResponse {
243+ response : Box :: new ( reqwest_response) ,
244+ extensions : Extensions :: new ( ) ,
245+ }
246+ }
247+
248+ #[ test]
249+ fn fromresponseparts_path_decodes_ok_on_2xx ( ) {
250+ futures:: executor:: block_on ( async {
251+ let response = build_response ( 200 , "" . to_string ( ) ) ;
252+ let result = TestFromResponse :: from_response ( response) . await ;
253+ assert ! (
254+ result. is_ok( ) ,
255+ "expected Ok(..) for HTTP 200 success case, got: {:?}" ,
256+ result
257+ ) ;
258+ } ) ;
259+ }
260+
261+ #[ test]
262+ fn fromresponseparts_falls_back_to_request_error_on_unparsable_error_body ( ) {
263+ futures:: executor:: block_on ( async {
264+ let response = build_response ( 400 , "" . to_string ( ) ) ;
265+ let result = TestFromResponse :: from_response ( response) . await ;
266+ assert ! ( result. is_err( ) , "expected Err(..) for HTTP 400 failed case" ) ;
267+ let error = result. unwrap_err ( ) ;
268+ assert ! ( matches!(
269+ error,
270+ ServerFnError :: Request ( RequestError :: Decode ( _) )
271+ ) ) ;
272+ } ) ;
273+ }
274+
275+ #[ test]
276+ fn fromresponseparts_parses_error_payload_on_http_error ( ) {
277+ futures:: executor:: block_on ( async {
278+ let body = r#"{
279+ "message": "qwerty",
280+ "code": 400
281+ }"# ;
282+ let response = build_response ( 400 , body. to_string ( ) ) ;
283+ let result = TestFromResponse :: from_response ( response) . await ;
284+ assert ! ( result. is_err( ) , "expected Err(..) for HTTP 400 failed case" ) ;
285+ let error = result. unwrap_err ( ) ;
286+ assert_eq ! (
287+ error,
288+ ServerFnError :: ServerError {
289+ message: "qwerty" . to_string( ) ,
290+ code: 400 ,
291+ details: None
292+ }
293+ ) ;
294+ } ) ;
295+ }
296+ }
0 commit comments