1- use  crate :: { from_json,  headers,  Response ,  StatusCode } ; 
1+ use  crate :: { 
2+     content_type,  from_json, 
3+     headers:: { self ,  Headers } , 
4+     Response ,  StatusCode , 
5+ } ; 
26use  bytes:: Bytes ; 
37use  serde:: Deserialize ; 
4- use  std:: collections:: HashMap ; 
58
69/// An unsuccessful HTTP response 
710#[ derive( Debug ) ]  
811pub  struct  HttpError  { 
912    status :  StatusCode , 
1013    details :  ErrorDetails , 
11-     headers :  std :: collections :: HashMap < String ,   String > , 
14+     headers :  Headers , 
1215    body :  Bytes , 
1316} 
1417
@@ -17,14 +20,8 @@ impl HttpError {
1720/// 
1821/// This does not check whether the response was a success and should only be used with unsuccessful responses. 
1922pub  async  fn  new ( response :  Response )  -> Self  { 
20-         let  status = response. status ( ) ; 
21-         let  headers:  HashMap < String ,  String >  = response
22-             . headers ( ) 
23-             . iter ( ) 
24-             . map ( |( name,  value) | ( name. as_str ( ) . to_owned ( ) ,  value. as_str ( ) . to_owned ( ) ) ) 
25-             . collect ( ) ; 
26-         let  body = response
27-             . into_body ( ) 
23+         let  ( status,  headers,  body)  = response. deconstruct ( ) ; 
24+         let  body = body
2825            . collect ( ) 
2926            . await 
3027            . unwrap_or_else ( |_| Bytes :: from_static ( b"<ERROR COLLECTING BODY>" ) ) ; 
@@ -71,11 +68,15 @@ impl std::fmt::Display for HttpError {
7168        write ! ( f,  "{tab}Body: \" {:?}\" ,{newline}" ,  self . body) ?; 
7269        write ! ( f,  "{tab}Headers: [{newline}" ) ?; 
7370        // TODO: sanitize headers 
74-         for  ( k,  v)  in  & self . headers  { 
75-             write ! ( f,  "{tab}{tab}{k}:{v}{newline}" ) ?; 
71+         for  ( k,  v)  in  self . headers . iter ( )  { 
72+             write ! ( 
73+                 f, 
74+                 "{tab}{tab}{k}:{v}{newline}" , 
75+                 k = k. as_str( ) , 
76+                 v = v. as_str( ) 
77+             ) ?; 
7678        } 
7779        write ! ( f,  "{tab}],{newline}}}{newline}" ) ?; 
78- 
7980        Ok ( ( ) ) 
8081    } 
8182} 
@@ -89,46 +90,87 @@ struct ErrorDetails {
8990} 
9091
9192impl  ErrorDetails  { 
92-     fn  new ( headers :  & HashMap < String ,  String > ,  body :  & [ u8 ] )  -> Self  { 
93-         let  mut  code = get_error_code_from_header ( headers) ; 
94-         code = code. or_else ( || get_error_code_from_body ( body) ) ; 
95-         let  message = get_error_message_from_body ( body) ; 
96-         Self  {  code,  message } 
93+     fn  new ( headers :  & Headers ,  body :  & [ u8 ] )  -> Self  { 
94+         let  header_err_code = get_error_code_from_header ( headers) ; 
95+         let  content_type = headers. get_optional_str ( & headers:: CONTENT_TYPE ) ; 
96+         let  ( body_err_code,  body_err_message)  =
97+             get_error_code_message_from_body ( body,  content_type) ; 
98+ 
99+         let  code = header_err_code. or ( body_err_code) ; 
100+         Self  { 
101+             code, 
102+             message :  body_err_message, 
103+         } 
97104    } 
98105} 
99106
100107/// Gets the error code if it's present in the headers 
101108/// 
102109/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors) 
103- fn  get_error_code_from_header ( headers :  & HashMap < String ,   String > )  -> Option < String >  { 
104-     headers. get ( headers:: ERROR_CODE . as_str ( ) ) . cloned ( ) 
110+ pub ( crate )   fn  get_error_code_from_header ( headers :  & Headers )  -> Option < String >  { 
111+     headers. get_optional_string ( & headers:: ERROR_CODE ) 
105112} 
106113
107114#[ derive( Deserialize ) ]  
108115struct  NestedError  { 
116+     #[ serde( alias = "Message" ) ]  
109117    message :  Option < String > , 
118+     #[ serde( alias = "Code" ) ]  
110119    code :  Option < String > , 
111120} 
112121
122+ /// Error from a response body, aliases are set because XML responses follow different case-ing 
113123#[ derive( Deserialize ) ]  
114124struct  ErrorBody  { 
125+     #[ serde( alias = "Error" ) ]  
115126    error :  Option < NestedError > , 
127+     #[ serde( alias = "Message" ) ]  
116128    message :  Option < String > , 
129+     #[ serde( alias = "Code" ) ]  
117130    code :  Option < String > , 
118131} 
119132
120- /// Gets the error code if it's present in the body 
121- /// 
122- /// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors) 
123- pub ( crate )  fn  get_error_code_from_body ( body :  & [ u8 ] )  -> Option < String >  { 
124-     let  decoded:  ErrorBody  = from_json ( body) . ok ( ) ?; 
125-     decoded. error . and_then ( |e| e. code ) . or ( decoded. code ) 
133+ impl  ErrorBody  { 
134+     /// Deconstructs self into error (code, message) 
135+ /// 
136+ /// The nested errors fields take precedence over those in the root of the structure 
137+ fn  into_code_message ( self )  -> ( Option < String > ,  Option < String > )  { 
138+         let  ( nested_code,  nested_message)  = self 
139+             . error 
140+             . map ( |nested_error| ( nested_error. code ,  nested_error. message ) ) 
141+             . unwrap_or ( ( None ,  None ) ) ; 
142+         ( nested_code. or ( self . code ) ,  nested_message. or ( self . message ) ) 
143+     } 
126144} 
127145
128- /// Gets the error message if it's present in the body 
146+ /// Gets the error code and message from the body based on the specified content_type 
147+ /// Support for xml decoding is dependent on the 'xml' feature flag 
129148/// 
149+ /// Assumes JSON if unspecified/inconclusive to maintain old behaviour 
150+ /// [#1275](https://github.com/Azure/azure-sdk-for-rust/issues/1275) 
130151/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors) 
131- pub ( crate )  fn  get_error_message_from_body ( body :  & [ u8 ] )  -> Option < String >  { 
132-     let  decoded:  ErrorBody  = from_json ( body) . ok ( ) ?; 
133-     decoded. error . and_then ( |e| e. message ) . or ( decoded. message ) 
152+ pub ( crate )  fn  get_error_code_message_from_body ( 
153+     body :  & [ u8 ] , 
154+     content_type :  Option < & str > , 
155+ )  -> ( Option < String > ,  Option < String > )  { 
156+     let  err_body:  Option < ErrorBody >  = if  content_type
157+         . is_some_and ( |ctype| ctype == content_type:: APPLICATION_XML . as_str ( ) ) 
158+     { 
159+         #[ cfg( feature = "xml" ) ]  
160+         { 
161+             crate :: xml:: read_xml ( body) . ok ( ) 
162+         } 
163+         #[ cfg( not( feature = "xml" ) ) ]  
164+         { 
165+             tracing:: warn!( "encountered XML response but the 'xml' feature flag was not specified" ) ; 
166+             None 
167+         } 
168+     }  else  { 
169+         // keep old default of assuming JSON 
170+         from_json ( body) . ok ( ) 
171+     } ; 
172+ 
173+     err_body
174+         . map ( ErrorBody :: into_code_message) 
175+         . unwrap_or ( ( None ,  None ) ) 
134176} 
0 commit comments