use crate::Error;
use async_trait::async_trait;
use bytes::Bytes;
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileMetadata {
pub path: String,
pub size: u64,
pub last_modified: Option<SystemTime>,
pub mime_type: Option<String>,
}
impl FileMetadata {
pub fn new(path: impl Into<String>, size: u64) -> Self {
Self {
path: path.into(),
size,
last_modified: None,
mime_type: None,
}
}
pub fn with_last_modified(mut self, time: SystemTime) -> Self {
self.last_modified = Some(time);
self
}
pub fn with_mime_type(mut self, mime: impl Into<String>) -> Self {
self.mime_type = Some(mime.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Visibility {
Public,
#[default]
Private,
}
#[derive(Debug, Clone, Default)]
pub struct PutOptions {
pub visibility: Visibility,
pub content_type: Option<String>,
pub metadata: Option<std::collections::HashMap<String, String>>,
}
impl PutOptions {
pub fn new() -> Self {
Self::default()
}
pub fn visibility(mut self, visibility: Visibility) -> Self {
self.visibility = visibility;
self
}
pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = Some(content_type.into());
self
}
pub fn public(mut self) -> Self {
self.visibility = Visibility::Public;
self
}
pub fn private(mut self) -> Self {
self.visibility = Visibility::Private;
self
}
}
#[async_trait]
pub trait StorageDriver: Send + Sync {
async fn exists(&self, path: &str) -> Result<bool, Error>;
async fn get(&self, path: &str) -> Result<Bytes, Error>;
async fn get_string(&self, path: &str) -> Result<String, Error> {
let bytes = self.get(path).await?;
String::from_utf8(bytes.to_vec()).map_err(|e| Error::Serialization(e.to_string()))
}
async fn put(&self, path: &str, contents: Bytes, options: PutOptions) -> Result<(), Error>;
async fn put_string(
&self,
path: &str,
contents: &str,
options: PutOptions,
) -> Result<(), Error> {
self.put(path, Bytes::from(contents.to_string()), options)
.await
}
async fn delete(&self, path: &str) -> Result<(), Error>;
async fn copy(&self, from: &str, to: &str) -> Result<(), Error>;
async fn rename(&self, from: &str, to: &str) -> Result<(), Error> {
self.copy(from, to).await?;
self.delete(from).await
}
async fn size(&self, path: &str) -> Result<u64, Error>;
async fn metadata(&self, path: &str) -> Result<FileMetadata, Error>;
async fn url(&self, path: &str) -> Result<String, Error>;
async fn temporary_url(
&self,
path: &str,
expiration: std::time::Duration,
) -> Result<String, Error>;
async fn files(&self, directory: &str) -> Result<Vec<String>, Error>;
async fn all_files(&self, directory: &str) -> Result<Vec<String>, Error>;
async fn directories(&self, directory: &str) -> Result<Vec<String>, Error>;
async fn make_directory(&self, path: &str) -> Result<(), Error>;
async fn delete_directory(&self, path: &str) -> Result<(), Error>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_file_metadata() {
let meta = FileMetadata::new("test.txt", 100).with_mime_type("text/plain");
assert_eq!(meta.path, "test.txt");
assert_eq!(meta.size, 100);
assert_eq!(meta.mime_type, Some("text/plain".to_string()));
}
#[test]
fn test_put_options() {
let opts = PutOptions::new().public().content_type("image/png");
assert_eq!(opts.visibility, Visibility::Public);
assert_eq!(opts.content_type, Some("image/png".to_string()));
}
#[test]
fn test_visibility_default() {
assert_eq!(Visibility::default(), Visibility::Private);
}
}