azure_core/error/
http_error.rs
1use crate::{
2 content_type, from_json,
3 headers::{self, Headers},
4 Response, StatusCode,
5};
6use bytes::Bytes;
7use serde::Deserialize;
8
9#[derive(Debug)]
11pub struct HttpError {
12 status: StatusCode,
13 details: ErrorDetails,
14 headers: Headers,
15 body: Bytes,
16}
17
18impl HttpError {
19 pub async fn new(response: Response) -> Self {
23 let (status, headers, body) = response.deconstruct();
24 let body = body
25 .collect()
26 .await
27 .unwrap_or_else(|_| Bytes::from_static(b"<ERROR COLLECTING BODY>"));
28 let details = ErrorDetails::new(&headers, &body);
29 HttpError {
30 status,
31 details,
32 headers,
33 body,
34 }
35 }
36
37 pub fn status(&self) -> StatusCode {
39 self.status
40 }
41
42 pub fn error_code(&self) -> Option<&str> {
44 self.details.code.as_deref()
45 }
46
47 pub fn error_message(&self) -> Option<&str> {
49 self.details.message.as_deref()
50 }
51}
52
53impl std::fmt::Display for HttpError {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 let newline = if f.alternate() { "\n" } else { " " };
56 let tab = if f.alternate() { "\t" } else { " " };
57 write!(f, "HttpError {{{newline}")?;
58 write!(f, "{tab}Status: {},{newline}", self.status)?;
59 write!(
60 f,
61 "{tab}Error Code: {},{newline}",
62 self.details
63 .code
64 .as_deref()
65 .unwrap_or("<unknown error code>")
66 )?;
67 write!(f, "{tab}Body: \"{:?}\",{newline}", self.body)?;
69 write!(f, "{tab}Headers: [{newline}")?;
70 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 )?;
78 }
79 write!(f, "{tab}],{newline}}}{newline}")?;
80 Ok(())
81 }
82}
83
84impl std::error::Error for HttpError {}
85
86#[derive(Debug)]
87struct ErrorDetails {
88 code: Option<String>,
89 message: Option<String>,
90}
91
92impl ErrorDetails {
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 }
104 }
105}
106
107pub(crate) fn get_error_code_from_header(headers: &Headers) -> Option<String> {
111 headers.get_optional_string(&headers::ERROR_CODE)
112}
113
114#[derive(Deserialize)]
115struct NestedError {
116 #[serde(alias = "Message")]
117 message: Option<String>,
118 #[serde(alias = "Code")]
119 code: Option<String>,
120}
121
122#[derive(Deserialize)]
124struct ErrorBody {
125 #[serde(alias = "Error")]
126 error: Option<NestedError>,
127 #[serde(alias = "Message")]
128 message: Option<String>,
129 #[serde(alias = "Code")]
130 code: Option<String>,
131}
132
133impl ErrorBody {
134 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 }
144}
145
146pub(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 from_json(body).ok()
171 };
172
173 err_body
174 .map(ErrorBody::into_code_message)
175 .unwrap_or((None, None))
176}