Skip to main content

entrenar/storage/cloud/
config.rs

1//! Unified backend configuration
2
3use crate::storage::cloud::azure::AzureConfig;
4use crate::storage::cloud::error::Result;
5use crate::storage::cloud::gcs::GCSConfig;
6use crate::storage::cloud::local::LocalBackend;
7use crate::storage::cloud::memory::InMemoryBackend;
8use crate::storage::cloud::s3::{MockS3Backend, S3Config};
9use crate::storage::cloud::traits::ArtifactBackend;
10use serde::{Deserialize, Serialize};
11use std::path::PathBuf;
12
13/// Unified artifact backend configuration
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub enum BackendConfig {
16    /// Local filesystem
17    Local { path: PathBuf },
18    /// In-memory (for testing)
19    Memory,
20    /// Amazon S3
21    S3(S3Config),
22    /// Azure Blob Storage
23    Azure(AzureConfig),
24    /// Google Cloud Storage
25    GCS(GCSConfig),
26}
27
28impl BackendConfig {
29    /// Create a local backend configuration
30    pub fn local(path: PathBuf) -> Self {
31        Self::Local { path }
32    }
33
34    /// Create an in-memory backend configuration
35    pub fn memory() -> Self {
36        Self::Memory
37    }
38
39    /// Create an S3 backend configuration
40    pub fn s3(bucket: &str, prefix: &str) -> Self {
41        Self::S3(S3Config::new(bucket, prefix))
42    }
43
44    /// Create a backend from this configuration
45    pub fn build(&self) -> Result<Box<dyn ArtifactBackend>> {
46        match self {
47            Self::Local { path } => Ok(Box::new(LocalBackend::new_and_init(path.clone())?)),
48            Self::Memory => Ok(Box::new(InMemoryBackend::new())),
49            Self::S3(config) => Ok(Box::new(MockS3Backend::new(config.clone()))),
50            Self::Azure(_config) => {
51                // For now, return a mock (real implementation would use azure SDK)
52                Ok(Box::new(InMemoryBackend::new()))
53            }
54            Self::GCS(_config) => {
55                // For now, return a mock (real implementation would use GCS SDK)
56                Ok(Box::new(InMemoryBackend::new()))
57            }
58        }
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use tempfile::TempDir;
66
67    #[test]
68    fn test_backend_config_local() {
69        let config = BackendConfig::local(PathBuf::from("/tmp/artifacts"));
70        match config {
71            BackendConfig::Local { path } => {
72                assert_eq!(path, PathBuf::from("/tmp/artifacts"));
73            }
74            _ => panic!("Expected Local"),
75        }
76    }
77
78    #[test]
79    fn test_backend_config_memory() {
80        let config = BackendConfig::memory();
81        assert!(matches!(config, BackendConfig::Memory));
82    }
83
84    #[test]
85    fn test_backend_config_s3() {
86        let config = BackendConfig::s3("bucket", "prefix");
87        match config {
88            BackendConfig::S3(c) => {
89                assert_eq!(c.bucket, "bucket");
90                assert_eq!(c.prefix, "prefix");
91            }
92            _ => panic!("Expected S3"),
93        }
94    }
95
96    #[test]
97    fn test_backend_config_build_memory() {
98        let config = BackendConfig::memory();
99        let backend = config.build().expect("builder should produce valid result");
100        assert_eq!(backend.backend_type(), "memory");
101    }
102
103    #[test]
104    fn test_backend_config_build_s3() {
105        let config = BackendConfig::s3("bucket", "prefix");
106        let backend = config.build().expect("builder should produce valid result");
107        assert_eq!(backend.backend_type(), "s3");
108    }
109
110    #[test]
111    fn test_backend_config_build_local() {
112        let tmp = TempDir::new().expect("temp file creation should succeed");
113        let config = BackendConfig::local(tmp.path().to_path_buf());
114        let backend = config.build().expect("builder should produce valid result");
115        assert_eq!(backend.backend_type(), "local");
116    }
117
118    #[test]
119    fn test_backend_config_build_azure() {
120        let config = BackendConfig::Azure(AzureConfig::new("account", "container"));
121        let backend = config.build().expect("builder should produce valid result");
122        // Currently returns InMemoryBackend as placeholder
123        assert_eq!(backend.backend_type(), "memory");
124    }
125
126    #[test]
127    fn test_backend_config_build_gcs() {
128        let config = BackendConfig::GCS(GCSConfig::new("bucket"));
129        let backend = config.build().expect("builder should produce valid result");
130        // Currently returns InMemoryBackend as placeholder
131        assert_eq!(backend.backend_type(), "memory");
132    }
133
134    #[test]
135    fn test_backend_config_serde() {
136        let configs = vec![
137            BackendConfig::local(PathBuf::from("/tmp/test")),
138            BackendConfig::memory(),
139            BackendConfig::s3("bucket", "prefix"),
140            BackendConfig::Azure(AzureConfig::new("account", "container")),
141            BackendConfig::GCS(GCSConfig::new("bucket")),
142        ];
143
144        for config in configs {
145            let json = serde_json::to_string(&config).expect("JSON serialization should succeed");
146            let parsed: BackendConfig =
147                serde_json::from_str(&json).expect("JSON deserialization should succeed");
148            // Just verify it parses without panic
149            let _ = parsed;
150        }
151    }
152}
153
154#[cfg(test)]
155mod property_tests {
156    use proptest::prelude::*;
157
158    proptest! {
159        #![proptest_config(ProptestConfig::with_cases(200))]
160
161        #[test]
162        fn prop_metadata_size_matches(
163            name in "[a-zA-Z0-9_]{1,20}",
164            size in 0u64..1_000_000
165        ) {
166            use crate::storage::cloud::metadata::ArtifactMetadata;
167            let meta = ArtifactMetadata::new(&name, "hash", size);
168            prop_assert_eq!(meta.size, size);
169        }
170    }
171}