@@ -5,6 +5,7 @@ use chunked_transfer;
55use serde_json;
66
77use std:: convert:: TryFrom ;
8+ use std:: fmt;
89#[ cfg( not( feature = "tokio" ) ) ]
910use std:: io:: Write ;
1011use std:: net:: ToSocketAddrs ;
@@ -348,21 +349,33 @@ impl HttpClient {
348349
349350 if !status. is_ok ( ) {
350351 // 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,
356355 } ;
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) ) ;
360357 }
361358
362359 Ok ( contents)
363360 }
364361}
365362
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+
366379/// HTTP response status code as defined by [RFC 7231].
367380///
368381/// [RFC 7231]: https://tools.ietf.org/html/rfc7231#section-6
@@ -538,16 +551,16 @@ pub(crate) mod client_tests {
538551 }
539552
540553 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 {
542555 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 ) ,
544557 MessageBody :: Content ( body) => {
545558 let body = body. to_string ( ) ;
546559 format ! (
547- "HTTP/1.1 200 OK \r \n \
560+ "{} \r \n \
548561 Content-Length: {}\r \n \
549562 \r \n \
550- {}", body. len( ) , body)
563+ {}", status , body. len( ) , body)
551564 } ,
552565 MessageBody :: ChunkedContent ( body) => {
553566 let mut chuncked_body = Vec :: new ( ) ;
@@ -557,18 +570,26 @@ pub(crate) mod client_tests {
557570 encoder. write_all ( body. to_string ( ) . as_bytes ( ) ) . unwrap ( ) ;
558571 }
559572 format ! (
560- "HTTP/1.1 200 OK \r \n \
573+ "{} \r \n \
561574 Transfer-Encoding: chunked\r \n \
562575 \r \n \
563- {}", String :: from_utf8( chuncked_body) . unwrap( ) )
576+ {}", status , String :: from_utf8( chuncked_body) . unwrap( ) )
564577 } ,
565578 } ;
566579 HttpServer :: responding_with ( response)
567580 }
568581
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+
569586 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)
572593 }
573594
574595 fn responding_with ( response : String ) -> Self {
@@ -732,16 +753,15 @@ pub(crate) mod client_tests {
732753
733754 #[ tokio:: test]
734755 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" ) ;
739757
740758 let mut client = HttpClient :: connect ( & server. endpoint ( ) ) . unwrap ( ) ;
741759 match client. get :: < JsonResponse > ( "/foo" , "foo.com" ) . await {
742760 Err ( e) => {
743- assert_eq ! ( e. get_ref( ) . unwrap( ) . to_string( ) , "Errored with status: 500 and contents: test error" ) ;
744761 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( ) ) ;
745765 } ,
746766 Ok ( _) => panic ! ( "Expected error" ) ,
747767 }
0 commit comments