@@ -5,6 +5,7 @@ use chunked_transfer;
5
5
use serde_json;
6
6
7
7
use std:: convert:: TryFrom ;
8
+ use std:: fmt;
8
9
#[ cfg( not( feature = "tokio" ) ) ]
9
10
use std:: io:: Write ;
10
11
use std:: net:: ToSocketAddrs ;
@@ -348,21 +349,33 @@ impl HttpClient {
348
349
349
350
if !status. is_ok ( ) {
350
351
// TODO: Handle 3xx redirection responses.
351
- let error_details = match String :: from_utf8 ( contents) {
352
- // Check that the string is all-ASCII with no control characters before returning
353
- // it.
354
- Ok ( s) if s. as_bytes ( ) . iter ( ) . all ( |c| c. is_ascii ( ) && !c. is_ascii_control ( ) ) => s,
355
- _ => "binary" . to_string ( )
352
+ let error = HttpError {
353
+ status_code : status. code . to_string ( ) ,
354
+ contents,
356
355
} ;
357
- let error_msg = format ! ( "Errored with status: {} and contents: {}" ,
358
- status. code, error_details) ;
359
- return Err ( std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , error_msg) ) ;
356
+ return Err ( std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , error) ) ;
360
357
}
361
358
362
359
Ok ( contents)
363
360
}
364
361
}
365
362
363
+ /// HTTP error consisting of a status code and body contents.
364
+ #[ derive( Debug ) ]
365
+ pub ( crate ) struct HttpError {
366
+ pub ( crate ) status_code : String ,
367
+ pub ( crate ) contents : Vec < u8 > ,
368
+ }
369
+
370
+ impl std:: error:: Error for HttpError { }
371
+
372
+ impl fmt:: Display for HttpError {
373
+ fn fmt ( & self , f : & mut fmt:: Formatter ) -> fmt:: Result {
374
+ let contents = String :: from_utf8_lossy ( & self . contents ) ;
375
+ write ! ( f, "status_code: {}, contents: {}" , self . status_code, contents)
376
+ }
377
+ }
378
+
366
379
/// HTTP response status code as defined by [RFC 7231].
367
380
///
368
381
/// [RFC 7231]: https://tools.ietf.org/html/rfc7231#section-6
@@ -538,16 +551,16 @@ pub(crate) mod client_tests {
538
551
}
539
552
540
553
impl HttpServer {
541
- pub fn responding_with_ok < T : ToString > ( body : MessageBody < T > ) -> Self {
554
+ fn responding_with_body < T : ToString > ( status : & str , body : MessageBody < T > ) -> Self {
542
555
let response = match body {
543
- MessageBody :: Empty => "HTTP/1.1 200 OK \r \n \r \n ". to_string ( ) ,
556
+ MessageBody :: Empty => format ! ( "{} \r \n \r \n ", status ) ,
544
557
MessageBody :: Content ( body) => {
545
558
let body = body. to_string ( ) ;
546
559
format ! (
547
- "HTTP/1.1 200 OK \r \n \
560
+ "{} \r \n \
548
561
Content-Length: {}\r \n \
549
562
\r \n \
550
- {}", body. len( ) , body)
563
+ {}", status , body. len( ) , body)
551
564
} ,
552
565
MessageBody :: ChunkedContent ( body) => {
553
566
let mut chuncked_body = Vec :: new ( ) ;
@@ -557,18 +570,26 @@ pub(crate) mod client_tests {
557
570
encoder. write_all ( body. to_string ( ) . as_bytes ( ) ) . unwrap ( ) ;
558
571
}
559
572
format ! (
560
- "HTTP/1.1 200 OK \r \n \
573
+ "{} \r \n \
561
574
Transfer-Encoding: chunked\r \n \
562
575
\r \n \
563
- {}", String :: from_utf8( chuncked_body) . unwrap( ) )
576
+ {}", status , String :: from_utf8( chuncked_body) . unwrap( ) )
564
577
} ,
565
578
} ;
566
579
HttpServer :: responding_with ( response)
567
580
}
568
581
582
+ pub fn responding_with_ok < T : ToString > ( body : MessageBody < T > ) -> Self {
583
+ HttpServer :: responding_with_body ( "HTTP/1.1 200 OK" , body)
584
+ }
585
+
569
586
pub fn responding_with_not_found ( ) -> Self {
570
- let response = "HTTP/1.1 404 Not Found\r \n \r \n " . to_string ( ) ;
571
- HttpServer :: responding_with ( response)
587
+ HttpServer :: responding_with_body :: < String > ( "HTTP/1.1 404 Not Found" , MessageBody :: Empty )
588
+ }
589
+
590
+ pub fn responding_with_server_error < T : ToString > ( content : T ) -> Self {
591
+ let body = MessageBody :: Content ( content) ;
592
+ HttpServer :: responding_with_body ( "HTTP/1.1 500 Internal Server Error" , body)
572
593
}
573
594
574
595
fn responding_with ( response : String ) -> Self {
@@ -732,16 +753,15 @@ pub(crate) mod client_tests {
732
753
733
754
#[ tokio:: test]
734
755
async fn read_error ( ) {
735
- let response = String :: from (
736
- "HTTP/1.1 500 Internal Server Error\r \n \
737
- Content-Length: 10\r \n \r \n test error\r \n ") ;
738
- let server = HttpServer :: responding_with ( response) ;
756
+ let server = HttpServer :: responding_with_server_error ( "foo" ) ;
739
757
740
758
let mut client = HttpClient :: connect ( & server. endpoint ( ) ) . unwrap ( ) ;
741
759
match client. get :: < JsonResponse > ( "/foo" , "foo.com" ) . await {
742
760
Err ( e) => {
743
- assert_eq ! ( e. get_ref( ) . unwrap( ) . to_string( ) , "Errored with status: 500 and contents: test error" ) ;
744
761
assert_eq ! ( e. kind( ) , std:: io:: ErrorKind :: Other ) ;
762
+ let http_error = e. into_inner ( ) . unwrap ( ) . downcast :: < HttpError > ( ) . unwrap ( ) ;
763
+ assert_eq ! ( http_error. status_code, "500" ) ;
764
+ assert_eq ! ( http_error. contents, "foo" . as_bytes( ) ) ;
745
765
} ,
746
766
Ok ( _) => panic ! ( "Expected error" ) ,
747
767
}
0 commit comments