entrenar/storage/cloud/
memory.rs1use crate::storage::cloud::error::{CloudError, Result};
4use crate::storage::cloud::metadata::ArtifactMetadata;
5use crate::storage::cloud::traits::{compute_hash, ArtifactBackend};
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8
9#[derive(Debug, Default)]
11pub struct InMemoryBackend {
12 data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
13 metadata: Arc<RwLock<HashMap<String, ArtifactMetadata>>>,
14}
15
16impl InMemoryBackend {
17 pub fn new() -> Self {
19 Self::default()
20 }
21}
22
23impl ArtifactBackend for InMemoryBackend {
24 fn put(&self, name: &str, data: &[u8]) -> Result<String> {
25 let hash = compute_hash(data);
26
27 self.data
28 .write()
29 .map_err(|e| CloudError::Backend(e.to_string()))?
30 .insert(hash.clone(), data.to_vec());
31
32 let metadata = ArtifactMetadata::new(name, &hash, data.len() as u64);
33 self.metadata
34 .write()
35 .map_err(|e| CloudError::Backend(e.to_string()))?
36 .insert(hash.clone(), metadata);
37
38 Ok(hash)
39 }
40
41 fn get(&self, hash: &str) -> Result<Vec<u8>> {
42 self.data
43 .read()
44 .map_err(|e| CloudError::Backend(e.to_string()))?
45 .get(hash)
46 .cloned()
47 .ok_or_else(|| CloudError::NotFound(hash.to_string()))
48 }
49
50 fn exists(&self, hash: &str) -> Result<bool> {
51 Ok(self.data.read().map_err(|e| CloudError::Backend(e.to_string()))?.contains_key(hash))
52 }
53
54 fn delete(&self, hash: &str) -> Result<()> {
55 let removed =
56 self.data.write().map_err(|e| CloudError::Backend(e.to_string()))?.remove(hash);
57 if removed.is_none() {
58 return Err(CloudError::NotFound(hash.to_string()));
59 }
60 self.metadata.write().map_err(|e| CloudError::Backend(e.to_string()))?.remove(hash);
61 Ok(())
62 }
63
64 fn get_metadata(&self, hash: &str) -> Result<ArtifactMetadata> {
65 self.metadata
66 .read()
67 .map_err(|e| CloudError::Backend(e.to_string()))?
68 .get(hash)
69 .cloned()
70 .ok_or_else(|| CloudError::NotFound(hash.to_string()))
71 }
72
73 fn list(&self) -> Result<Vec<ArtifactMetadata>> {
74 Ok(self
75 .metadata
76 .read()
77 .map_err(|e| CloudError::Backend(e.to_string()))?
78 .values()
79 .cloned()
80 .collect())
81 }
82
83 fn backend_type(&self) -> &'static str {
84 "memory"
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_in_memory_backend_put_get() {
94 let backend = InMemoryBackend::new();
95 let data = b"test data";
96 let hash = backend.put("test.bin", data).expect("operation should succeed");
97
98 let retrieved = backend.get(&hash).expect("key should exist");
99 assert_eq!(retrieved, data);
100 }
101
102 #[test]
103 fn test_in_memory_backend_exists() {
104 let backend = InMemoryBackend::new();
105 let hash = backend.put("test.bin", b"data").expect("operation should succeed");
106
107 assert!(backend.exists(&hash).expect("operation should succeed"));
108 assert!(!backend.exists("nonexistent").expect("operation should succeed"));
109 }
110
111 #[test]
112 fn test_in_memory_backend_delete() {
113 let backend = InMemoryBackend::new();
114 let hash = backend.put("test.bin", b"data").expect("operation should succeed");
115
116 backend.delete(&hash).expect("operation should succeed");
117 assert!(!backend.exists(&hash).expect("operation should succeed"));
118 }
119
120 #[test]
121 fn test_in_memory_backend_delete_not_found() {
122 let backend = InMemoryBackend::new();
123 let result = backend.delete("nonexistent");
124 assert!(result.is_err());
125 }
126
127 #[test]
128 fn test_in_memory_backend_get_metadata() {
129 let backend = InMemoryBackend::new();
130 let data = b"test data 123";
131 let hash = backend.put("model.bin", data).expect("operation should succeed");
132
133 let meta = backend.get_metadata(&hash).expect("operation should succeed");
134 assert_eq!(meta.name, "model.bin");
135 assert_eq!(meta.size, data.len() as u64);
136 }
137
138 #[test]
139 fn test_in_memory_backend_list() {
140 let backend = InMemoryBackend::new();
141 backend.put("file1.bin", b"data1").expect("operation should succeed");
142 backend.put("file2.bin", b"data2").expect("operation should succeed");
143
144 let list = backend.list().expect("operation should succeed");
145 assert_eq!(list.len(), 2);
146 }
147
148 #[test]
149 fn test_in_memory_backend_type() {
150 let backend = InMemoryBackend::new();
151 assert_eq!(backend.backend_type(), "memory");
152 }
153
154 #[test]
155 fn test_in_memory_backend_get_not_found() {
156 let backend = InMemoryBackend::new();
157 let result = backend.get("nonexistent");
158 assert!(result.is_err());
159 }
160
161 #[test]
162 fn test_in_memory_backend_get_metadata_not_found() {
163 let backend = InMemoryBackend::new();
164 let result = backend.get_metadata("nonexistent");
165 assert!(result.is_err());
166 }
167
168 #[test]
169 fn test_in_memory_backend_default() {
170 let backend = InMemoryBackend::default();
171 assert_eq!(backend.backend_type(), "memory");
172 }
173}
174
175#[cfg(test)]
176mod property_tests {
177 use super::*;
178 use proptest::prelude::*;
179
180 proptest! {
181 #![proptest_config(ProptestConfig::with_cases(200))]
182
183 #[test]
184 fn prop_memory_backend_roundtrip(
185 name in "[a-zA-Z0-9_]{1,50}",
186 data in prop::collection::vec(any::<u8>(), 1..1000)
187 ) {
188 let backend = InMemoryBackend::new();
189 let hash = backend.put(&name, &data).expect("operation should succeed");
190 let retrieved = backend.get(&hash).expect("key should exist");
191 prop_assert_eq!(retrieved, data);
192 }
193 }
194}