ferro_storage/
storage.rs

1//! Core storage trait and types.
2
3use crate::Error;
4use async_trait::async_trait;
5use bytes::Bytes;
6use serde::{Deserialize, Serialize};
7use std::time::SystemTime;
8
9/// File metadata.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct FileMetadata {
12    /// File path.
13    pub path: String,
14    /// File size in bytes.
15    pub size: u64,
16    /// Last modified time.
17    pub last_modified: Option<SystemTime>,
18    /// MIME type.
19    pub mime_type: Option<String>,
20}
21
22impl FileMetadata {
23    /// Create new file metadata.
24    pub fn new(path: impl Into<String>, size: u64) -> Self {
25        Self {
26            path: path.into(),
27            size,
28            last_modified: None,
29            mime_type: None,
30        }
31    }
32
33    /// Set last modified time.
34    pub fn with_last_modified(mut self, time: SystemTime) -> Self {
35        self.last_modified = Some(time);
36        self
37    }
38
39    /// Set MIME type.
40    pub fn with_mime_type(mut self, mime: impl Into<String>) -> Self {
41        self.mime_type = Some(mime.into());
42        self
43    }
44}
45
46/// Visibility of stored files.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "lowercase")]
49pub enum Visibility {
50    /// File is publicly accessible.
51    Public,
52    /// File is private.
53    Private,
54}
55
56impl Default for Visibility {
57    fn default() -> Self {
58        Self::Private
59    }
60}
61
62/// Options for storing files.
63#[derive(Debug, Clone, Default)]
64pub struct PutOptions {
65    /// File visibility.
66    pub visibility: Visibility,
67    /// Content type override.
68    pub content_type: Option<String>,
69    /// Custom metadata.
70    pub metadata: Option<std::collections::HashMap<String, String>>,
71}
72
73impl PutOptions {
74    /// Create new put options.
75    pub fn new() -> Self {
76        Self::default()
77    }
78
79    /// Set visibility.
80    pub fn visibility(mut self, visibility: Visibility) -> Self {
81        self.visibility = visibility;
82        self
83    }
84
85    /// Set content type.
86    pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
87        self.content_type = Some(content_type.into());
88        self
89    }
90
91    /// Make file public.
92    pub fn public(mut self) -> Self {
93        self.visibility = Visibility::Public;
94        self
95    }
96
97    /// Make file private.
98    pub fn private(mut self) -> Self {
99        self.visibility = Visibility::Private;
100        self
101    }
102}
103
104/// Storage driver trait.
105#[async_trait]
106pub trait StorageDriver: Send + Sync {
107    /// Check if a file exists.
108    async fn exists(&self, path: &str) -> Result<bool, Error>;
109
110    /// Get file contents as bytes.
111    async fn get(&self, path: &str) -> Result<Bytes, Error>;
112
113    /// Get file contents as string.
114    async fn get_string(&self, path: &str) -> Result<String, Error> {
115        let bytes = self.get(path).await?;
116        String::from_utf8(bytes.to_vec()).map_err(|e| Error::Serialization(e.to_string()))
117    }
118
119    /// Put file contents.
120    async fn put(&self, path: &str, contents: Bytes, options: PutOptions) -> Result<(), Error>;
121
122    /// Put string contents.
123    async fn put_string(
124        &self,
125        path: &str,
126        contents: &str,
127        options: PutOptions,
128    ) -> Result<(), Error> {
129        self.put(path, Bytes::from(contents.to_string()), options)
130            .await
131    }
132
133    /// Delete a file.
134    async fn delete(&self, path: &str) -> Result<(), Error>;
135
136    /// Copy a file.
137    async fn copy(&self, from: &str, to: &str) -> Result<(), Error>;
138
139    /// Move a file.
140    async fn rename(&self, from: &str, to: &str) -> Result<(), Error> {
141        self.copy(from, to).await?;
142        self.delete(from).await
143    }
144
145    /// Get file size.
146    async fn size(&self, path: &str) -> Result<u64, Error>;
147
148    /// Get file metadata.
149    async fn metadata(&self, path: &str) -> Result<FileMetadata, Error>;
150
151    /// Get URL to a file.
152    async fn url(&self, path: &str) -> Result<String, Error>;
153
154    /// Get a temporary URL (for private files).
155    async fn temporary_url(
156        &self,
157        path: &str,
158        expiration: std::time::Duration,
159    ) -> Result<String, Error>;
160
161    /// List files in a directory.
162    async fn files(&self, directory: &str) -> Result<Vec<String>, Error>;
163
164    /// List all files recursively.
165    async fn all_files(&self, directory: &str) -> Result<Vec<String>, Error>;
166
167    /// List directories.
168    async fn directories(&self, directory: &str) -> Result<Vec<String>, Error>;
169
170    /// Create a directory.
171    async fn make_directory(&self, path: &str) -> Result<(), Error>;
172
173    /// Delete a directory.
174    async fn delete_directory(&self, path: &str) -> Result<(), Error>;
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_file_metadata() {
183        let meta = FileMetadata::new("test.txt", 100).with_mime_type("text/plain");
184
185        assert_eq!(meta.path, "test.txt");
186        assert_eq!(meta.size, 100);
187        assert_eq!(meta.mime_type, Some("text/plain".to_string()));
188    }
189
190    #[test]
191    fn test_put_options() {
192        let opts = PutOptions::new().public().content_type("image/png");
193
194        assert_eq!(opts.visibility, Visibility::Public);
195        assert_eq!(opts.content_type, Some("image/png".to_string()));
196    }
197
198    #[test]
199    fn test_visibility_default() {
200        assert_eq!(Visibility::default(), Visibility::Private);
201    }
202}