1use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct StorageConfig {
9 pub max_file_size: Option<u64>,
11
12 pub allowed_types: Option<Vec<String>>,
14
15 pub default_cache_control: Option<String>,
17
18 pub validate_files: bool,
20
21 #[cfg(feature = "access-control")]
23 pub enable_access_control: bool,
24
25 pub cleanup_interval: u64,
27
28 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), allowed_types: None, 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, temp_file_max_age: 86400, }
44 }
45}
46
47impl StorageConfig {
48 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn with_max_file_size(mut self, size: u64) -> Self {
55 self.max_file_size = Some(size);
56 self
57 }
58
59 pub fn unlimited_file_size(mut self) -> Self {
61 self.max_file_size = None;
62 self
63 }
64
65 pub fn with_allowed_types(mut self, types: Vec<String>) -> Self {
67 self.allowed_types = Some(types);
68 self
69 }
70
71 pub fn allow_all_types(mut self) -> Self {
73 self.allowed_types = None;
74 self
75 }
76
77 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 pub fn with_cache_control(mut self, cache_control: String) -> Self {
88 self.default_cache_control = Some(cache_control);
89 self
90 }
91
92 pub fn no_cache_control(mut self) -> Self {
94 self.default_cache_control = None;
95 self
96 }
97
98 pub fn with_validation(mut self) -> Self {
100 self.validate_files = true;
101 self
102 }
103
104 pub fn without_validation(mut self) -> Self {
106 self.validate_files = false;
107 self
108 }
109
110 pub fn with_cleanup_interval(mut self, interval: u64) -> Self {
112 self.cleanup_interval = interval;
113 self
114 }
115
116 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#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct LocalStorageConfig {
126 pub root_path: PathBuf,
128
129 pub create_directories: bool,
131
132 #[cfg(unix)]
134 pub file_permissions: Option<u32>,
135
136 #[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 pub fn new() -> Self {
157 Self::default()
158 }
159
160 pub fn with_root_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
162 self.root_path = path.into();
163 self
164 }
165
166 pub fn create_directories(mut self) -> Self {
168 self.create_directories = true;
169 self
170 }
171
172 pub fn no_create_directories(mut self) -> Self {
174 self.create_directories = false;
175 self
176 }
177
178 #[cfg(unix)]
180 pub fn with_file_permissions(mut self, permissions: u32) -> Self {
181 self.file_permissions = Some(permissions);
182 self
183 }
184
185 #[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#[cfg(feature = "aws-s3")]
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct S3Config {
197 pub bucket: String,
199
200 pub region: String,
202
203 pub endpoint: Option<String>,
205
206 pub access_key_id: Option<String>,
208
209 pub secret_access_key: Option<String>,
211
212 pub session_token: Option<String>,
214
215 pub prefix: Option<String>,
217
218 pub default_acl: Option<String>,
220
221 pub server_side_encryption: bool,
223
224 pub kms_key_id: Option<String>,
226
227 pub path_style: bool,
229
230 pub cdn_domain: Option<String>,
232}
233
234#[cfg(feature = "aws-s3")]
235impl S3Config {
236 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 pub fn with_region(mut self, region: String) -> Self {
256 self.region = region;
257 self
258 }
259
260 pub fn with_endpoint(mut self, endpoint: String) -> Self {
262 self.endpoint = Some(endpoint);
263 self
264 }
265
266 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 pub fn with_session_token(mut self, session_token: String) -> Self {
275 self.session_token = Some(session_token);
276 self
277 }
278
279 pub fn with_prefix(mut self, prefix: String) -> Self {
281 self.prefix = Some(prefix);
282 self
283 }
284
285 pub fn with_acl(mut self, acl: String) -> Self {
287 self.default_acl = Some(acl);
288 self
289 }
290
291 pub fn with_encryption(mut self) -> Self {
293 self.server_side_encryption = true;
294 self
295 }
296
297 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 pub fn path_style(mut self) -> Self {
306 self.path_style = true;
307 self
308 }
309
310 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}