use crate::fs_utils::{FileReadError, FileWriteError};
use std::io;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OperationFailureClass {
UserVisible,
Persistence,
RuntimeConfig,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OperationRetryAdvice {
Retry,
RetryAfterChange,
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OperationFailureSource {
PathPolicy,
Missing,
Metadata {
kind: io::ErrorKind,
},
UnsupportedFileType,
TooLarge,
Open {
kind: io::ErrorKind,
},
Read {
kind: io::ErrorKind,
},
MissingParent,
CreateTemp {
kind: io::ErrorKind,
},
WriteTemp {
kind: io::ErrorKind,
},
SyncTemp {
kind: io::ErrorKind,
},
SetTempPermissions {
kind: io::ErrorKind,
},
Rename {
kind: io::ErrorKind,
},
}
impl OperationFailureSource {
#[must_use]
pub fn from_file_read_error(error: &FileReadError) -> Self {
match error {
FileReadError::Policy(_error) => Self::PathPolicy,
FileReadError::Missing { .. } => Self::Missing,
FileReadError::Metadata { source, .. } => Self::Metadata {
kind: source.kind(),
},
FileReadError::UnsupportedFileType { .. } => Self::UnsupportedFileType,
FileReadError::TooLarge { .. } => Self::TooLarge,
FileReadError::Open { source, .. } => Self::Open {
kind: source.kind(),
},
FileReadError::Read { source, .. } => Self::Read {
kind: source.kind(),
},
}
}
#[must_use]
pub fn from_file_write_error(error: &FileWriteError) -> Self {
match error {
FileWriteError::Policy(_error) => Self::PathPolicy,
FileWriteError::Metadata { source, .. } => Self::Metadata {
kind: source.kind(),
},
FileWriteError::UnsupportedFileType { .. } => Self::UnsupportedFileType,
FileWriteError::MissingParent { .. } => Self::MissingParent,
FileWriteError::CreateTemp { source, .. } => Self::CreateTemp {
kind: source.kind(),
},
FileWriteError::WriteTemp { source, .. } => Self::WriteTemp {
kind: source.kind(),
},
FileWriteError::SyncTemp { source, .. } => Self::SyncTemp {
kind: source.kind(),
},
FileWriteError::SetTempPermissions { source, .. } => Self::SetTempPermissions {
kind: source.kind(),
},
FileWriteError::Rename { source, .. } => Self::Rename {
kind: source.kind(),
},
}
}
#[must_use]
pub const fn retry_advice(self) -> OperationRetryAdvice {
match self {
Self::PathPolicy
| Self::Missing
| Self::UnsupportedFileType
| Self::TooLarge
| Self::MissingParent => OperationRetryAdvice::RetryAfterChange,
Self::Metadata { kind }
| Self::Open { kind }
| Self::Read { kind }
| Self::CreateTemp { kind }
| Self::WriteTemp { kind }
| Self::SyncTemp { kind }
| Self::SetTempPermissions { kind }
| Self::Rename { kind } => retry_advice_for_io_kind(kind),
}
}
}
const fn retry_advice_for_io_kind(kind: io::ErrorKind) -> OperationRetryAdvice {
match kind {
io::ErrorKind::Interrupted | io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => {
OperationRetryAdvice::Retry
}
io::ErrorKind::NotFound
| io::ErrorKind::PermissionDenied
| io::ErrorKind::AlreadyExists
| io::ErrorKind::InvalidInput
| io::ErrorKind::InvalidData
| io::ErrorKind::WriteZero
| io::ErrorKind::Unsupported => OperationRetryAdvice::RetryAfterChange,
_ => OperationRetryAdvice::Unknown,
}
}
#[cfg(test)]
mod tests {
use super::{OperationFailureSource, OperationRetryAdvice};
use crate::fs_utils::{FileReadError, FileWriteError};
use std::{io, path::PathBuf};
#[test]
fn read_failures_map_to_redacted_source_shapes() {
let path = PathBuf::from("secret/path.txt");
let missing = FileReadError::Missing { path: path.clone() };
assert_eq!(
OperationFailureSource::from_file_read_error(&missing),
OperationFailureSource::Missing
);
let open = FileReadError::Open {
path: path.clone(),
source: io::Error::from(io::ErrorKind::PermissionDenied),
};
assert_eq!(
OperationFailureSource::from_file_read_error(&open),
OperationFailureSource::Open {
kind: io::ErrorKind::PermissionDenied
}
);
let too_large = FileReadError::TooLarge {
path,
size: 2048,
max_size: 1024,
};
assert_eq!(
OperationFailureSource::from_file_read_error(&too_large),
OperationFailureSource::TooLarge
);
}
#[test]
fn write_failures_map_to_redacted_source_shapes() {
let error = FileWriteError::Rename {
temp_path: PathBuf::from("secret/.tmp"),
target_path: PathBuf::from("secret/path.txt"),
source: io::Error::from(io::ErrorKind::TimedOut),
};
assert_eq!(
OperationFailureSource::from_file_write_error(&error),
OperationFailureSource::Rename {
kind: io::ErrorKind::TimedOut
}
);
}
#[test]
fn retry_advice_is_closed_over_redacted_shapes() {
assert_eq!(
OperationFailureSource::Missing.retry_advice(),
OperationRetryAdvice::RetryAfterChange
);
assert_eq!(
OperationFailureSource::Read {
kind: io::ErrorKind::TimedOut,
}
.retry_advice(),
OperationRetryAdvice::Retry
);
assert_eq!(
OperationFailureSource::UnsupportedFileType.retry_advice(),
OperationRetryAdvice::RetryAfterChange
);
}
}