Files
rustfs/ecstore/src/error.rs
2025-06-23 12:03:46 +08:00

1322 lines
54 KiB
Rust

use s3s::{S3Error, S3ErrorCode};
use rustfs_utils::path::decode_dir_object;
use crate::disk::error::DiskError;
pub type Error = StorageError;
pub type Result<T> = core::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum StorageError {
#[error("Faulty disk")]
FaultyDisk,
#[error("Disk full")]
DiskFull,
#[error("Volume not found")]
VolumeNotFound,
#[error("Volume exists")]
VolumeExists,
#[error("File not found")]
FileNotFound,
#[error("File version not found")]
FileVersionNotFound,
#[error("File name too long")]
FileNameTooLong,
#[error("File access denied")]
FileAccessDenied,
#[error("File is corrupted")]
FileCorrupt,
#[error("Not a regular file")]
IsNotRegular,
#[error("Volume not empty")]
VolumeNotEmpty,
#[error("Volume access denied")]
VolumeAccessDenied,
#[error("Corrupted format")]
CorruptedFormat,
#[error("Corrupted backend")]
CorruptedBackend,
#[error("Unformatted disk")]
UnformattedDisk,
#[error("Disk not found")]
DiskNotFound,
#[error("Drive is root")]
DriveIsRoot,
#[error("Faulty remote disk")]
FaultyRemoteDisk,
#[error("Disk access denied")]
DiskAccessDenied,
#[error("Unexpected error")]
Unexpected,
#[error("Too many open files")]
TooManyOpenFiles,
#[error("No heal required")]
NoHealRequired,
#[error("Config not found")]
ConfigNotFound,
#[error("not implemented")]
NotImplemented,
#[error("Invalid arguments provided for {0}/{1}-{2}")]
InvalidArgument(String, String, String),
#[error("method not allowed")]
MethodNotAllowed,
#[error("Bucket not found: {0}")]
BucketNotFound(String),
#[error("Bucket not empty: {0}")]
BucketNotEmpty(String),
#[error("Bucket name invalid: {0}")]
BucketNameInvalid(String),
#[error("Object name invalid: {0}/{1}")]
ObjectNameInvalid(String, String),
#[error("Bucket exists: {0}")]
BucketExists(String),
#[error("Storage reached its minimum free drive threshold.")]
StorageFull,
#[error("Please reduce your request rate")]
SlowDown,
#[error("Prefix access is denied:{0}/{1}")]
PrefixAccessDenied(String, String),
#[error("Invalid UploadID KeyCombination: {0}/{1}")]
InvalidUploadIDKeyCombination(String, String),
#[error("Malformed UploadID: {0}")]
MalformedUploadID(String),
#[error("Object name too long: {0}/{1}")]
ObjectNameTooLong(String, String),
#[error("Object name contains forward slash as prefix: {0}/{1}")]
ObjectNamePrefixAsSlash(String, String),
#[error("Object not found: {0}/{1}")]
ObjectNotFound(String, String),
#[error("Version not found: {0}/{1}-{2}")]
VersionNotFound(String, String, String),
#[error("Invalid upload id: {0}/{1}-{2}")]
InvalidUploadID(String, String, String),
#[error("Specified part could not be found. PartNumber {0}, Expected {1}, got {2}")]
InvalidPart(usize, String, String),
#[error("Invalid version id: {0}/{1}-{2}")]
InvalidVersionID(String, String, String),
#[error("invalid data movement operation, source and destination pool are the same for : {0}/{1}-{2}")]
DataMovementOverwriteErr(String, String, String),
#[error("Object exists on :{0} as directory {1}")]
ObjectExistsAsDirectory(String, String),
// #[error("Storage resources are insufficient for the read operation")]
// InsufficientReadQuorum,
// #[error("Storage resources are insufficient for the write operation")]
// InsufficientWriteQuorum,
#[error("Decommission not started")]
DecommissionNotStarted,
#[error("Decommission already running")]
DecommissionAlreadyRunning,
#[error("DoneForNow")]
DoneForNow,
#[error("erasure read quorum")]
ErasureReadQuorum,
#[error("erasure write quorum")]
ErasureWriteQuorum,
#[error("not first disk")]
NotFirstDisk,
#[error("first disk wait")]
FirstDiskWait,
#[error("Bucket policy not found")]
BucketPolicyNotFound,
#[error("Io error: {0}")]
Io(std::io::Error),
}
impl StorageError {
pub fn other<E>(error: E) -> Self
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
StorageError::Io(std::io::Error::other(error))
}
}
impl From<DiskError> for StorageError {
fn from(e: DiskError) -> Self {
match e {
DiskError::Io(io_error) => StorageError::Io(io_error),
// DiskError::MaxVersionsExceeded => todo!(),
DiskError::Unexpected => StorageError::Unexpected,
DiskError::CorruptedFormat => StorageError::CorruptedFormat,
DiskError::CorruptedBackend => StorageError::CorruptedBackend,
DiskError::UnformattedDisk => StorageError::UnformattedDisk,
// DiskError::InconsistentDisk => StorageError::InconsistentDisk,
// DiskError::UnsupportedDisk => StorageError::UnsupportedDisk,
DiskError::DiskFull => StorageError::DiskFull,
// DiskError::DiskNotDir => StorageError::DiskNotDir,
DiskError::DiskNotFound => StorageError::DiskNotFound,
// DiskError::DiskOngoingReq => StorageError::DiskOngoingReq,
DiskError::DriveIsRoot => StorageError::DriveIsRoot,
DiskError::FaultyRemoteDisk => StorageError::FaultyRemoteDisk,
DiskError::FaultyDisk => StorageError::FaultyDisk,
DiskError::DiskAccessDenied => StorageError::DiskAccessDenied,
DiskError::FileNotFound => StorageError::FileNotFound,
DiskError::FileVersionNotFound => StorageError::FileVersionNotFound,
DiskError::TooManyOpenFiles => StorageError::TooManyOpenFiles,
DiskError::FileNameTooLong => StorageError::FileNameTooLong,
DiskError::VolumeExists => StorageError::VolumeExists,
DiskError::IsNotRegular => StorageError::IsNotRegular,
// DiskError::PathNotFound => StorageError::PathNotFound,
DiskError::VolumeNotFound => StorageError::VolumeNotFound,
DiskError::VolumeNotEmpty => StorageError::VolumeNotEmpty,
DiskError::VolumeAccessDenied => StorageError::VolumeAccessDenied,
DiskError::FileAccessDenied => StorageError::FileAccessDenied,
DiskError::FileCorrupt => StorageError::FileCorrupt,
// DiskError::BitrotHashAlgoInvalid => StorageError::BitrotHashAlgoInvalid,
// DiskError::CrossDeviceLink => StorageError::CrossDeviceLink,
// DiskError::LessData => StorageError::LessData,
// DiskError::MoreData => StorageError::MoreData,
// DiskError::OutdatedXLMeta => StorageError::OutdatedXLMeta,
// DiskError::PartMissingOrCorrupt => StorageError::PartMissingOrCorrupt,
DiskError::NoHealRequired => StorageError::NoHealRequired,
DiskError::MethodNotAllowed => StorageError::MethodNotAllowed,
DiskError::ErasureReadQuorum => StorageError::ErasureReadQuorum,
DiskError::ErasureWriteQuorum => StorageError::ErasureWriteQuorum,
_ => StorageError::Io(std::io::Error::other(e)),
}
}
}
impl From<StorageError> for DiskError {
fn from(val: StorageError) -> Self {
match val {
StorageError::Io(io_error) => io_error.into(),
StorageError::Unexpected => DiskError::Unexpected,
StorageError::FileNotFound => DiskError::FileNotFound,
StorageError::FileVersionNotFound => DiskError::FileVersionNotFound,
StorageError::FileCorrupt => DiskError::FileCorrupt,
StorageError::MethodNotAllowed => DiskError::MethodNotAllowed,
StorageError::StorageFull => DiskError::DiskFull,
StorageError::SlowDown => DiskError::TooManyOpenFiles,
StorageError::ErasureReadQuorum => DiskError::ErasureReadQuorum,
StorageError::ErasureWriteQuorum => DiskError::ErasureWriteQuorum,
StorageError::TooManyOpenFiles => DiskError::TooManyOpenFiles,
StorageError::NoHealRequired => DiskError::NoHealRequired,
StorageError::CorruptedFormat => DiskError::CorruptedFormat,
StorageError::CorruptedBackend => DiskError::CorruptedBackend,
StorageError::UnformattedDisk => DiskError::UnformattedDisk,
StorageError::DiskNotFound => DiskError::DiskNotFound,
StorageError::FaultyDisk => DiskError::FaultyDisk,
StorageError::DiskFull => DiskError::DiskFull,
StorageError::VolumeNotFound => DiskError::VolumeNotFound,
StorageError::VolumeExists => DiskError::VolumeExists,
StorageError::FileNameTooLong => DiskError::FileNameTooLong,
_ => DiskError::other(val),
}
}
}
impl From<std::io::Error> for StorageError {
fn from(e: std::io::Error) -> Self {
match e.downcast::<StorageError>() {
Ok(storage_error) => storage_error,
Err(io_error) => match io_error.downcast::<DiskError>() {
Ok(disk_error) => disk_error.into(),
Err(io_error) => StorageError::Io(io_error),
},
}
}
}
impl From<StorageError> for std::io::Error {
fn from(e: StorageError) -> Self {
match e {
StorageError::Io(io_error) => io_error,
e => std::io::Error::other(e),
}
}
}
impl From<rustfs_filemeta::Error> for StorageError {
fn from(e: rustfs_filemeta::Error) -> Self {
match e {
rustfs_filemeta::Error::DoneForNow => StorageError::DoneForNow,
rustfs_filemeta::Error::MethodNotAllowed => StorageError::MethodNotAllowed,
rustfs_filemeta::Error::VolumeNotFound => StorageError::VolumeNotFound,
rustfs_filemeta::Error::FileNotFound => StorageError::FileNotFound,
rustfs_filemeta::Error::FileVersionNotFound => StorageError::FileVersionNotFound,
rustfs_filemeta::Error::FileCorrupt => StorageError::FileCorrupt,
rustfs_filemeta::Error::Unexpected => StorageError::Unexpected,
rustfs_filemeta::Error::Io(io_error) => io_error.into(),
_ => StorageError::Io(std::io::Error::other(e)),
}
}
}
impl From<StorageError> for rustfs_filemeta::Error {
fn from(val: StorageError) -> Self {
match val {
StorageError::Unexpected => rustfs_filemeta::Error::Unexpected,
StorageError::FileNotFound => rustfs_filemeta::Error::FileNotFound,
StorageError::FileVersionNotFound => rustfs_filemeta::Error::FileVersionNotFound,
StorageError::FileCorrupt => rustfs_filemeta::Error::FileCorrupt,
StorageError::DoneForNow => rustfs_filemeta::Error::DoneForNow,
StorageError::MethodNotAllowed => rustfs_filemeta::Error::MethodNotAllowed,
StorageError::VolumeNotFound => rustfs_filemeta::Error::VolumeNotFound,
StorageError::Io(io_error) => io_error.into(),
_ => rustfs_filemeta::Error::other(val),
}
}
}
impl PartialEq for StorageError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(StorageError::Io(e1), StorageError::Io(e2)) => e1.kind() == e2.kind() && e1.to_string() == e2.to_string(),
(e1, e2) => e1.to_u32() == e2.to_u32(),
}
}
}
impl Clone for StorageError {
fn clone(&self) -> Self {
match self {
StorageError::Io(e) => StorageError::Io(std::io::Error::new(e.kind(), e.to_string())),
StorageError::FaultyDisk => StorageError::FaultyDisk,
StorageError::DiskFull => StorageError::DiskFull,
StorageError::VolumeNotFound => StorageError::VolumeNotFound,
StorageError::VolumeExists => StorageError::VolumeExists,
StorageError::FileNotFound => StorageError::FileNotFound,
StorageError::FileVersionNotFound => StorageError::FileVersionNotFound,
StorageError::FileNameTooLong => StorageError::FileNameTooLong,
StorageError::FileAccessDenied => StorageError::FileAccessDenied,
StorageError::FileCorrupt => StorageError::FileCorrupt,
StorageError::IsNotRegular => StorageError::IsNotRegular,
StorageError::VolumeNotEmpty => StorageError::VolumeNotEmpty,
StorageError::VolumeAccessDenied => StorageError::VolumeAccessDenied,
StorageError::CorruptedFormat => StorageError::CorruptedFormat,
StorageError::CorruptedBackend => StorageError::CorruptedBackend,
StorageError::UnformattedDisk => StorageError::UnformattedDisk,
StorageError::DiskNotFound => StorageError::DiskNotFound,
StorageError::DriveIsRoot => StorageError::DriveIsRoot,
StorageError::FaultyRemoteDisk => StorageError::FaultyRemoteDisk,
StorageError::DiskAccessDenied => StorageError::DiskAccessDenied,
StorageError::Unexpected => StorageError::Unexpected,
StorageError::ConfigNotFound => StorageError::ConfigNotFound,
StorageError::NotImplemented => StorageError::NotImplemented,
StorageError::InvalidArgument(a, b, c) => StorageError::InvalidArgument(a.clone(), b.clone(), c.clone()),
StorageError::MethodNotAllowed => StorageError::MethodNotAllowed,
StorageError::BucketNotFound(a) => StorageError::BucketNotFound(a.clone()),
StorageError::BucketNotEmpty(a) => StorageError::BucketNotEmpty(a.clone()),
StorageError::BucketNameInvalid(a) => StorageError::BucketNameInvalid(a.clone()),
StorageError::ObjectNameInvalid(a, b) => StorageError::ObjectNameInvalid(a.clone(), b.clone()),
StorageError::BucketExists(a) => StorageError::BucketExists(a.clone()),
StorageError::StorageFull => StorageError::StorageFull,
StorageError::SlowDown => StorageError::SlowDown,
StorageError::PrefixAccessDenied(a, b) => StorageError::PrefixAccessDenied(a.clone(), b.clone()),
StorageError::InvalidUploadIDKeyCombination(a, b) => {
StorageError::InvalidUploadIDKeyCombination(a.clone(), b.clone())
}
StorageError::MalformedUploadID(a) => StorageError::MalformedUploadID(a.clone()),
StorageError::ObjectNameTooLong(a, b) => StorageError::ObjectNameTooLong(a.clone(), b.clone()),
StorageError::ObjectNamePrefixAsSlash(a, b) => StorageError::ObjectNamePrefixAsSlash(a.clone(), b.clone()),
StorageError::ObjectNotFound(a, b) => StorageError::ObjectNotFound(a.clone(), b.clone()),
StorageError::VersionNotFound(a, b, c) => StorageError::VersionNotFound(a.clone(), b.clone(), c.clone()),
StorageError::InvalidUploadID(a, b, c) => StorageError::InvalidUploadID(a.clone(), b.clone(), c.clone()),
StorageError::InvalidVersionID(a, b, c) => StorageError::InvalidVersionID(a.clone(), b.clone(), c.clone()),
StorageError::DataMovementOverwriteErr(a, b, c) => {
StorageError::DataMovementOverwriteErr(a.clone(), b.clone(), c.clone())
}
StorageError::ObjectExistsAsDirectory(a, b) => StorageError::ObjectExistsAsDirectory(a.clone(), b.clone()),
// StorageError::InsufficientReadQuorum => StorageError::InsufficientReadQuorum,
// StorageError::InsufficientWriteQuorum => StorageError::InsufficientWriteQuorum,
StorageError::DecommissionNotStarted => StorageError::DecommissionNotStarted,
StorageError::DecommissionAlreadyRunning => StorageError::DecommissionAlreadyRunning,
StorageError::DoneForNow => StorageError::DoneForNow,
StorageError::InvalidPart(a, b, c) => StorageError::InvalidPart(*a, b.clone(), c.clone()),
StorageError::ErasureReadQuorum => StorageError::ErasureReadQuorum,
StorageError::ErasureWriteQuorum => StorageError::ErasureWriteQuorum,
StorageError::NotFirstDisk => StorageError::NotFirstDisk,
StorageError::FirstDiskWait => StorageError::FirstDiskWait,
StorageError::TooManyOpenFiles => StorageError::TooManyOpenFiles,
StorageError::NoHealRequired => StorageError::NoHealRequired,
StorageError::BucketPolicyNotFound => StorageError::BucketPolicyNotFound,
}
}
}
impl StorageError {
pub fn to_u32(&self) -> u32 {
match self {
StorageError::Io(_) => 0x01,
StorageError::FaultyDisk => 0x02,
StorageError::DiskFull => 0x03,
StorageError::VolumeNotFound => 0x04,
StorageError::VolumeExists => 0x05,
StorageError::FileNotFound => 0x06,
StorageError::FileVersionNotFound => 0x07,
StorageError::FileNameTooLong => 0x08,
StorageError::FileAccessDenied => 0x09,
StorageError::FileCorrupt => 0x0A,
StorageError::IsNotRegular => 0x0B,
StorageError::VolumeNotEmpty => 0x0C,
StorageError::VolumeAccessDenied => 0x0D,
StorageError::CorruptedFormat => 0x0E,
StorageError::CorruptedBackend => 0x0F,
StorageError::UnformattedDisk => 0x10,
StorageError::DiskNotFound => 0x11,
StorageError::DriveIsRoot => 0x12,
StorageError::FaultyRemoteDisk => 0x13,
StorageError::DiskAccessDenied => 0x14,
StorageError::Unexpected => 0x15,
StorageError::NotImplemented => 0x16,
StorageError::InvalidArgument(_, _, _) => 0x17,
StorageError::MethodNotAllowed => 0x18,
StorageError::BucketNotFound(_) => 0x19,
StorageError::BucketNotEmpty(_) => 0x1A,
StorageError::BucketNameInvalid(_) => 0x1B,
StorageError::ObjectNameInvalid(_, _) => 0x1C,
StorageError::BucketExists(_) => 0x1D,
StorageError::StorageFull => 0x1E,
StorageError::SlowDown => 0x1F,
StorageError::PrefixAccessDenied(_, _) => 0x20,
StorageError::InvalidUploadIDKeyCombination(_, _) => 0x21,
StorageError::MalformedUploadID(_) => 0x22,
StorageError::ObjectNameTooLong(_, _) => 0x23,
StorageError::ObjectNamePrefixAsSlash(_, _) => 0x24,
StorageError::ObjectNotFound(_, _) => 0x25,
StorageError::VersionNotFound(_, _, _) => 0x26,
StorageError::InvalidUploadID(_, _, _) => 0x27,
StorageError::InvalidVersionID(_, _, _) => 0x28,
StorageError::DataMovementOverwriteErr(_, _, _) => 0x29,
StorageError::ObjectExistsAsDirectory(_, _) => 0x2A,
// StorageError::InsufficientReadQuorum => 0x2B,
// StorageError::InsufficientWriteQuorum => 0x2C,
StorageError::DecommissionNotStarted => 0x2D,
StorageError::InvalidPart(_, _, _) => 0x2E,
StorageError::DoneForNow => 0x2F,
StorageError::DecommissionAlreadyRunning => 0x30,
StorageError::ErasureReadQuorum => 0x31,
StorageError::ErasureWriteQuorum => 0x32,
StorageError::NotFirstDisk => 0x33,
StorageError::FirstDiskWait => 0x34,
StorageError::ConfigNotFound => 0x35,
StorageError::TooManyOpenFiles => 0x36,
StorageError::NoHealRequired => 0x37,
StorageError::BucketPolicyNotFound => 0x38,
}
}
pub fn from_u32(error: u32) -> Option<Self> {
match error {
0x01 => Some(StorageError::Io(std::io::Error::other("Io error"))),
0x02 => Some(StorageError::FaultyDisk),
0x03 => Some(StorageError::DiskFull),
0x04 => Some(StorageError::VolumeNotFound),
0x05 => Some(StorageError::VolumeExists),
0x06 => Some(StorageError::FileNotFound),
0x07 => Some(StorageError::FileVersionNotFound),
0x08 => Some(StorageError::FileNameTooLong),
0x09 => Some(StorageError::FileAccessDenied),
0x0A => Some(StorageError::FileCorrupt),
0x0B => Some(StorageError::IsNotRegular),
0x0C => Some(StorageError::VolumeNotEmpty),
0x0D => Some(StorageError::VolumeAccessDenied),
0x0E => Some(StorageError::CorruptedFormat),
0x0F => Some(StorageError::CorruptedBackend),
0x10 => Some(StorageError::UnformattedDisk),
0x11 => Some(StorageError::DiskNotFound),
0x12 => Some(StorageError::DriveIsRoot),
0x13 => Some(StorageError::FaultyRemoteDisk),
0x14 => Some(StorageError::DiskAccessDenied),
0x15 => Some(StorageError::Unexpected),
0x16 => Some(StorageError::NotImplemented),
0x17 => Some(StorageError::InvalidArgument(Default::default(), Default::default(), Default::default())),
0x18 => Some(StorageError::MethodNotAllowed),
0x19 => Some(StorageError::BucketNotFound(Default::default())),
0x1A => Some(StorageError::BucketNotEmpty(Default::default())),
0x1B => Some(StorageError::BucketNameInvalid(Default::default())),
0x1C => Some(StorageError::ObjectNameInvalid(Default::default(), Default::default())),
0x1D => Some(StorageError::BucketExists(Default::default())),
0x1E => Some(StorageError::StorageFull),
0x1F => Some(StorageError::SlowDown),
0x20 => Some(StorageError::PrefixAccessDenied(Default::default(), Default::default())),
0x21 => Some(StorageError::InvalidUploadIDKeyCombination(Default::default(), Default::default())),
0x22 => Some(StorageError::MalformedUploadID(Default::default())),
0x23 => Some(StorageError::ObjectNameTooLong(Default::default(), Default::default())),
0x24 => Some(StorageError::ObjectNamePrefixAsSlash(Default::default(), Default::default())),
0x25 => Some(StorageError::ObjectNotFound(Default::default(), Default::default())),
0x26 => Some(StorageError::VersionNotFound(Default::default(), Default::default(), Default::default())),
0x27 => Some(StorageError::InvalidUploadID(Default::default(), Default::default(), Default::default())),
0x28 => Some(StorageError::InvalidVersionID(Default::default(), Default::default(), Default::default())),
0x29 => Some(StorageError::DataMovementOverwriteErr(
Default::default(),
Default::default(),
Default::default(),
)),
0x2A => Some(StorageError::ObjectExistsAsDirectory(Default::default(), Default::default())),
// 0x2B => Some(StorageError::InsufficientReadQuorum),
// 0x2C => Some(StorageError::InsufficientWriteQuorum),
0x2D => Some(StorageError::DecommissionNotStarted),
0x2E => Some(StorageError::InvalidPart(Default::default(), Default::default(), Default::default())),
0x2F => Some(StorageError::DoneForNow),
0x30 => Some(StorageError::DecommissionAlreadyRunning),
0x31 => Some(StorageError::ErasureReadQuorum),
0x32 => Some(StorageError::ErasureWriteQuorum),
0x33 => Some(StorageError::NotFirstDisk),
0x34 => Some(StorageError::FirstDiskWait),
0x35 => Some(StorageError::ConfigNotFound),
0x36 => Some(StorageError::TooManyOpenFiles),
0x37 => Some(StorageError::NoHealRequired),
0x38 => Some(StorageError::BucketPolicyNotFound),
_ => None,
}
}
}
impl From<tokio::task::JoinError> for StorageError {
fn from(e: tokio::task::JoinError) -> Self {
StorageError::other(e)
}
}
impl From<serde_json::Error> for StorageError {
fn from(e: serde_json::Error) -> Self {
StorageError::other(e)
}
}
impl From<rmp_serde::encode::Error> for Error {
fn from(e: rmp_serde::encode::Error) -> Self {
Error::other(e)
}
}
impl From<rmp::encode::ValueWriteError> for Error {
fn from(e: rmp::encode::ValueWriteError) -> Self {
Error::other(e)
}
}
impl From<rmp::decode::ValueReadError> for Error {
fn from(e: rmp::decode::ValueReadError) -> Self {
Error::other(e)
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(e: std::string::FromUtf8Error) -> Self {
Error::other(e)
}
}
impl From<rmp::decode::NumValueReadError> for Error {
fn from(e: rmp::decode::NumValueReadError) -> Self {
Error::other(e)
}
}
impl From<rmp_serde::decode::Error> for Error {
fn from(e: rmp_serde::decode::Error) -> Self {
Error::other(e)
}
}
impl From<s3s::xml::SerError> for Error {
fn from(e: s3s::xml::SerError) -> Self {
Error::other(e)
}
}
impl From<s3s::xml::DeError> for Error {
fn from(e: s3s::xml::DeError) -> Self {
Error::other(e)
}
}
impl From<tonic::Status> for Error {
fn from(e: tonic::Status) -> Self {
Error::other(e.to_string())
}
}
impl From<uuid::Error> for Error {
fn from(e: uuid::Error) -> Self {
Error::other(e)
}
}
impl From<time::error::ComponentRange> for Error {
fn from(e: time::error::ComponentRange) -> Self {
Error::other(e)
}
}
pub fn is_err_object_not_found(err: &Error) -> bool {
matches!(err, &Error::FileNotFound) || matches!(err, &Error::ObjectNotFound(_, _))
}
pub fn is_err_version_not_found(err: &Error) -> bool {
matches!(err, &Error::FileVersionNotFound) || matches!(err, &Error::VersionNotFound(_, _, _))
}
pub fn is_err_bucket_exists(err: &Error) -> bool {
matches!(err, &StorageError::BucketExists(_))
}
pub fn is_err_read_quorum(err: &Error) -> bool {
matches!(err, &StorageError::ErasureReadQuorum)
}
pub fn is_err_invalid_upload_id(err: &Error) -> bool {
matches!(err, &StorageError::InvalidUploadID(_, _, _))
}
pub fn is_err_bucket_not_found(err: &Error) -> bool {
matches!(err, &StorageError::VolumeNotFound)
| matches!(err, &StorageError::DiskNotFound)
| matches!(err, &StorageError::BucketNotFound(_))
}
pub fn is_err_data_movement_overwrite(err: &Error) -> bool {
matches!(err, &StorageError::DataMovementOverwriteErr(_, _, _))
}
pub fn is_all_not_found(errs: &[Option<Error>]) -> bool {
for err in errs.iter() {
if let Some(err) = err {
if is_err_object_not_found(err) || is_err_version_not_found(err) || is_err_bucket_not_found(err) {
continue;
}
return false;
}
return false;
}
!errs.is_empty()
}
pub fn is_all_volume_not_found(errs: &[Option<Error>]) -> bool {
for err in errs.iter() {
if let Some(err) = err {
if is_err_bucket_not_found(err) {
continue;
}
return false;
}
return false;
}
!errs.is_empty()
}
// pub fn is_all_not_found(errs: &[Option<Error>]) -> bool {
// for err in errs.iter() {
// if let Some(err) = err {
// if let Some(err) = err.downcast_ref::<DiskError>() {
// match err {
// DiskError::FileNotFound | DiskError::VolumeNotFound | &DiskError::FileVersionNotFound => {
// continue;
// }
// _ => return false,
// }
// }
// }
// return false;
// }
// !errs.is_empty()
// }
pub fn to_object_err(err: Error, params: Vec<&str>) -> Error {
match err {
StorageError::DiskFull => StorageError::StorageFull,
StorageError::FileNotFound => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::ObjectNotFound(bucket, object)
}
StorageError::FileVersionNotFound => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
let version = params.get(2).cloned().unwrap_or_default().to_owned();
StorageError::VersionNotFound(bucket, object, version)
}
StorageError::TooManyOpenFiles => StorageError::SlowDown,
StorageError::FileNameTooLong => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::ObjectNameInvalid(bucket, object)
}
StorageError::VolumeExists => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
StorageError::BucketExists(bucket)
}
StorageError::IsNotRegular => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::ObjectExistsAsDirectory(bucket, object)
}
StorageError::VolumeNotFound => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
StorageError::BucketNotFound(bucket)
}
StorageError::VolumeNotEmpty => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
StorageError::BucketNotEmpty(bucket)
}
StorageError::FileAccessDenied => {
let bucket = params.first().cloned().unwrap_or_default().to_owned();
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
StorageError::PrefixAccessDenied(bucket, object)
}
_ => err,
}
}
pub fn is_network_or_host_down(err: &str, expect_timeouts: bool) -> bool {
err.contains("Connection closed by foreign host")
|| err.contains("TLS handshake timeout")
|| err.contains("i/o timeout")
|| err.contains("connection timed out")
|| err.contains("connection reset by peer")
|| err.contains("broken pipe")
|| err.to_lowercase().contains("503 service unavailable")
|| err.contains("use of closed network connection")
|| err.contains("An existing connection was forcibly closed by the remote host")
|| err.contains("client error (Connect)")
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct GenericError {
pub bucket: String,
pub object: String,
pub version_id: String,
//pub err: Error,
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum ObjectApiError {
#[error("Operation timed out")]
OperationTimedOut,
#[error("etag of the object has changed")]
InvalidETag,
#[error("BackendDown")]
BackendDown(String),
#[error("Unsupported headers in Metadata")]
UnsupportedMetadata,
#[error("Method not allowed: {}/{}", .0.bucket, .0.object)]
MethodNotAllowed(GenericError),
#[error("The operation is not valid for the current state of the object {}/{}({})", .0.bucket, .0.object, .0.version_id)]
InvalidObjectState(GenericError),
}
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
#[error("{}", .message)]
pub struct ErrorResponse {
pub code: S3ErrorCode,
pub message: String,
pub key: Option<String>,
pub bucket_name: Option<String>,
pub region: Option<String>,
pub request_id: Option<String>,
pub host_id: String,
}
pub fn error_resp_to_object_err(err: ErrorResponse, params: Vec<&str>) -> std::io::Error {
let mut bucket = "";
let mut object = "";
let mut version_id = "";
if params.len() >= 1 {
bucket = params[0];
}
if params.len() >= 2 {
object = params[1];
}
if params.len() >= 3 {
version_id = params[2];
}
if is_network_or_host_down(&err.to_string(), false) {
return std::io::Error::other(ObjectApiError::BackendDown(format!("{}", err)));
}
let mut err_ = std::io::Error::other(err.to_string());
let r_err = err;
let mut err = err_;
let bucket = bucket.to_string();
let object = object.to_string();
let version_id = version_id.to_string();
match r_err.code {
/*S3ErrorCode::SlowDownWrite => {
err = StorageError::InsufficientWriteQuorum;//{bucket: bucket, object: object};
}*/
/*S3ErrorCode::SlowDown/* SlowDownRead */ => {
err = std::io::Error::other(StorageError::InsufficientReadQuorum.to_string());
}*/
/*S3ErrorCode::PreconditionFailed => {
err = Error::from(StorageError::PreConditionFailed);
}*/
/*S3ErrorCode::InvalidRange => {
err = Error::from(StorageError::InvalidRange);
}*/
/*S3ErrorCode::BucketAlreadyOwnedByYou => {
err = Error::from(StorageError::BucketAlreadyOwnedByYou);
}*/
S3ErrorCode::BucketNotEmpty => {
err = std::io::Error::other(StorageError::BucketNotEmpty("".to_string()).to_string());
}
/*S3ErrorCode::NoSuchBucketPolicy => {
err = Error::from(StorageError::BucketPolicyNotFound);
}*/
/*S3ErrorCode::NoSuchLifecycleConfiguration => {
err = Error::from(StorageError::BucketLifecycleNotFound);
}*/
S3ErrorCode::InvalidBucketName => {
err = std::io::Error::other(StorageError::BucketNameInvalid(bucket));
}
S3ErrorCode::InvalidPart => {
err = std::io::Error::other(StorageError::InvalidPart(0, bucket, object /* , version_id */));
}
S3ErrorCode::NoSuchBucket => {
err = std::io::Error::other(StorageError::BucketNotFound(bucket));
}
S3ErrorCode::NoSuchKey => {
if object != "" {
err = std::io::Error::other(StorageError::ObjectNotFound(bucket, object));
} else {
err = std::io::Error::other(StorageError::BucketNotFound(bucket));
}
}
S3ErrorCode::NoSuchVersion => {
if object != "" {
err = std::io::Error::other(StorageError::ObjectNotFound(bucket, object)); //, version_id);
} else {
err = std::io::Error::other(StorageError::BucketNotFound(bucket));
}
}
/*S3ErrorCode::XRustFsInvalidObjectName => {
err = Error::from(StorageError::ObjectNameInvalid(bucket, object));
}*/
S3ErrorCode::AccessDenied => {
err = std::io::Error::other(StorageError::PrefixAccessDenied(bucket, object));
}
/*S3ErrorCode::XAmzContentSHA256Mismatch => {
err = hash.SHA256Mismatch{};
}*/
S3ErrorCode::NoSuchUpload => {
err = std::io::Error::other(StorageError::InvalidUploadID(bucket, object, version_id));
}
/*S3ErrorCode::EntityTooSmall => {
err = std::io::Error::other(StorageError::PartTooSmall);
}*/
/*S3ErrorCode::ReplicationPermissionCheck => {
err = std::io::Error::other(StorageError::ReplicationPermissionCheck);
}*/
_ => {
err = std::io::Error::other("err");
}
}
err
}
pub fn storage_to_object_err(err: Error, params: Vec<&str>) -> S3Error {
let storage_err = &err;
let mut bucket: String = "".to_string();
let mut object: String = "".to_string();
if params.len() >= 1 {
bucket = params[0].to_string();
}
if params.len() >= 2 {
object = decode_dir_object(params[1]);
}
return match storage_err {
/*StorageError::NotImplemented => s3_error!(NotImplemented),
StorageError::InvalidArgument(bucket, object, version_id) => {
s3_error!(InvalidArgument, "Invalid arguments provided for {}/{}-{}", bucket, object, version_id)
}*/
StorageError::MethodNotAllowed => S3Error::with_message(
S3ErrorCode::MethodNotAllowed,
ObjectApiError::MethodNotAllowed(GenericError {
bucket: bucket,
object: object,
..Default::default()
})
.to_string(),
),
/*StorageError::BucketNotFound(bucket) => {
s3_error!(NoSuchBucket, "bucket not found {}", bucket)
}
StorageError::BucketNotEmpty(bucket) => s3_error!(BucketNotEmpty, "bucket not empty {}", bucket),
StorageError::BucketNameInvalid(bucket) => s3_error!(InvalidBucketName, "invalid bucket name {}", bucket),
StorageError::ObjectNameInvalid(bucket, object) => {
s3_error!(InvalidArgument, "invalid object name {}/{}", bucket, object)
}
StorageError::BucketExists(bucket) => s3_error!(BucketAlreadyExists, "{}", bucket),
StorageError::StorageFull => s3_error!(ServiceUnavailable, "Storage reached its minimum free drive threshold."),
StorageError::SlowDown => s3_error!(SlowDown, "Please reduce your request rate"),
StorageError::PrefixAccessDenied(bucket, object) => {
s3_error!(AccessDenied, "PrefixAccessDenied {}/{}", bucket, object)
}
StorageError::InvalidUploadIDKeyCombination(bucket, object) => {
s3_error!(InvalidArgument, "Invalid UploadID KeyCombination: {}/{}", bucket, object)
}
StorageError::MalformedUploadID(bucket) => s3_error!(InvalidArgument, "Malformed UploadID: {}", bucket),
StorageError::ObjectNameTooLong(bucket, object) => {
s3_error!(InvalidArgument, "Object name too long: {}/{}", bucket, object)
}
StorageError::ObjectNamePrefixAsSlash(bucket, object) => {
s3_error!(InvalidArgument, "Object name contains forward slash as prefix: {}/{}", bucket, object)
}
StorageError::ObjectNotFound(bucket, object) => s3_error!(NoSuchKey, "{}/{}", bucket, object),
StorageError::VersionNotFound(bucket, object, version_id) => {
s3_error!(NoSuchVersion, "{}/{}/{}", bucket, object, version_id)
}
StorageError::InvalidUploadID(bucket, object, version_id) => {
s3_error!(InvalidPart, "Invalid upload id: {}/{}-{}", bucket, object, version_id)
}
StorageError::InvalidVersionID(bucket, object, version_id) => {
s3_error!(InvalidArgument, "Invalid version id: {}/{}-{}", bucket, object, version_id)
}
// extended
StorageError::DataMovementOverwriteErr(bucket, object, version_id) => s3_error!(
InvalidArgument,
"invalid data movement operation, source and destination pool are the same for : {}/{}-{}",
bucket,
object,
version_id
),
// extended
StorageError::ObjectExistsAsDirectory(bucket, object) => {
s3_error!(InvalidArgument, "Object exists on :{} as directory {}", bucket, object)
}
StorageError::InsufficientReadQuorum => {
s3_error!(SlowDown, "Storage resources are insufficient for the read operation")
}
StorageError::InsufficientWriteQuorum => {
s3_error!(SlowDown, "Storage resources are insufficient for the write operation")
}
StorageError::DecommissionNotStarted => s3_error!(InvalidArgument, "Decommission Not Started"),
StorageError::VolumeNotFound(bucket) => {
s3_error!(NoSuchBucket, "bucket not found {}", bucket)
}
StorageError::InvalidPart(bucket, object, version_id) => {
s3_error!(
InvalidPart,
"Specified part could not be found. PartNumber {}, Expected {}, got {}",
bucket,
object,
version_id
)
}
StorageError::DoneForNow => s3_error!(InternalError, "DoneForNow"),*/
_ => s3s::S3Error::with_message(S3ErrorCode::Custom("err".into()), err.to_string()),
};
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Error as IoError, ErrorKind};
#[test]
fn test_storage_error_to_u32() {
// Test Io error uses 0x01
let io_error = StorageError::Io(IoError::other("test"));
assert_eq!(io_error.to_u32(), 0x01);
// Test other errors have correct codes
assert_eq!(StorageError::FaultyDisk.to_u32(), 0x02);
assert_eq!(StorageError::DiskFull.to_u32(), 0x03);
assert_eq!(StorageError::VolumeNotFound.to_u32(), 0x04);
assert_eq!(StorageError::VolumeExists.to_u32(), 0x05);
assert_eq!(StorageError::FileNotFound.to_u32(), 0x06);
assert_eq!(StorageError::DecommissionAlreadyRunning.to_u32(), 0x30);
}
#[test]
fn test_storage_error_from_u32() {
// Test Io error conversion
assert!(matches!(StorageError::from_u32(0x01), Some(StorageError::Io(_))));
// Test other error conversions
assert!(matches!(StorageError::from_u32(0x02), Some(StorageError::FaultyDisk)));
assert!(matches!(StorageError::from_u32(0x03), Some(StorageError::DiskFull)));
assert!(matches!(StorageError::from_u32(0x04), Some(StorageError::VolumeNotFound)));
assert!(matches!(StorageError::from_u32(0x30), Some(StorageError::DecommissionAlreadyRunning)));
// Test invalid code returns None
assert!(StorageError::from_u32(0xFF).is_none());
}
#[test]
fn test_storage_error_partial_eq() {
// Test IO error comparison
let io1 = StorageError::Io(IoError::new(ErrorKind::NotFound, "file not found"));
let io2 = StorageError::Io(IoError::new(ErrorKind::NotFound, "file not found"));
let io3 = StorageError::Io(IoError::new(ErrorKind::PermissionDenied, "access denied"));
assert_eq!(io1, io2);
assert_ne!(io1, io3);
// Test non-IO error comparison
let bucket1 = StorageError::BucketExists("test".to_string());
let bucket2 = StorageError::BucketExists("different".to_string());
assert_eq!(bucket1, bucket2); // Same error type, different parameters
let disk_error = StorageError::DiskFull;
assert_ne!(bucket1, disk_error);
}
#[test]
fn test_storage_error_from_disk_error() {
// Test conversion from DiskError
let disk_io = DiskError::Io(IoError::other("disk io error"));
let storage_error: StorageError = disk_io.into();
assert!(matches!(storage_error, StorageError::Io(_)));
let disk_full = DiskError::DiskFull;
let storage_error: StorageError = disk_full.into();
assert_eq!(storage_error, StorageError::DiskFull);
let file_not_found = DiskError::FileNotFound;
let storage_error: StorageError = file_not_found.into();
assert_eq!(storage_error, StorageError::FileNotFound);
}
#[test]
fn test_storage_error_from_io_error() {
// Test direct IO error conversion
let io_error = IoError::new(ErrorKind::NotFound, "test error");
let storage_error: StorageError = io_error.into();
assert!(matches!(storage_error, StorageError::Io(_)));
// Test IO error containing DiskError
let disk_error = DiskError::DiskFull;
let io_with_disk_error = IoError::other(disk_error);
let storage_error: StorageError = io_with_disk_error.into();
assert_eq!(storage_error, StorageError::DiskFull);
// Test IO error containing StorageError
let original_storage_error = StorageError::BucketNotFound("test".to_string());
let io_with_storage_error = IoError::other(original_storage_error.clone());
let recovered_storage_error: StorageError = io_with_storage_error.into();
assert_eq!(recovered_storage_error, original_storage_error);
}
#[test]
fn test_storage_error_to_io_error() {
// Test conversion to IO error
let storage_error = StorageError::DiskFull;
let io_error: IoError = storage_error.into();
assert_eq!(io_error.kind(), ErrorKind::Other);
// Test IO error round trip
let original_io = IoError::new(ErrorKind::PermissionDenied, "access denied");
let storage_error = StorageError::Io(original_io);
let converted_io: IoError = storage_error.into();
assert_eq!(converted_io.kind(), ErrorKind::PermissionDenied);
}
#[test]
fn test_bucket_and_object_errors() {
let bucket_not_found = StorageError::BucketNotFound("mybucket".to_string());
let object_not_found = StorageError::ObjectNotFound("mybucket".to_string(), "myobject".to_string());
let version_not_found = StorageError::VersionNotFound("mybucket".to_string(), "myobject".to_string(), "v1".to_string());
// Test different error codes
assert_ne!(bucket_not_found.to_u32(), object_not_found.to_u32());
assert_ne!(object_not_found.to_u32(), version_not_found.to_u32());
// Test error messages contain expected information
assert!(bucket_not_found.to_string().contains("mybucket"));
assert!(object_not_found.to_string().contains("mybucket"));
assert!(object_not_found.to_string().contains("myobject"));
assert!(version_not_found.to_string().contains("v1"));
}
#[test]
fn test_upload_id_errors() {
let invalid_upload = StorageError::InvalidUploadID("bucket".to_string(), "object".to_string(), "uploadid".to_string());
let malformed_upload = StorageError::MalformedUploadID("badid".to_string());
assert_ne!(invalid_upload.to_u32(), malformed_upload.to_u32());
assert!(invalid_upload.to_string().contains("uploadid"));
assert!(malformed_upload.to_string().contains("badid"));
}
#[test]
fn test_round_trip_conversion() {
// Test that to_u32 and from_u32 are consistent for all variants
let test_errors = vec![
StorageError::FaultyDisk,
StorageError::DiskFull,
StorageError::VolumeNotFound,
StorageError::BucketExists("test".to_string()),
StorageError::ObjectNotFound("bucket".to_string(), "object".to_string()),
StorageError::DecommissionAlreadyRunning,
];
for original_error in test_errors {
let code = original_error.to_u32();
if let Some(recovered_error) = StorageError::from_u32(code) {
// For errors with parameters, we only check the variant type
assert_eq!(std::mem::discriminant(&original_error), std::mem::discriminant(&recovered_error));
} else {
panic!("Failed to recover error from code: {:#x}", code);
}
}
}
#[test]
fn test_storage_error_io_roundtrip() {
// Test StorageError -> std::io::Error -> StorageError roundtrip conversion
let original_storage_errors = vec![
StorageError::FileNotFound,
StorageError::VolumeNotFound,
StorageError::DiskFull,
StorageError::FileCorrupt,
StorageError::MethodNotAllowed,
StorageError::BucketExists("test-bucket".to_string()),
StorageError::ObjectNotFound("bucket".to_string(), "object".to_string()),
];
for original_error in original_storage_errors {
// Convert to io::Error and back
let io_error: std::io::Error = original_error.clone().into();
let recovered_error: StorageError = io_error.into();
// Check that conversion preserves the essential error information
match &original_error {
StorageError::Io(_) => {
// Io errors should maintain their inner structure
assert!(matches!(recovered_error, StorageError::Io(_)));
}
_ => {
// Other errors should be recoverable via downcast or match to equivalent type
assert_eq!(original_error.to_u32(), recovered_error.to_u32());
}
}
}
}
#[test]
fn test_io_error_with_storage_error_inside() {
// Test that io::Error containing StorageError can be properly converted back
let original_storage_error = StorageError::FileNotFound;
let io_with_storage_error = std::io::Error::other(original_storage_error.clone());
// Convert io::Error back to StorageError
let recovered_storage_error: StorageError = io_with_storage_error.into();
assert_eq!(original_storage_error, recovered_storage_error);
}
#[test]
fn test_io_error_with_disk_error_inside() {
// Test io::Error containing DiskError -> StorageError conversion
let original_disk_error = DiskError::FileNotFound;
let io_with_disk_error = std::io::Error::other(original_disk_error.clone());
// Convert io::Error to StorageError
let storage_error: StorageError = io_with_disk_error.into();
assert_eq!(storage_error, StorageError::FileNotFound);
}
#[test]
fn test_nested_error_conversion_chain() {
// Test complex conversion chain: DiskError -> StorageError -> io::Error -> StorageError
let original_disk_error = DiskError::DiskFull;
let storage_error1: StorageError = original_disk_error.into();
let io_error: std::io::Error = storage_error1.into();
let storage_error2: StorageError = io_error.into();
assert_eq!(storage_error2, StorageError::DiskFull);
}
#[test]
fn test_storage_error_different_io_kinds() {
use std::io::ErrorKind;
let test_cases = vec![
(ErrorKind::NotFound, "not found"),
(ErrorKind::PermissionDenied, "permission denied"),
(ErrorKind::ConnectionRefused, "connection refused"),
(ErrorKind::TimedOut, "timed out"),
(ErrorKind::InvalidInput, "invalid input"),
(ErrorKind::BrokenPipe, "broken pipe"),
];
for (kind, message) in test_cases {
let io_error = std::io::Error::new(kind, message);
let storage_error: StorageError = io_error.into();
// Should become StorageError::Io with the same kind and message
match storage_error {
StorageError::Io(inner_io) => {
assert_eq!(inner_io.kind(), kind);
assert!(inner_io.to_string().contains(message));
}
_ => panic!("Expected StorageError::Io variant for kind: {:?}", kind),
}
}
}
#[test]
fn test_storage_error_to_io_error_preserves_information() {
let test_cases = vec![
StorageError::FileNotFound,
StorageError::VolumeNotFound,
StorageError::DiskFull,
StorageError::FileCorrupt,
StorageError::MethodNotAllowed,
StorageError::StorageFull,
StorageError::SlowDown,
StorageError::BucketExists("test-bucket".to_string()),
];
for storage_error in test_cases {
let io_error: std::io::Error = storage_error.clone().into();
// Error message should be preserved
assert!(io_error.to_string().contains(&storage_error.to_string()));
// Should be able to downcast back to StorageError
let recovered_error = io_error.downcast::<StorageError>();
assert!(recovered_error.is_ok());
assert_eq!(recovered_error.unwrap(), storage_error);
}
}
#[test]
fn test_storage_error_io_variant_preservation() {
// Test StorageError::Io variant preserves original io::Error
let original_io = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof");
let storage_error = StorageError::Io(original_io);
let converted_io: std::io::Error = storage_error.into();
assert_eq!(converted_io.kind(), std::io::ErrorKind::UnexpectedEof);
assert!(converted_io.to_string().contains("unexpected eof"));
}
#[test]
fn test_from_filemeta_error_conversions() {
// Test conversions from rustfs_filemeta::Error
use rustfs_filemeta::Error as FilemetaError;
let filemeta_errors = vec![
(FilemetaError::FileNotFound, StorageError::FileNotFound),
(FilemetaError::FileVersionNotFound, StorageError::FileVersionNotFound),
(FilemetaError::FileCorrupt, StorageError::FileCorrupt),
(FilemetaError::MethodNotAllowed, StorageError::MethodNotAllowed),
(FilemetaError::VolumeNotFound, StorageError::VolumeNotFound),
(FilemetaError::DoneForNow, StorageError::DoneForNow),
(FilemetaError::Unexpected, StorageError::Unexpected),
];
for (filemeta_error, expected_storage_error) in filemeta_errors {
let converted_storage_error: StorageError = filemeta_error.into();
assert_eq!(converted_storage_error, expected_storage_error);
// Test reverse conversion
let converted_back: rustfs_filemeta::Error = converted_storage_error.into();
assert_eq!(converted_back, expected_storage_error.into());
}
}
#[test]
fn test_error_message_consistency() {
let storage_errors = vec![
StorageError::BucketNotFound("test-bucket".to_string()),
StorageError::ObjectNotFound("bucket".to_string(), "object".to_string()),
StorageError::VersionNotFound("bucket".to_string(), "object".to_string(), "v1".to_string()),
StorageError::InvalidUploadID("bucket".to_string(), "object".to_string(), "upload123".to_string()),
];
for storage_error in storage_errors {
let original_message = storage_error.to_string();
let io_error: std::io::Error = storage_error.clone().into();
// The io::Error should contain the original error message or info
assert!(io_error.to_string().contains(&original_message));
}
}
#[test]
fn test_error_equality_after_conversion() {
let storage_errors = vec![
StorageError::FileNotFound,
StorageError::VolumeNotFound,
StorageError::DiskFull,
StorageError::MethodNotAllowed,
];
for original_error in storage_errors {
// Test that equality is preserved through conversion
let io_error: std::io::Error = original_error.clone().into();
let recovered_error: StorageError = io_error.into();
assert_eq!(original_error, recovered_error);
}
}
}