Skip to main content

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