azure_core_mirror/error/
http_error.rs

1use crate::{headers, Response, StatusCode};
2use bytes::Bytes;
3use std::collections::HashMap;
4
5/// An unsuccessful HTTP response
6#[derive(Debug)]
7pub struct HttpError {
8    status: StatusCode,
9    details: ErrorDetails,
10    headers: std::collections::HashMap<String, String>,
11    body: Bytes,
12}
13
14impl HttpError {
15    /// Create an error from an http response.
16    ///
17    /// This does not check whether the response was a success and should only be used with unsuccessful responses.
18    pub async fn new(response: Response) -> Self {
19        let status = response.status();
20        let headers: HashMap<String, String> = response
21            .headers()
22            .iter()
23            .map(|(name, value)| (name.as_str().to_owned(), value.as_str().to_owned()))
24            .collect();
25        let body = response
26            .into_body()
27            .collect()
28            .await
29            .unwrap_or_else(|_| Bytes::from_static(b"<ERROR COLLECTING BODY>"));
30        let details = ErrorDetails::new(&headers, &body);
31        HttpError {
32            status,
33            details,
34            headers,
35            body,
36        }
37    }
38
39    /// Get the status code for the http error
40    pub fn status(&self) -> StatusCode {
41        self.status
42    }
43
44    /// Get a reference to the http error's error code.
45    pub fn error_code(&self) -> Option<&str> {
46        self.details.code.as_deref()
47    }
48
49    /// Get a reference to the http error's error message.
50    pub fn error_message(&self) -> Option<&str> {
51        self.details.message.as_deref()
52    }
53}
54
55impl std::fmt::Display for HttpError {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        let newline = if f.alternate() { "\n" } else { " " };
58        let tab = if f.alternate() { "\t" } else { " " };
59        write!(f, "HttpError {{{newline}")?;
60        write!(f, "{tab}Status: {},{newline}", self.status)?;
61        write!(
62            f,
63            "{tab}Error Code: {},{newline}",
64            self.details
65                .code
66                .as_deref()
67                .unwrap_or("<unknown error code>")
68        )?;
69        // TODO: sanitize body
70        write!(f, "{tab}Body: \"{:?}\",{newline}", self.body)?;
71        write!(f, "{tab}Headers: [{newline}")?;
72        // TODO: sanitize headers
73        for (k, v) in &self.headers {
74            write!(f, "{tab}{tab}{}:{}{newline}", k, v)?;
75        }
76        write!(f, "{tab}],{newline}}}{newline}")?;
77
78        Ok(())
79    }
80}
81
82impl std::error::Error for HttpError {}
83
84#[derive(Debug)]
85struct ErrorDetails {
86    code: Option<String>,
87    message: Option<String>,
88}
89
90impl ErrorDetails {
91    fn new(headers: &HashMap<String, String>, body: &[u8]) -> Self {
92        let mut code = get_error_code_from_header(headers);
93        code = code.or_else(|| get_error_code_from_body(body));
94        let message = get_error_message_from_body(body);
95        Self { code, message }
96    }
97}
98
99/// Gets the error code if it's present in the headers
100///
101/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
102fn get_error_code_from_header(headers: &HashMap<String, String>) -> Option<String> {
103    headers.get(headers::ERROR_CODE.as_str()).cloned()
104}
105
106/// Gets the error code if it's present in the body
107///
108/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
109pub(crate) fn get_error_code_from_body(body: &[u8]) -> Option<String> {
110    let json = serde_json::from_slice::<serde_json::Value>(body).ok()?;
111    let nested = || json.get("error")?.get("code")?.as_str();
112    let top_level = || json.get("code")?.as_str();
113    let code = nested().or_else(top_level);
114    code.map(|c| c.to_owned())
115}
116
117/// Gets the error message if it's present in the body
118///
119/// For more info, see [here](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md#handling-errors)
120pub(crate) fn get_error_message_from_body(body: &[u8]) -> Option<String> {
121    let json = serde_json::from_slice::<serde_json::Value>(body).ok()?;
122    let nested = || json.get("error")?.get("message")?.as_str();
123    let top_level = || json.get("message")?.as_str();
124    let code = nested().or_else(top_level);
125    code.map(|c| c.to_owned())
126}