use crate::generated::models::StorageErrorCode;
use azure_core::{error::ErrorKind, http::RawResponse, xml};
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;
pub type Result<T> = std::result::Result<T, StorageError>;
#[derive(Debug, Clone)]
pub struct StorageError {
pub status_code: azure_core::http::StatusCode,
pub error_code: Option<StorageErrorCode>,
pub message: Option<String>,
pub request_id: Option<String>,
pub reason: Option<String>,
pub authentication_error_detail: Option<String>,
pub copy_source_status_code: Option<azure_core::http::StatusCode>,
pub copy_source_error_code: Option<String>,
pub copy_source_error_message: Option<String>,
pub additional_error_info: HashMap<String, String>,
}
impl StorageError {
fn value_to_string(value: &Value) -> String {
match value {
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Number(n) => n.to_string(),
Value::String(s) => s.clone(),
Value::Object(obj) if obj.len() == 1 && obj.contains_key("$text") => obj
.get("$text")
.and_then(|v| v.as_str())
.map(String::from)
.unwrap_or_default(),
_ => serde_json::to_string(value).unwrap_or_default(),
}
}
fn from_xml(
status_code: azure_core::http::StatusCode,
error_code: Option<StorageErrorCode>,
request_id: Option<String>,
raw_response: RawResponse,
) -> std::result::Result<Self, azure_core::Error> {
#[derive(Deserialize)]
#[serde(rename = "Error")]
struct StorageErrorXml {
#[serde(rename = "Code")]
code: Option<String>,
#[serde(rename = "Message")]
message: Option<String>,
#[serde(rename = "Reason")]
reason: Option<String>,
#[serde(rename = "AuthenticationErrorDetail")]
authentication_error_detail: Option<String>,
#[serde(rename = "CopySourceStatusCode")]
copy_source_status_code: Option<String>,
#[serde(rename = "CopySourceErrorCode")]
copy_source_error_code: Option<String>,
#[serde(rename = "CopySourceErrorMessage")]
copy_source_error_message: Option<String>,
#[serde(flatten)]
additional_fields: HashMap<String, Value>,
}
let xml_fields = xml::from_xml::<_, StorageErrorXml>(raw_response.body())?;
let additional_error_info = xml_fields
.additional_fields
.iter()
.map(|(k, v)| (k.clone(), Self::value_to_string(v)))
.collect();
Ok(StorageError {
status_code,
error_code,
message: xml_fields.message,
request_id,
reason: xml_fields.reason,
authentication_error_detail: xml_fields.authentication_error_detail,
copy_source_status_code: xml_fields
.copy_source_status_code
.and_then(|s| s.parse::<u16>().ok())
.map(azure_core::http::StatusCode::from),
copy_source_error_code: xml_fields.copy_source_error_code,
copy_source_error_message: xml_fields.copy_source_error_message,
additional_error_info,
})
}
}
impl std::fmt::Display for StorageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "HTTP Status Code: {}", self.status_code)?;
if let Some(request_id) = &self.request_id {
writeln!(f, "Request ID: {}", request_id)?;
}
if let Some(error_code) = &self.error_code {
writeln!(f, "Storage Error Code: {}", error_code)?;
}
if let Some(message) = &self.message {
writeln!(f, "Error Message: {}", message)?;
}
if let Some(reason) = &self.reason {
writeln!(f, "Reason: {}", reason)?;
}
if let Some(detail) = &self.authentication_error_detail {
writeln!(f, "Authentication Error Detail: {}", detail)?;
}
if let Some(status) = &self.copy_source_status_code {
writeln!(f, "Copy Source Status Code: {}", status)?;
}
if let Some(code) = &self.copy_source_error_code {
writeln!(f, "Copy Source Error Code: {}", code)?;
}
if let Some(message) = &self.copy_source_error_message {
writeln!(f, "Copy Source Error Message: {}", message)?;
}
if !self.additional_error_info.is_empty() {
writeln!(f, "\nAdditional Error Info:")?;
for (key, value) in &self.additional_error_info {
writeln!(f, "{}: {}", key, value)?;
}
}
Ok(())
}
}
impl std::error::Error for StorageError {}
impl TryFrom<azure_core::Error> for StorageError {
type Error = azure_core::Error;
fn try_from(error: azure_core::Error) -> std::result::Result<Self, Self::Error> {
match error.kind() {
ErrorKind::HttpResponse {
status,
raw_response: Some(raw_response),
..
} => {
let headers = raw_response.headers();
let body = raw_response.body();
let error_code = headers
.get_optional_string(&azure_core::http::headers::HeaderName::from_static(
"x-ms-error-code",
))
.and_then(|code| {
code.parse()
.ok()
.or(Some(StorageErrorCode::UnknownValue(code)))
});
let request_id = headers.get_optional_string(
&azure_core::http::headers::HeaderName::from_static("x-ms-request-id"),
);
if body.is_empty() {
let message = Some(status.canonical_reason().to_string());
return Ok(StorageError {
status_code: *status,
error_code,
message,
request_id,
reason: None,
authentication_error_detail: None,
copy_source_status_code: None,
copy_source_error_code: None,
copy_source_error_message: None,
additional_error_info: HashMap::new(),
});
}
StorageError::from_xml(
*status,
error_code,
request_id,
raw_response.as_ref().clone(),
)
}
ErrorKind::HttpResponse {
status,
raw_response: None,
..
} => {
let message = Some(status.canonical_reason().to_string());
Ok(StorageError {
status_code: *status,
error_code: None,
message,
request_id: None,
reason: None,
authentication_error_detail: None,
copy_source_status_code: None,
copy_source_error_code: None,
copy_source_error_message: None,
additional_error_info: HashMap::new(),
})
}
_ => Err(azure_core::Error::with_message(
azure_core::error::ErrorKind::DataConversion,
"ErrorKind was not HttpResponse and could not be parsed.",
)),
}
}
}