use core::fmt;
use hyper::{header::InvalidHeaderValue, Error as RequestError};
use std::error::Error as StdError;
use std::{fmt::Display, result};
pub type Result<T> = result::Result<T, Error>;
pub type Error = MinioError;
#[derive(Debug)]
pub struct ValueError(String);
impl ValueError {
pub fn new<T: Into<String>>(value: T) -> Self {
Self(value.into())
}
}
impl Display for ValueError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "value error: {}", self.0)
}
}
impl StdError for ValueError {}
impl From<&str> for ValueError {
fn from(err: &str) -> Self {
Self(err.to_string())
}
}
#[derive(Debug)]
pub struct XmlError(String);
impl Display for XmlError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "xmlerror: {}", self.0)
}
}
impl StdError for XmlError {}
impl From<quick_xml::DeError> for XmlError {
fn from(err: quick_xml::DeError) -> Self {
Self(err.to_string())
}
}
impl From<quick_xml::Error> for XmlError {
fn from(err: quick_xml::Error) -> Self {
Self(err.to_string())
}
}
#[derive(Debug)]
pub struct S3Error {
pub code: String,
pub message: String,
pub resource: String,
pub request_id: String,
pub host_id: Option<String>,
pub bucket_name: Option<String>,
pub object_name: Option<String>,
}
impl std::fmt::Display for S3Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "S3Error: {}", self.message)
}
}
impl StdError for S3Error {}
impl TryFrom<&[u8]> for S3Error {
type Error = XmlError;
fn try_from(res: &[u8]) -> std::result::Result<Self, Self::Error> {
let mut reader = quick_xml::Reader::from_reader(res);
reader.trim_text(true);
let mut code: Option<String> = None;
let mut message: Option<String> = None;
let mut resource: Option<String> = None;
let mut request_id: Option<String> = None;
let mut host_id: Option<String> = None;
let mut bucket_name: Option<String> = None;
let mut object_name: Option<String> = None;
loop {
match reader.read_event() {
Ok(quick_xml::events::Event::Start(ref e)) => {
match e.name().as_ref() {
b"Error" => {}
b"Code" => {
code = Some(reader.read_text(e.to_end().name())?.into_owned());
}
b"Message" => {
message = Some(reader.read_text(e.to_end().name())?.into_owned());
}
b"Resource" => {
resource = Some(reader.read_text(e.to_end().name())?.into_owned());
}
b"RequestId" => {
request_id = Some(reader.read_text(e.to_end().name())?.into_owned());
}
b"BucketName" => {
bucket_name = Some(reader.read_text(e.to_end().name())?.into_owned());
}
b"ObjectName" => {
object_name = Some(reader.read_text(e.to_end().name())?.into_owned());
}
b"HostId" => {
host_id = Some(reader.read_text(e.to_end().name())?.into_owned());
}
_ => {}
};
}
Err(e) => Err(e)?,
Ok(quick_xml::events::Event::Eof) => break,
_ => (),
}
}
if let (Some(code), Some(message), Some(resource), Some(request_id)) =
(code, message, resource, request_id)
{
Ok(Self {
code,
message,
resource,
request_id,
host_id,
bucket_name,
object_name,
})
} else {
Err(XmlError("Invalid error response".to_string()))
}
}
}
impl TryFrom<&str> for S3Error {
type Error = XmlError;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
value.as_bytes().try_into()
}
}
#[derive(Debug)]
pub enum MinioError {
ValueError(String),
RequestError(RequestError),
XmlError(XmlError),
S3Error(S3Error),
HttpError,
IoError(String),
}
impl StdError for MinioError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
MinioError::RequestError(e) => e.source(),
MinioError::S3Error(e) => e.source(),
_ => None,
}
}
}
impl fmt::Display for MinioError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
MinioError::ValueError(e) => write!(f, "{}", e),
MinioError::RequestError(e) => write!(f, "{}", e),
MinioError::XmlError(e) => write!(f, "{}", e),
MinioError::S3Error(e) => write!(f, "{}", e),
MinioError::HttpError => write!(
f,
"HttpError, S3 service returned invalid or no error response."
),
MinioError::IoError(e) => write!(f, "{}", e),
}
}
}
impl From<S3Error> for MinioError {
fn from(err: S3Error) -> Self {
MinioError::S3Error(err)
}
}
impl From<ValueError> for MinioError {
fn from(err: ValueError) -> Self {
MinioError::ValueError(err.0)
}
}
impl From<XmlError> for MinioError {
fn from(err: XmlError) -> Self {
MinioError::XmlError(err)
}
}
impl From<RequestError> for Error {
fn from(err: RequestError) -> Self {
MinioError::RequestError(err)
}
}
impl From<tokio::io::Error> for Error {
fn from(err: tokio::io::Error) -> Self {
MinioError::IoError(err.to_string())
}
}
impl From<reqwest::Error> for Error {
fn from(err: reqwest::Error) -> Self {
if err.is_builder() {
return Self::ValueError(err.to_string());
}
Self::HttpError
}
}
impl From<InvalidHeaderValue> for Error {
fn from(err: InvalidHeaderValue) -> Self {
return Self::ValueError(err.to_string());
}
}
#[cfg(test)]
mod tests {
use super::S3Error;
use crate::errors::{Result, XmlError};
#[test]
fn test_s3_error() {
let res = r#"<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>NoSuchKey</Code>
<Message>The resource you requested does not exist</Message>
<Resource>/mybucket/myfoto.jpg</Resource>
<RequestId>4442587FB7D0A2F9</RequestId>
</Error>"#;
let result: std::result::Result<S3Error, XmlError> = res.as_bytes().try_into();
assert!(result.is_ok());
println!("{:?}", result);
}
}