use async_trait::async_trait;
use auths_core::error::AuthsErrorInfo;
use std::fmt;
#[derive(Debug)]
#[non_exhaustive]
pub enum StorageError {
NotFound(String),
CasConflict {
expected: Option<Vec<u8>>,
found: Option<Vec<u8>>,
},
Io(Box<dyn std::error::Error + Send + Sync>),
}
impl fmt::Display for StorageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StorageError::NotFound(path) => write!(f, "not found: {}", path),
StorageError::CasConflict { expected, found } => {
write!(
f,
"CAS conflict: expected {:?}, found {:?}",
expected.as_ref().map(|v| v.len()),
found.as_ref().map(|v| v.len())
)
}
StorageError::Io(e) => write!(f, "storage I/O error: {}", e),
}
}
}
impl std::error::Error for StorageError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
StorageError::Io(e) => Some(e.as_ref()),
_ => None,
}
}
}
impl AuthsErrorInfo for StorageError {
fn error_code(&self) -> &'static str {
match self {
Self::NotFound(_) => "AUTHS-E4409",
Self::CasConflict { .. } => "AUTHS-E4410",
Self::Io(_) => "AUTHS-E4411",
}
}
fn suggestion(&self) -> Option<&'static str> {
match self {
Self::NotFound(_) => Some("Verify the storage path exists and is initialized"),
Self::CasConflict { .. } => {
Some("A concurrent modification was detected; retry the operation")
}
Self::Io(_) => {
Some("Check file permissions, disk space, and storage backend connectivity")
}
}
}
}
impl StorageError {
pub fn not_found(path: impl Into<String>) -> Self {
StorageError::NotFound(path.into())
}
pub fn cas_conflict(expected: Option<Vec<u8>>, found: Option<Vec<u8>>) -> Self {
StorageError::CasConflict { expected, found }
}
pub fn io<E: std::error::Error + Send + Sync + 'static>(e: E) -> Self {
StorageError::Io(Box::new(e))
}
}
#[async_trait]
pub trait StorageDriver: Send + Sync {
async fn get_blob(&self, path: &str) -> Result<Vec<u8>, StorageError>;
async fn put_blob(&self, path: &str, data: &[u8]) -> Result<(), StorageError>;
async fn cas_update(
&self,
ref_key: &str,
expected: Option<&[u8]>,
new: &[u8],
) -> Result<(), StorageError>;
async fn list_prefix(&self, prefix: &str) -> Result<Vec<String>, StorageError>;
async fn exists(&self, path: &str) -> Result<bool, StorageError>;
async fn delete(&self, path: &str) -> Result<(), StorageError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn storage_error_display() {
let err = StorageError::not_found("foo/bar");
assert_eq!(err.to_string(), "not found: foo/bar");
let err = StorageError::cas_conflict(Some(vec![1, 2, 3]), None);
assert!(err.to_string().contains("CAS conflict"));
}
#[test]
fn storage_error_io() {
let io_err = std::io::Error::other("test");
let err = StorageError::io(io_err);
assert!(err.to_string().contains("I/O error"));
}
}