mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 08:18:18 +00:00
1444 lines
64 KiB
Rust
1444 lines
64 KiB
Rust
// Copyright 2024 RustFS Team
|
||
//
|
||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
// you may not use this file except in compliance with the License.
|
||
// You may obtain a copy of the License at
|
||
//
|
||
// http://www.apache.org/licenses/LICENSE-2.0
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
use crate::bucket::error::BucketMetadataError;
|
||
use crate::disk::error::DiskError;
|
||
use rustfs_storage_api::StorageErrorCode;
|
||
use rustfs_utils::path::decode_dir_object;
|
||
use s3s::{S3Error, S3ErrorCode};
|
||
|
||
pub type Error = StorageError;
|
||
pub type Result<T> = core::result::Result<T, Error>;
|
||
|
||
/// Storage layer error type covering disk, volume, bucket, object, multipart,
|
||
/// erasure-coding, and operational error conditions.
|
||
///
|
||
/// Variants are organized by domain:
|
||
/// - **Disk / Volume** – low-level storage I/O
|
||
/// - **Bucket** – bucket-level operations
|
||
/// - **Object** – object-level operations (including versioning)
|
||
/// - **Multipart** – multipart upload lifecycle
|
||
/// - **Erasure / Quorum** – erasure coding and quorum failures
|
||
/// - **Operational** – decommission, healing, rate-limiting, etc.
|
||
/// - **Generic** – I/O, locks, and catch-all errors
|
||
#[derive(Debug, thiserror::Error)]
|
||
pub enum StorageError {
|
||
// ── Disk / Volume ────────────────────────────────────────────────
|
||
#[error("Faulty disk")]
|
||
FaultyDisk,
|
||
#[error("Faulty remote disk")]
|
||
FaultyRemoteDisk,
|
||
#[error("Disk full")]
|
||
DiskFull,
|
||
#[error("Disk not found")]
|
||
DiskNotFound,
|
||
#[error("Disk access denied")]
|
||
DiskAccessDenied,
|
||
#[error("Drive is root")]
|
||
DriveIsRoot,
|
||
#[error("Unformatted disk")]
|
||
UnformattedDisk,
|
||
#[error("Corrupted format")]
|
||
CorruptedFormat,
|
||
#[error("Corrupted backend")]
|
||
CorruptedBackend,
|
||
#[error("Too many open files")]
|
||
TooManyOpenFiles,
|
||
|
||
#[error("Volume not found")]
|
||
VolumeNotFound,
|
||
#[error("Volume exists")]
|
||
VolumeExists,
|
||
#[error("Volume not empty")]
|
||
VolumeNotEmpty,
|
||
#[error("Volume access denied")]
|
||
VolumeAccessDenied,
|
||
|
||
#[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,
|
||
|
||
// ── Bucket ───────────────────────────────────────────────────────
|
||
#[error("Bucket not found: {0}")]
|
||
BucketNotFound(String),
|
||
#[error("Bucket exists: {0}")]
|
||
BucketExists(String),
|
||
#[error("Bucket not empty: {0}")]
|
||
BucketNotEmpty(String),
|
||
#[error("Bucket name invalid: {0}")]
|
||
BucketNameInvalid(String),
|
||
|
||
// ── Object ───────────────────────────────────────────────────────
|
||
#[error("Object not found: {0}/{1}")]
|
||
ObjectNotFound(String, String),
|
||
#[error("Object name invalid: {0}/{1}")]
|
||
ObjectNameInvalid(String, 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 exists on :{0} as directory {1}")]
|
||
ObjectExistsAsDirectory(String, String),
|
||
#[error("Version not found: {0}/{1}-{2}")]
|
||
VersionNotFound(String, 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("Prefix access is denied:{0}/{1}")]
|
||
PrefixAccessDenied(String, String),
|
||
|
||
// ── Multipart ────────────────────────────────────────────────────
|
||
#[error("Invalid upload id: {0}/{1}-{2}")]
|
||
InvalidUploadID(String, String, String),
|
||
#[error("Invalid UploadID KeyCombination: {0}/{1}")]
|
||
InvalidUploadIDKeyCombination(String, String),
|
||
#[error("Malformed UploadID: {0}")]
|
||
MalformedUploadID(String),
|
||
#[error("Specified part could not be found. PartNumber {0}, Expected {1}, got {2}")]
|
||
InvalidPart(usize, String, String),
|
||
#[error("Invalid part number: {0}")]
|
||
InvalidPartNumber(usize),
|
||
#[error("Your proposed upload is smaller than the minimum allowed size. Part {0} size {1} is less than minimum {2}")]
|
||
EntityTooSmall(usize, i64, i64),
|
||
|
||
// ── Erasure / Quorum ─────────────────────────────────────────────
|
||
#[error("erasure read quorum")]
|
||
ErasureReadQuorum,
|
||
#[error("erasure write quorum")]
|
||
ErasureWriteQuorum,
|
||
#[error("Storage resources are insufficient for the read operation: {0}/{1}")]
|
||
InsufficientReadQuorum(String, String),
|
||
#[error("Storage resources are insufficient for the write operation: {0}/{1}")]
|
||
InsufficientWriteQuorum(String, String),
|
||
#[error("not first disk")]
|
||
NotFirstDisk,
|
||
#[error("first disk wait")]
|
||
FirstDiskWait,
|
||
|
||
// ── Operational ──────────────────────────────────────────────────
|
||
#[error("Storage reached its minimum free drive threshold.")]
|
||
StorageFull,
|
||
#[error("Please reduce your request rate")]
|
||
SlowDown,
|
||
#[error("Decommission not started")]
|
||
DecommissionNotStarted,
|
||
#[error("Decommission already running")]
|
||
DecommissionAlreadyRunning,
|
||
#[error("Rebalance already running")]
|
||
RebalanceAlreadyRunning,
|
||
#[error("Operation canceled")]
|
||
OperationCanceled,
|
||
#[error("No heal required")]
|
||
NoHealRequired,
|
||
#[error("DoneForNow")]
|
||
DoneForNow,
|
||
#[error("Config not found")]
|
||
ConfigNotFound,
|
||
#[error("Precondition failed")]
|
||
PreconditionFailed,
|
||
#[error("Not modified")]
|
||
NotModified,
|
||
#[error("Invalid range specified: {0}")]
|
||
InvalidRangeSpec(String),
|
||
#[error("Namespace lock quorum unavailable for {mode} lock on {bucket}/{object}: required {required}, achieved {achieved}")]
|
||
NamespaceLockQuorumUnavailable {
|
||
mode: &'static str,
|
||
bucket: String,
|
||
object: String,
|
||
required: usize,
|
||
achieved: usize,
|
||
},
|
||
|
||
// ── Generic ──────────────────────────────────────────────────────
|
||
#[error("Unexpected error")]
|
||
Unexpected,
|
||
#[error("not implemented")]
|
||
NotImplemented,
|
||
#[error("Invalid arguments provided for {0}/{1}-{2}")]
|
||
InvalidArgument(String, String, String),
|
||
#[error("method not allowed")]
|
||
MethodNotAllowed,
|
||
#[error("Io error: {0}")]
|
||
Io(std::io::Error),
|
||
#[error("Lock error: {0}")]
|
||
Lock(#[from] rustfs_lock::LockError),
|
||
}
|
||
|
||
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))
|
||
}
|
||
|
||
pub fn is_not_found(&self) -> bool {
|
||
matches!(
|
||
self,
|
||
StorageError::FileNotFound
|
||
| StorageError::ObjectNotFound(_, _)
|
||
| StorageError::FileVersionNotFound
|
||
| StorageError::VersionNotFound(_, _, _)
|
||
| StorageError::VolumeNotFound
|
||
| StorageError::DiskNotFound
|
||
| StorageError::BucketNotFound(_)
|
||
)
|
||
}
|
||
|
||
pub fn is_quorum_error(&self) -> bool {
|
||
matches!(
|
||
self,
|
||
StorageError::ErasureReadQuorum
|
||
| StorageError::ErasureWriteQuorum
|
||
| StorageError::InsufficientReadQuorum(_, _)
|
||
| StorageError::InsufficientWriteQuorum(_, _)
|
||
| StorageError::NamespaceLockQuorumUnavailable { .. }
|
||
)
|
||
}
|
||
}
|
||
|
||
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<BucketMetadataError> for Error {
|
||
fn from(e: BucketMetadataError) -> Self {
|
||
match e {
|
||
BucketMetadataError::TaggingNotFound => Error::ConfigNotFound,
|
||
BucketMetadataError::BucketPolicyNotFound => Error::ConfigNotFound,
|
||
BucketMetadataError::BucketObjectLockConfigNotFound => Error::ConfigNotFound,
|
||
BucketMetadataError::BucketLifecycleNotFound => Error::ConfigNotFound,
|
||
BucketMetadataError::BucketSSEConfigNotFound => Error::ConfigNotFound,
|
||
BucketMetadataError::BucketQuotaConfigNotFound => Error::ConfigNotFound,
|
||
BucketMetadataError::BucketReplicationConfigNotFound => Error::ConfigNotFound,
|
||
BucketMetadataError::BucketRemoteTargetNotFound => Error::ConfigNotFound,
|
||
_ => Error::other(e),
|
||
}
|
||
}
|
||
}
|
||
|
||
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::InvalidPart(a, b, c) => StorageError::InvalidPart(*a, b.clone(), c.clone()),
|
||
StorageError::EntityTooSmall(a, b, c) => StorageError::EntityTooSmall(*a, *b, *c),
|
||
StorageError::DoneForNow => StorageError::DoneForNow,
|
||
StorageError::DecommissionAlreadyRunning => StorageError::DecommissionAlreadyRunning,
|
||
StorageError::RebalanceAlreadyRunning => StorageError::RebalanceAlreadyRunning,
|
||
StorageError::OperationCanceled => StorageError::OperationCanceled,
|
||
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::Lock(e) => StorageError::Lock(e.clone()),
|
||
StorageError::InsufficientReadQuorum(a, b) => StorageError::InsufficientReadQuorum(a.clone(), b.clone()),
|
||
StorageError::InsufficientWriteQuorum(a, b) => StorageError::InsufficientWriteQuorum(a.clone(), b.clone()),
|
||
StorageError::PreconditionFailed => StorageError::PreconditionFailed,
|
||
StorageError::NotModified => StorageError::NotModified,
|
||
StorageError::InvalidPartNumber(a) => StorageError::InvalidPartNumber(*a),
|
||
StorageError::InvalidRangeSpec(a) => StorageError::InvalidRangeSpec(a.clone()),
|
||
StorageError::NamespaceLockQuorumUnavailable {
|
||
mode,
|
||
bucket,
|
||
object,
|
||
required,
|
||
achieved,
|
||
} => StorageError::NamespaceLockQuorumUnavailable {
|
||
mode,
|
||
bucket: bucket.clone(),
|
||
object: object.clone(),
|
||
required: *required,
|
||
achieved: *achieved,
|
||
},
|
||
}
|
||
}
|
||
}
|
||
|
||
impl StorageError {
|
||
fn code(&self) -> StorageErrorCode {
|
||
match self {
|
||
StorageError::Io(_) => StorageErrorCode::Io,
|
||
StorageError::FaultyDisk => StorageErrorCode::FaultyDisk,
|
||
StorageError::DiskFull => StorageErrorCode::DiskFull,
|
||
StorageError::VolumeNotFound => StorageErrorCode::VolumeNotFound,
|
||
StorageError::VolumeExists => StorageErrorCode::VolumeExists,
|
||
StorageError::FileNotFound => StorageErrorCode::FileNotFound,
|
||
StorageError::FileVersionNotFound => StorageErrorCode::FileVersionNotFound,
|
||
StorageError::FileNameTooLong => StorageErrorCode::FileNameTooLong,
|
||
StorageError::FileAccessDenied => StorageErrorCode::FileAccessDenied,
|
||
StorageError::FileCorrupt => StorageErrorCode::FileCorrupt,
|
||
StorageError::IsNotRegular => StorageErrorCode::IsNotRegular,
|
||
StorageError::VolumeNotEmpty => StorageErrorCode::VolumeNotEmpty,
|
||
StorageError::VolumeAccessDenied => StorageErrorCode::VolumeAccessDenied,
|
||
StorageError::CorruptedFormat => StorageErrorCode::CorruptedFormat,
|
||
StorageError::CorruptedBackend => StorageErrorCode::CorruptedBackend,
|
||
StorageError::UnformattedDisk => StorageErrorCode::UnformattedDisk,
|
||
StorageError::DiskNotFound => StorageErrorCode::DiskNotFound,
|
||
StorageError::DriveIsRoot => StorageErrorCode::DriveIsRoot,
|
||
StorageError::FaultyRemoteDisk => StorageErrorCode::FaultyRemoteDisk,
|
||
StorageError::DiskAccessDenied => StorageErrorCode::DiskAccessDenied,
|
||
StorageError::Unexpected => StorageErrorCode::Unexpected,
|
||
StorageError::NotImplemented => StorageErrorCode::NotImplemented,
|
||
StorageError::InvalidArgument(_, _, _) => StorageErrorCode::InvalidArgument,
|
||
StorageError::MethodNotAllowed => StorageErrorCode::MethodNotAllowed,
|
||
StorageError::BucketNotFound(_) => StorageErrorCode::BucketNotFound,
|
||
StorageError::BucketNotEmpty(_) => StorageErrorCode::BucketNotEmpty,
|
||
StorageError::BucketNameInvalid(_) => StorageErrorCode::BucketNameInvalid,
|
||
StorageError::ObjectNameInvalid(_, _) => StorageErrorCode::ObjectNameInvalid,
|
||
StorageError::BucketExists(_) => StorageErrorCode::BucketExists,
|
||
StorageError::StorageFull => StorageErrorCode::StorageFull,
|
||
StorageError::SlowDown => StorageErrorCode::SlowDown,
|
||
StorageError::PrefixAccessDenied(_, _) => StorageErrorCode::PrefixAccessDenied,
|
||
StorageError::InvalidUploadIDKeyCombination(_, _) => StorageErrorCode::InvalidUploadIDKeyCombination,
|
||
StorageError::MalformedUploadID(_) => StorageErrorCode::MalformedUploadID,
|
||
StorageError::ObjectNameTooLong(_, _) => StorageErrorCode::ObjectNameTooLong,
|
||
StorageError::ObjectNamePrefixAsSlash(_, _) => StorageErrorCode::ObjectNamePrefixAsSlash,
|
||
StorageError::ObjectNotFound(_, _) => StorageErrorCode::ObjectNotFound,
|
||
StorageError::VersionNotFound(_, _, _) => StorageErrorCode::VersionNotFound,
|
||
StorageError::InvalidUploadID(_, _, _) => StorageErrorCode::InvalidUploadID,
|
||
StorageError::InvalidVersionID(_, _, _) => StorageErrorCode::InvalidVersionID,
|
||
StorageError::DataMovementOverwriteErr(_, _, _) => StorageErrorCode::DataMovementOverwriteErr,
|
||
StorageError::ObjectExistsAsDirectory(_, _) => StorageErrorCode::ObjectExistsAsDirectory,
|
||
StorageError::DecommissionNotStarted => StorageErrorCode::DecommissionNotStarted,
|
||
StorageError::InvalidPart(_, _, _) => StorageErrorCode::InvalidPart,
|
||
StorageError::DoneForNow => StorageErrorCode::DoneForNow,
|
||
StorageError::DecommissionAlreadyRunning => StorageErrorCode::DecommissionAlreadyRunning,
|
||
StorageError::RebalanceAlreadyRunning => StorageErrorCode::RebalanceAlreadyRunning,
|
||
StorageError::OperationCanceled => StorageErrorCode::OperationCanceled,
|
||
StorageError::ErasureReadQuorum => StorageErrorCode::ErasureReadQuorum,
|
||
StorageError::ErasureWriteQuorum => StorageErrorCode::ErasureWriteQuorum,
|
||
StorageError::NotFirstDisk => StorageErrorCode::NotFirstDisk,
|
||
StorageError::FirstDiskWait => StorageErrorCode::FirstDiskWait,
|
||
StorageError::ConfigNotFound => StorageErrorCode::ConfigNotFound,
|
||
StorageError::TooManyOpenFiles => StorageErrorCode::TooManyOpenFiles,
|
||
StorageError::NoHealRequired => StorageErrorCode::NoHealRequired,
|
||
StorageError::Lock(_) => StorageErrorCode::Lock,
|
||
StorageError::InsufficientReadQuorum(_, _) => StorageErrorCode::InsufficientReadQuorum,
|
||
StorageError::InsufficientWriteQuorum(_, _) => StorageErrorCode::InsufficientWriteQuorum,
|
||
StorageError::PreconditionFailed => StorageErrorCode::PreconditionFailed,
|
||
StorageError::EntityTooSmall(_, _, _) => StorageErrorCode::EntityTooSmall,
|
||
StorageError::InvalidRangeSpec(_) => StorageErrorCode::InvalidRangeSpec,
|
||
StorageError::NotModified => StorageErrorCode::NotModified,
|
||
StorageError::InvalidPartNumber(_) => StorageErrorCode::InvalidPartNumber,
|
||
StorageError::NamespaceLockQuorumUnavailable { .. } => StorageErrorCode::NamespaceLockQuorumUnavailable,
|
||
}
|
||
}
|
||
|
||
pub fn to_u32(&self) -> u32 {
|
||
self.code().as_u32()
|
||
}
|
||
|
||
pub fn from_u32(error: u32) -> Option<Self> {
|
||
match StorageErrorCode::from_u32(error)? {
|
||
StorageErrorCode::Io => Some(StorageError::Io(std::io::Error::other("Io error"))),
|
||
StorageErrorCode::FaultyDisk => Some(StorageError::FaultyDisk),
|
||
StorageErrorCode::DiskFull => Some(StorageError::DiskFull),
|
||
StorageErrorCode::VolumeNotFound => Some(StorageError::VolumeNotFound),
|
||
StorageErrorCode::VolumeExists => Some(StorageError::VolumeExists),
|
||
StorageErrorCode::FileNotFound => Some(StorageError::FileNotFound),
|
||
StorageErrorCode::FileVersionNotFound => Some(StorageError::FileVersionNotFound),
|
||
StorageErrorCode::FileNameTooLong => Some(StorageError::FileNameTooLong),
|
||
StorageErrorCode::FileAccessDenied => Some(StorageError::FileAccessDenied),
|
||
StorageErrorCode::FileCorrupt => Some(StorageError::FileCorrupt),
|
||
StorageErrorCode::IsNotRegular => Some(StorageError::IsNotRegular),
|
||
StorageErrorCode::VolumeNotEmpty => Some(StorageError::VolumeNotEmpty),
|
||
StorageErrorCode::VolumeAccessDenied => Some(StorageError::VolumeAccessDenied),
|
||
StorageErrorCode::CorruptedFormat => Some(StorageError::CorruptedFormat),
|
||
StorageErrorCode::CorruptedBackend => Some(StorageError::CorruptedBackend),
|
||
StorageErrorCode::UnformattedDisk => Some(StorageError::UnformattedDisk),
|
||
StorageErrorCode::DiskNotFound => Some(StorageError::DiskNotFound),
|
||
StorageErrorCode::DriveIsRoot => Some(StorageError::DriveIsRoot),
|
||
StorageErrorCode::FaultyRemoteDisk => Some(StorageError::FaultyRemoteDisk),
|
||
StorageErrorCode::DiskAccessDenied => Some(StorageError::DiskAccessDenied),
|
||
StorageErrorCode::Unexpected => Some(StorageError::Unexpected),
|
||
StorageErrorCode::NotImplemented => Some(StorageError::NotImplemented),
|
||
StorageErrorCode::InvalidArgument => {
|
||
Some(StorageError::InvalidArgument(Default::default(), Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::MethodNotAllowed => Some(StorageError::MethodNotAllowed),
|
||
StorageErrorCode::BucketNotFound => Some(StorageError::BucketNotFound(Default::default())),
|
||
StorageErrorCode::BucketNotEmpty => Some(StorageError::BucketNotEmpty(Default::default())),
|
||
StorageErrorCode::BucketNameInvalid => Some(StorageError::BucketNameInvalid(Default::default())),
|
||
StorageErrorCode::ObjectNameInvalid => Some(StorageError::ObjectNameInvalid(Default::default(), Default::default())),
|
||
StorageErrorCode::BucketExists => Some(StorageError::BucketExists(Default::default())),
|
||
StorageErrorCode::StorageFull => Some(StorageError::StorageFull),
|
||
StorageErrorCode::SlowDown => Some(StorageError::SlowDown),
|
||
StorageErrorCode::PrefixAccessDenied => {
|
||
Some(StorageError::PrefixAccessDenied(Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::InvalidUploadIDKeyCombination => {
|
||
Some(StorageError::InvalidUploadIDKeyCombination(Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::MalformedUploadID => Some(StorageError::MalformedUploadID(Default::default())),
|
||
StorageErrorCode::ObjectNameTooLong => Some(StorageError::ObjectNameTooLong(Default::default(), Default::default())),
|
||
StorageErrorCode::ObjectNamePrefixAsSlash => {
|
||
Some(StorageError::ObjectNamePrefixAsSlash(Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::ObjectNotFound => Some(StorageError::ObjectNotFound(Default::default(), Default::default())),
|
||
StorageErrorCode::VersionNotFound => {
|
||
Some(StorageError::VersionNotFound(Default::default(), Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::InvalidUploadID => {
|
||
Some(StorageError::InvalidUploadID(Default::default(), Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::InvalidVersionID => {
|
||
Some(StorageError::InvalidVersionID(Default::default(), Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::DataMovementOverwriteErr => Some(StorageError::DataMovementOverwriteErr(
|
||
Default::default(),
|
||
Default::default(),
|
||
Default::default(),
|
||
)),
|
||
StorageErrorCode::ObjectExistsAsDirectory => {
|
||
Some(StorageError::ObjectExistsAsDirectory(Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::DecommissionNotStarted => Some(StorageError::DecommissionNotStarted),
|
||
StorageErrorCode::InvalidPart => {
|
||
Some(StorageError::InvalidPart(Default::default(), Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::DoneForNow => Some(StorageError::DoneForNow),
|
||
StorageErrorCode::DecommissionAlreadyRunning => Some(StorageError::DecommissionAlreadyRunning),
|
||
StorageErrorCode::RebalanceAlreadyRunning => Some(StorageError::RebalanceAlreadyRunning),
|
||
StorageErrorCode::OperationCanceled => Some(StorageError::OperationCanceled),
|
||
StorageErrorCode::ErasureReadQuorum => Some(StorageError::ErasureReadQuorum),
|
||
StorageErrorCode::ErasureWriteQuorum => Some(StorageError::ErasureWriteQuorum),
|
||
StorageErrorCode::NotFirstDisk => Some(StorageError::NotFirstDisk),
|
||
StorageErrorCode::FirstDiskWait => Some(StorageError::FirstDiskWait),
|
||
StorageErrorCode::ConfigNotFound => Some(StorageError::ConfigNotFound),
|
||
StorageErrorCode::TooManyOpenFiles => Some(StorageError::TooManyOpenFiles),
|
||
StorageErrorCode::NoHealRequired => Some(StorageError::NoHealRequired),
|
||
StorageErrorCode::Lock => {
|
||
Some(StorageError::Lock(rustfs_lock::LockError::internal("Generic lock error".to_string())))
|
||
}
|
||
StorageErrorCode::InsufficientReadQuorum => {
|
||
Some(StorageError::InsufficientReadQuorum(Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::InsufficientWriteQuorum => {
|
||
Some(StorageError::InsufficientWriteQuorum(Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::PreconditionFailed => Some(StorageError::PreconditionFailed),
|
||
StorageErrorCode::EntityTooSmall => {
|
||
Some(StorageError::EntityTooSmall(Default::default(), Default::default(), Default::default()))
|
||
}
|
||
StorageErrorCode::InvalidRangeSpec => Some(StorageError::InvalidRangeSpec(Default::default())),
|
||
StorageErrorCode::NotModified => Some(StorageError::NotModified),
|
||
StorageErrorCode::InvalidPartNumber => Some(StorageError::InvalidPartNumber(Default::default())),
|
||
StorageErrorCode::NamespaceLockQuorumUnavailable => Some(StorageError::NamespaceLockQuorumUnavailable {
|
||
mode: "write",
|
||
bucket: Default::default(),
|
||
object: Default::default(),
|
||
required: Default::default(),
|
||
achieved: Default::default(),
|
||
}),
|
||
}
|
||
}
|
||
}
|
||
|
||
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 classify_system_path_failure_reason(err: &Error) -> &'static str {
|
||
match err {
|
||
StorageError::ConfigNotFound => "config_not_found",
|
||
StorageError::ErasureReadQuorum | StorageError::InsufficientReadQuorum(_, _) => "read_quorum",
|
||
StorageError::Io(io_err) => match io_err.kind() {
|
||
std::io::ErrorKind::TimedOut => "timeout",
|
||
_ => "io",
|
||
},
|
||
_ => "other",
|
||
}
|
||
}
|
||
|
||
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_err_decommission_running(err: &Error) -> bool {
|
||
matches!(err, &StorageError::DecommissionAlreadyRunning)
|
||
}
|
||
|
||
pub fn is_err_rebalance_running(err: &Error) -> bool {
|
||
matches!(err, &StorageError::RebalanceAlreadyRunning)
|
||
}
|
||
|
||
pub fn is_err_operation_canceled(err: &Error) -> bool {
|
||
matches!(err, &StorageError::OperationCanceled)
|
||
}
|
||
|
||
pub fn is_err_not_initialized(err: &Error) -> bool {
|
||
err.to_string().contains("errServerNotInitialized") || err.to_string().contains("ServerNotInitialized")
|
||
}
|
||
|
||
pub fn is_err_io(err: &Error) -> bool {
|
||
matches!(err, &StorageError::Io(_))
|
||
}
|
||
|
||
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 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)
|
||
}
|
||
|
||
StorageError::ErasureReadQuorum => {
|
||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
|
||
StorageError::InsufficientReadQuorum(bucket, object)
|
||
}
|
||
StorageError::ErasureWriteQuorum => {
|
||
let bucket = params.first().cloned().unwrap_or_default().to_owned();
|
||
let object = params.get(1).cloned().map(decode_dir_object).unwrap_or_default();
|
||
StorageError::InsufficientWriteQuorum(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.is_empty() {
|
||
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 err_ = std::io::Error::other(err.to_string());
|
||
let r_err = err;
|
||
let err;
|
||
let bucket = bucket.to_string();
|
||
let object = object.to_string();
|
||
let version_id = version_id.to_string();
|
||
|
||
match r_err.code {
|
||
S3ErrorCode::BucketNotEmpty => {
|
||
err = std::io::Error::other(StorageError::BucketNotEmpty("".to_string()).to_string());
|
||
}
|
||
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.is_empty() {
|
||
err = std::io::Error::other(StorageError::ObjectNotFound(bucket, object));
|
||
} else {
|
||
err = std::io::Error::other(StorageError::BucketNotFound(bucket));
|
||
}
|
||
}
|
||
S3ErrorCode::NoSuchVersion => {
|
||
if !object.is_empty() {
|
||
err = std::io::Error::other(StorageError::ObjectNotFound(bucket, object)); //, version_id);
|
||
} else {
|
||
err = std::io::Error::other(StorageError::BucketNotFound(bucket));
|
||
}
|
||
}
|
||
S3ErrorCode::AccessDenied => {
|
||
err = std::io::Error::other(StorageError::PrefixAccessDenied(bucket, object));
|
||
}
|
||
S3ErrorCode::NoSuchUpload => {
|
||
err = std::io::Error::other(StorageError::InvalidUploadID(bucket, object, version_id));
|
||
}
|
||
_ => {
|
||
err = 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.is_empty() {
|
||
bucket = params[0].to_string();
|
||
}
|
||
if params.len() >= 2 {
|
||
object = decode_dir_object(params[1]);
|
||
}
|
||
match storage_err {
|
||
StorageError::MethodNotAllowed => S3Error::with_message(
|
||
S3ErrorCode::MethodNotAllowed,
|
||
ObjectApiError::MethodNotAllowed(GenericError {
|
||
bucket,
|
||
object,
|
||
..Default::default()
|
||
})
|
||
.to_string(),
|
||
),
|
||
_ => 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);
|
||
assert_eq!(StorageError::RebalanceAlreadyRunning.to_u32(), 0x40);
|
||
assert_eq!(StorageError::OperationCanceled.to_u32(), 0x41);
|
||
assert_eq!(
|
||
StorageError::NamespaceLockQuorumUnavailable {
|
||
mode: "write",
|
||
bucket: "bucket".into(),
|
||
object: "object".into(),
|
||
required: 3,
|
||
achieved: 2,
|
||
}
|
||
.to_u32(),
|
||
0x42
|
||
);
|
||
}
|
||
|
||
#[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)));
|
||
assert!(matches!(StorageError::from_u32(0x40), Some(StorageError::RebalanceAlreadyRunning)));
|
||
assert!(matches!(StorageError::from_u32(0x41), Some(StorageError::OperationCanceled)));
|
||
assert!(matches!(
|
||
StorageError::from_u32(0x42),
|
||
Some(StorageError::NamespaceLockQuorumUnavailable { .. })
|
||
));
|
||
|
||
// Test invalid code returns None
|
||
assert!(StorageError::from_u32(0xFF).is_none());
|
||
}
|
||
|
||
#[test]
|
||
fn test_storage_error_code_contract_matches_storage_api() {
|
||
assert_eq!(StorageError::DiskFull.to_u32(), StorageErrorCode::DiskFull.as_u32());
|
||
assert_eq!(
|
||
StorageError::ObjectNotFound("bucket".to_string(), "object".to_string()).to_u32(),
|
||
StorageErrorCode::ObjectNotFound.as_u32()
|
||
);
|
||
assert!(matches!(
|
||
StorageError::from_u32(StorageErrorCode::ErasureReadQuorum.as_u32()),
|
||
Some(StorageError::ErasureReadQuorum)
|
||
));
|
||
assert!(matches!(
|
||
StorageError::from_u32(StorageErrorCode::NamespaceLockQuorumUnavailable.as_u32()),
|
||
Some(StorageError::NamespaceLockQuorumUnavailable { .. })
|
||
));
|
||
assert!(StorageError::from_u32(0x2B).is_none());
|
||
assert!(StorageError::from_u32(0x2C).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_error_running_state_helpers() {
|
||
assert!(is_err_decommission_running(&StorageError::DecommissionAlreadyRunning));
|
||
assert!(!is_err_decommission_running(&StorageError::RebalanceAlreadyRunning));
|
||
|
||
assert!(is_err_rebalance_running(&StorageError::RebalanceAlreadyRunning));
|
||
assert!(!is_err_rebalance_running(&StorageError::DecommissionAlreadyRunning));
|
||
assert!(is_err_operation_canceled(&StorageError::OperationCanceled));
|
||
assert!(!is_err_operation_canceled(&StorageError::RebalanceAlreadyRunning));
|
||
assert!(is_err_not_initialized(&StorageError::other("errServerNotInitialized")));
|
||
assert!(is_err_not_initialized(&StorageError::other("ServerNotInitialized")));
|
||
assert!(!is_err_not_initialized(&StorageError::DecommissionAlreadyRunning));
|
||
}
|
||
|
||
#[test]
|
||
fn test_classify_system_path_failure_reason() {
|
||
assert_eq!(classify_system_path_failure_reason(&StorageError::ConfigNotFound), "config_not_found");
|
||
assert_eq!(classify_system_path_failure_reason(&StorageError::ErasureReadQuorum), "read_quorum");
|
||
assert_eq!(
|
||
classify_system_path_failure_reason(&StorageError::InsufficientReadQuorum(
|
||
"bucket".to_string(),
|
||
"object".to_string()
|
||
)),
|
||
"read_quorum"
|
||
);
|
||
assert_eq!(
|
||
classify_system_path_failure_reason(&StorageError::Io(IoError::new(ErrorKind::TimedOut, "probe"))),
|
||
"timeout"
|
||
);
|
||
assert_eq!(
|
||
classify_system_path_failure_reason(&StorageError::Io(IoError::new(ErrorKind::PermissionDenied, "probe"))),
|
||
"io"
|
||
);
|
||
assert_eq!(classify_system_path_failure_reason(&StorageError::DiskFull), "other");
|
||
}
|
||
|
||
#[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,
|
||
StorageError::RebalanceAlreadyRunning,
|
||
StorageError::OperationCanceled,
|
||
];
|
||
|
||
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: {code:#x}");
|
||
}
|
||
}
|
||
}
|
||
|
||
#[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);
|
||
|
||
// 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);
|
||
}
|
||
}
|
||
}
|