elif_storage/
config.rs

1//! Storage configuration
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Storage configuration
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct StorageConfig {
9    /// Maximum file size in bytes (None = unlimited)
10    pub max_file_size: Option<u64>,
11    
12    /// Allowed file types (MIME type prefixes, None = all allowed)
13    pub allowed_types: Option<Vec<String>>,
14    
15    /// Default cache control header
16    pub default_cache_control: Option<String>,
17    
18    /// Enable file validation
19    pub validate_files: bool,
20    
21    /// Enable access control
22    #[cfg(feature = "access-control")]
23    pub enable_access_control: bool,
24    
25    /// Temporary file cleanup interval (in seconds)
26    pub cleanup_interval: u64,
27    
28    /// Temporary file max age (in seconds)
29    pub temp_file_max_age: u64,
30}
31
32impl Default for StorageConfig {
33    fn default() -> Self {
34        Self {
35            max_file_size: Some(100 * 1024 * 1024), // 100MB default
36            allowed_types: None, // All types allowed by default
37            default_cache_control: Some("public, max-age=3600".to_string()),
38            validate_files: true,
39            #[cfg(feature = "access-control")]
40            enable_access_control: false,
41            cleanup_interval: 3600, // 1 hour
42            temp_file_max_age: 86400, // 24 hours
43        }
44    }
45}
46
47impl StorageConfig {
48    /// Create a new storage configuration
49    pub fn new() -> Self {
50        Self::default()
51    }
52    
53    /// Set maximum file size
54    pub fn with_max_file_size(mut self, size: u64) -> Self {
55        self.max_file_size = Some(size);
56        self
57    }
58    
59    /// Remove file size limit
60    pub fn unlimited_file_size(mut self) -> Self {
61        self.max_file_size = None;
62        self
63    }
64    
65    /// Set allowed file types
66    pub fn with_allowed_types(mut self, types: Vec<String>) -> Self {
67        self.allowed_types = Some(types);
68        self
69    }
70    
71    /// Allow all file types
72    pub fn allow_all_types(mut self) -> Self {
73        self.allowed_types = None;
74        self
75    }
76    
77    /// Add allowed file type
78    pub fn allow_type(mut self, mime_type: String) -> Self {
79        match &mut self.allowed_types {
80            Some(types) => types.push(mime_type),
81            None => self.allowed_types = Some(vec![mime_type]),
82        }
83        self
84    }
85    
86    /// Set default cache control
87    pub fn with_cache_control(mut self, cache_control: String) -> Self {
88        self.default_cache_control = Some(cache_control);
89        self
90    }
91    
92    /// Disable cache control
93    pub fn no_cache_control(mut self) -> Self {
94        self.default_cache_control = None;
95        self
96    }
97    
98    /// Enable file validation
99    pub fn with_validation(mut self) -> Self {
100        self.validate_files = true;
101        self
102    }
103    
104    /// Disable file validation
105    pub fn without_validation(mut self) -> Self {
106        self.validate_files = false;
107        self
108    }
109    
110    /// Set cleanup interval
111    pub fn with_cleanup_interval(mut self, interval: u64) -> Self {
112        self.cleanup_interval = interval;
113        self
114    }
115    
116    /// Set temporary file max age
117    pub fn with_temp_file_max_age(mut self, max_age: u64) -> Self {
118        self.temp_file_max_age = max_age;
119        self
120    }
121}
122
123/// Local storage configuration
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct LocalStorageConfig {
126    /// Root directory for file storage
127    pub root_path: PathBuf,
128    
129    /// Create directories if they don't exist
130    pub create_directories: bool,
131    
132    /// File permissions (Unix only)
133    #[cfg(unix)]
134    pub file_permissions: Option<u32>,
135    
136    /// Directory permissions (Unix only)
137    #[cfg(unix)]
138    pub directory_permissions: Option<u32>,
139}
140
141impl Default for LocalStorageConfig {
142    fn default() -> Self {
143        Self {
144            root_path: PathBuf::from("./storage"),
145            create_directories: true,
146            #[cfg(unix)]
147            file_permissions: Some(0o644),
148            #[cfg(unix)]
149            directory_permissions: Some(0o755),
150        }
151    }
152}
153
154impl LocalStorageConfig {
155    /// Create new local storage configuration
156    pub fn new() -> Self {
157        Self::default()
158    }
159    
160    /// Set root path
161    pub fn with_root_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
162        self.root_path = path.into();
163        self
164    }
165    
166    /// Enable automatic directory creation
167    pub fn create_directories(mut self) -> Self {
168        self.create_directories = true;
169        self
170    }
171    
172    /// Disable automatic directory creation
173    pub fn no_create_directories(mut self) -> Self {
174        self.create_directories = false;
175        self
176    }
177    
178    /// Set file permissions (Unix only)
179    #[cfg(unix)]
180    pub fn with_file_permissions(mut self, permissions: u32) -> Self {
181        self.file_permissions = Some(permissions);
182        self
183    }
184    
185    /// Set directory permissions (Unix only)
186    #[cfg(unix)]
187    pub fn with_directory_permissions(mut self, permissions: u32) -> Self {
188        self.directory_permissions = Some(permissions);
189        self
190    }
191}
192
193/// AWS S3 configuration
194#[cfg(feature = "aws-s3")]
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct S3Config {
197    /// S3 bucket name
198    pub bucket: String,
199    
200    /// AWS region
201    pub region: String,
202    
203    /// Custom endpoint (for S3-compatible services)
204    pub endpoint: Option<String>,
205    
206    /// Access key ID (if not using IAM roles)
207    pub access_key_id: Option<String>,
208    
209    /// Secret access key (if not using IAM roles)
210    pub secret_access_key: Option<String>,
211    
212    /// Session token (for temporary credentials)
213    pub session_token: Option<String>,
214    
215    /// Path prefix for all files
216    pub prefix: Option<String>,
217    
218    /// Default ACL for uploaded files
219    pub default_acl: Option<String>,
220    
221    /// Enable server-side encryption
222    pub server_side_encryption: bool,
223    
224    /// KMS key ID for encryption
225    pub kms_key_id: Option<String>,
226    
227    /// Use path-style URLs
228    pub path_style: bool,
229    
230    /// CDN domain for public URLs
231    pub cdn_domain: Option<String>,
232}
233
234#[cfg(feature = "aws-s3")]
235impl S3Config {
236    /// Create new S3 configuration
237    pub fn new(bucket: String) -> Self {
238        Self {
239            bucket,
240            region: "us-east-1".to_string(),
241            endpoint: None,
242            access_key_id: None,
243            secret_access_key: None,
244            session_token: None,
245            prefix: None,
246            default_acl: None,
247            server_side_encryption: false,
248            kms_key_id: None,
249            path_style: false,
250            cdn_domain: None,
251        }
252    }
253    
254    /// Set AWS region
255    pub fn with_region(mut self, region: String) -> Self {
256        self.region = region;
257        self
258    }
259    
260    /// Set custom endpoint (for S3-compatible services)
261    pub fn with_endpoint(mut self, endpoint: String) -> Self {
262        self.endpoint = Some(endpoint);
263        self
264    }
265    
266    /// Set AWS credentials
267    pub fn with_credentials(mut self, access_key_id: String, secret_access_key: String) -> Self {
268        self.access_key_id = Some(access_key_id);
269        self.secret_access_key = Some(secret_access_key);
270        self
271    }
272    
273    /// Set session token for temporary credentials
274    pub fn with_session_token(mut self, session_token: String) -> Self {
275        self.session_token = Some(session_token);
276        self
277    }
278    
279    /// Set path prefix
280    pub fn with_prefix(mut self, prefix: String) -> Self {
281        self.prefix = Some(prefix);
282        self
283    }
284    
285    /// Set default ACL
286    pub fn with_acl(mut self, acl: String) -> Self {
287        self.default_acl = Some(acl);
288        self
289    }
290    
291    /// Enable server-side encryption
292    pub fn with_encryption(mut self) -> Self {
293        self.server_side_encryption = true;
294        self
295    }
296    
297    /// Set KMS key for encryption
298    pub fn with_kms_key(mut self, key_id: String) -> Self {
299        self.kms_key_id = Some(key_id);
300        self.server_side_encryption = true;
301        self
302    }
303    
304    /// Use path-style URLs
305    pub fn path_style(mut self) -> Self {
306        self.path_style = true;
307        self
308    }
309    
310    /// Set CDN domain
311    pub fn with_cdn(mut self, domain: String) -> Self {
312        self.cdn_domain = Some(domain);
313        self
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    
321    #[test]
322    fn test_storage_config_defaults() {
323        let config = StorageConfig::default();
324        assert_eq!(config.max_file_size, Some(100 * 1024 * 1024));
325        assert_eq!(config.allowed_types, None);
326        assert!(config.validate_files);
327        assert_eq!(config.cleanup_interval, 3600);
328        assert_eq!(config.temp_file_max_age, 86400);
329    }
330    
331    #[test]
332    fn test_storage_config_builder() {
333        let config = StorageConfig::new()
334            .with_max_file_size(50 * 1024 * 1024)
335            .allow_type("image/".to_string())
336            .allow_type("text/plain".to_string())
337            .with_cache_control("no-cache".to_string())
338            .without_validation();
339            
340        assert_eq!(config.max_file_size, Some(50 * 1024 * 1024));
341        assert_eq!(config.allowed_types, Some(vec!["image/".to_string(), "text/plain".to_string()]));
342        assert_eq!(config.default_cache_control, Some("no-cache".to_string()));
343        assert!(!config.validate_files);
344    }
345    
346    #[test]
347    fn test_local_config_defaults() {
348        let config = LocalStorageConfig::default();
349        assert_eq!(config.root_path, PathBuf::from("./storage"));
350        assert!(config.create_directories);
351        
352        #[cfg(unix)]
353        {
354            assert_eq!(config.file_permissions, Some(0o644));
355            assert_eq!(config.directory_permissions, Some(0o755));
356        }
357    }
358    
359    #[test]
360    fn test_local_config_builder() {
361        let config = LocalStorageConfig::new()
362            .with_root_path("/tmp/storage")
363            .no_create_directories();
364            
365        assert_eq!(config.root_path, PathBuf::from("/tmp/storage"));
366        assert!(!config.create_directories);
367    }
368    
369    #[cfg(feature = "aws-s3")]
370    #[test]
371    fn test_s3_config() {
372        let config = S3Config::new("my-bucket".to_string())
373            .with_region("us-west-2".to_string())
374            .with_prefix("uploads/".to_string())
375            .with_encryption()
376            .with_cdn("cdn.example.com".to_string());
377            
378        assert_eq!(config.bucket, "my-bucket");
379        assert_eq!(config.region, "us-west-2");
380        assert_eq!(config.prefix, Some("uploads/".to_string()));
381        assert!(config.server_side_encryption);
382        assert_eq!(config.cdn_domain, Some("cdn.example.com".to_string()));
383    }
384}