1
- use crate :: { from_json, headers, Response , StatusCode } ;
1
+ use crate :: {
2
+ content_type, from_json,
3
+ headers:: { self , Headers } ,
4
+ Response , StatusCode ,
5
+ } ;
2
6
use bytes:: Bytes ;
3
7
use serde:: Deserialize ;
4
- use std:: collections:: HashMap ;
5
8
6
9
/// An unsuccessful HTTP response
7
10
#[ derive( Debug ) ]
8
11
pub struct HttpError {
9
12
status : StatusCode ,
10
13
details : ErrorDetails ,
11
- headers : std :: collections :: HashMap < String , String > ,
14
+ headers : Headers ,
12
15
body : Bytes ,
13
16
}
14
17
@@ -17,14 +20,8 @@ impl HttpError {
17
20
///
18
21
/// This does not check whether the response was a success and should only be used with unsuccessful responses.
19
22
pub 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
28
25
. collect ( )
29
26
. await
30
27
. unwrap_or_else ( |_| Bytes :: from_static ( b"<ERROR COLLECTING BODY>" ) ) ;
@@ -71,11 +68,15 @@ impl std::fmt::Display for HttpError {
71
68
write ! ( f, "{tab}Body: \" {:?}\" ,{newline}" , self . body) ?;
72
69
write ! ( f, "{tab}Headers: [{newline}" ) ?;
73
70
// 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
+ ) ?;
76
78
}
77
79
write ! ( f, "{tab}],{newline}}}{newline}" ) ?;
78
-
79
80
Ok ( ( ) )
80
81
}
81
82
}
@@ -89,46 +90,87 @@ struct ErrorDetails {
89
90
}
90
91
91
92
impl 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
+ }
97
104
}
98
105
}
99
106
100
107
/// Gets the error code if it's present in the headers
101
108
///
102
109
/// 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 )
105
112
}
106
113
107
114
#[ derive( Deserialize ) ]
108
115
struct NestedError {
116
+ #[ serde( alias = "Message" ) ]
109
117
message : Option < String > ,
118
+ #[ serde( alias = "Code" ) ]
110
119
code : Option < String > ,
111
120
}
112
121
122
+ /// Error from a response body, aliases are set because XML responses follow different case-ing
113
123
#[ derive( Deserialize ) ]
114
124
struct ErrorBody {
125
+ #[ serde( alias = "Error" ) ]
115
126
error : Option < NestedError > ,
127
+ #[ serde( alias = "Message" ) ]
116
128
message : Option < String > ,
129
+ #[ serde( alias = "Code" ) ]
117
130
code : Option < String > ,
118
131
}
119
132
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
+ }
126
144
}
127
145
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
129
148
///
149
+ /// Assumes JSON if unspecified/inconclusive to maintain old behaviour
150
+ /// [#1275](https://github.com/Azure/azure-sdk-for-rust/issues/1275)
130
151
/// 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 ) )
134
176
}
0 commit comments