rustrails_storage/service/
mod.rs1use std::{sync::Arc, time::Duration};
4
5use async_trait::async_trait;
6use bytes::Bytes;
7use thiserror::Error;
8use url::Url;
9
10pub mod disk;
11pub mod gcs;
12pub mod memory;
13pub mod mirror;
14pub mod s3;
15
16pub use disk::DiskService;
17pub use gcs::GcsService;
18pub use memory::MemoryService;
19pub use mirror::MirrorService;
20pub use s3::S3Service;
21
22#[derive(Debug, Error)]
24pub enum StorageError {
25 #[error("storage key not found: {0}")]
27 NotFound(String),
28 #[error("storage key already exists: {0}")]
30 DuplicateKey(String),
31 #[error("i/o failure for {path}: {source}")]
33 Io {
34 path: String,
36 #[source]
38 source: std::io::Error,
39 },
40 #[error("object store failure for {path}: {message}")]
42 ObjectStore {
43 path: String,
45 message: String,
47 },
48 #[error("invalid storage url: {0}")]
50 InvalidUrl(String),
51}
52
53#[async_trait]
55pub trait StorageService: Send + Sync {
56 fn name(&self) -> &str;
58
59 async fn upload(&self, key: &str, data: Bytes) -> Result<(), StorageError>;
65
66 async fn download(&self, key: &str) -> Result<Bytes, StorageError>;
72
73 async fn delete(&self, key: &str) -> Result<(), StorageError>;
79
80 async fn exists(&self, key: &str) -> Result<bool, StorageError>;
86
87 async fn url(&self, key: &str, expires_in: Duration) -> Result<Url, StorageError>;
93}
94
95pub type DynStorageService = Arc<dyn StorageService>;
97
98pub(crate) fn checked_key(key: &str) -> Result<&str, StorageError> {
99 let trimmed = key.trim();
100 if trimmed.is_empty() {
101 return Err(StorageError::InvalidUrl(
102 "storage key must not be empty".to_owned(),
103 ));
104 }
105 Ok(trimmed)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_checked_key_rejects_empty_values() {
114 assert!(checked_key(" ").is_err());
115 }
116
117 #[test]
118 fn test_checked_key_returns_trimmed_input() {
119 assert_eq!(checked_key("abc").expect("key should be valid"), "abc");
120 }
121}