upstream_rs/application/operations/
metadata_operation.rs1use crate::services::storage::{
2 metadata_storage::MetadataStorage, package_storage::PackageStorage,
3};
4use anyhow::{Context, Result};
5
6pub struct MetadataManager<'a> {
7 package_storage: &'a mut PackageStorage,
8 metadata_storage: &'a mut MetadataStorage,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct MetadataSetResult {
13 pub key: String,
14 pub value: String,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct MetadataBulkSetResult {
19 pub applied: Vec<MetadataSetResult>,
20 pub failures: Vec<(String, String)>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct MetadataBulkGetResult {
25 pub values: Vec<(String, String)>,
26 pub failures: Vec<(String, String)>,
27}
28
29impl<'a> MetadataManager<'a> {
30 pub fn new(
31 package_storage: &'a mut PackageStorage,
32 metadata_storage: &'a mut MetadataStorage,
33 ) -> Self {
34 Self {
35 package_storage,
36 metadata_storage,
37 }
38 }
39
40 pub fn pin_package(&mut self, name: &str, reason: Option<String>) -> Result<()> {
42 let package = self
43 .package_storage
44 .get_mut_package_by_name(name)
45 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", name))?;
46
47 if package.is_pinned {
48 return Ok(());
49 }
50
51 package.is_pinned = true;
52 self.package_storage.save_packages()?;
53 if let Some(reason) = reason {
54 self.metadata_storage.set_pin_reason(name, reason)?;
55 }
56
57 Ok(())
58 }
59
60 pub fn unpin_package(&mut self, name: &str) -> Result<()> {
62 let package = self
63 .package_storage
64 .get_mut_package_by_name(name)
65 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", name))?;
66
67 if !package.is_pinned {
68 return Ok(());
69 }
70
71 package.is_pinned = false;
72 self.package_storage.save_packages()?;
73 self.metadata_storage.clear_pin_reason(name)?;
74
75 Ok(())
76 }
77
78 pub fn remove_package(&mut self, name: &str) -> Result<()> {
80 if !self.package_storage.remove_package_by_name(name)? {
81 return Err(anyhow::anyhow!("Package '{}' not found", name));
82 }
83 self.metadata_storage.remove_package(name)?;
84
85 Ok(())
86 }
87
88 pub fn rename_package(&mut self, old_name: &str, new_name: &str) -> Result<bool> {
90 let old_name = old_name.trim();
91 let new_name = new_name.trim();
92
93 if old_name.is_empty() || new_name.is_empty() {
94 return Err(anyhow::anyhow!("Package names cannot be empty"));
95 }
96
97 if old_name == new_name {
98 return Ok(false);
99 }
100
101 if self.package_storage.get_package_by_name(new_name).is_some() {
102 return Err(anyhow::anyhow!("Package '{}' already exists", new_name));
103 }
104
105 let package = self
106 .package_storage
107 .get_mut_package_by_name(old_name)
108 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", old_name))?;
109
110 package.name = new_name.to_string();
111 self.package_storage.save_packages()?;
112 self.metadata_storage.rename_package(old_name, new_name)?;
113
114 Ok(true)
115 }
116
117 pub fn set_key(&mut self, name: &str, set_key: &str) -> Result<MetadataSetResult> {
120 let (key_path, value) = Self::parse_set_key(set_key)?;
121
122 let package = self
124 .package_storage
125 .get_package_by_name(name)
126 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", name))?;
127
128 let mut json_value = serde_json::to_value(package)?;
130
131 Self::set_nested_value(&mut json_value, &key_path, &value)?;
133
134 let updated_package: crate::models::upstream::Package =
136 serde_json::from_value(json_value).context("Failed to deserialize updated package")?;
137
138 self.package_storage
140 .add_or_update_package(updated_package)?;
141
142 Ok(MetadataSetResult {
143 key: key_path,
144 value,
145 })
146 }
147
148 pub fn get_key(&self, name: &str, get_key: &str) -> Result<String> {
151 let key_path = get_key.trim();
152 if key_path.is_empty() {
153 return Err(anyhow::anyhow!("Key path cannot be empty"));
154 }
155
156 let package = self
158 .package_storage
159 .get_package_by_name(name)
160 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", name))?;
161
162 let json_value = serde_json::to_value(package)?;
164
165 let value = Self::get_nested_value(&json_value, key_path)?;
167
168 let value_str = Self::format_value(&value);
169
170 Ok(value_str)
171 }
172
173 pub fn set_bulk(&mut self, name: &str, set_keys: &[String]) -> MetadataBulkSetResult {
175 let mut applied = Vec::new();
176 let mut failures = Vec::new();
177
178 for set_key in set_keys {
179 match self.set_key(name, set_key) {
180 Ok(result) => applied.push(result),
181 Err(err) => failures.push((set_key.clone(), err.to_string())),
182 }
183 }
184
185 MetadataBulkSetResult { applied, failures }
186 }
187
188 pub fn get_bulk(&self, name: &str, get_keys: &[String]) -> MetadataBulkGetResult {
190 let mut values = Vec::new();
191 let mut failures = Vec::new();
192
193 for get_key in get_keys {
194 match self.get_key(name, get_key) {
195 Ok(value) => {
196 values.push((get_key.clone(), value));
197 }
198 Err(err) => failures.push((get_key.clone(), err.to_string())),
199 }
200 }
201
202 MetadataBulkGetResult { values, failures }
203 }
204
205 fn parse_set_key(set_key: &str) -> Result<(String, String)> {
207 let parts: Vec<&str> = set_key.splitn(2, '=').collect();
208 if parts.len() != 2 {
209 return Err(anyhow::anyhow!(
210 "Invalid set_key format. Expected 'key=value', got '{}'",
211 set_key
212 ));
213 }
214
215 let key_path = parts[0].trim();
216 let value = parts[1].trim();
217
218 if key_path.is_empty() {
219 return Err(anyhow::anyhow!("Key path cannot be empty"));
220 }
221
222 Ok((key_path.to_string(), value.to_string()))
223 }
224
225 fn get_nested_value(json: &serde_json::Value, path: &str) -> Result<serde_json::Value> {
227 let keys: Vec<&str> = path.split('.').collect();
228 let mut current = json;
229
230 for key in keys {
231 current = current
232 .get(key)
233 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found", key))?;
234 }
235
236 Ok(current.clone())
237 }
238
239 fn set_nested_value(json: &mut serde_json::Value, path: &str, value: &str) -> Result<()> {
241 let keys: Vec<&str> = path.split('.').collect();
242
243 if keys.is_empty() {
244 return Err(anyhow::anyhow!("Empty path"));
245 }
246
247 let mut current = json;
248
249 for key in &keys[..keys.len() - 1] {
251 current = current
252 .get_mut(key)
253 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found", key))?;
254 }
255
256 let final_key = keys[keys.len() - 1];
258 let target = current
259 .get_mut(final_key)
260 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found", final_key))?;
261
262 *target = Self::parse_value_for_type(target, value)?;
264
265 Ok(())
266 }
267
268 fn parse_value_for_type(
270 existing: &serde_json::Value,
271 value_str: &str,
272 ) -> Result<serde_json::Value> {
273 match existing {
274 serde_json::Value::Bool(_) => {
275 let bool_val = value_str
276 .parse::<bool>()
277 .with_context(|| format!("Expected boolean value, got '{}'", value_str))?;
278 Ok(serde_json::Value::Bool(bool_val))
279 }
280 serde_json::Value::Number(_) => {
281 if let Ok(int_val) = value_str.parse::<i64>() {
282 Ok(serde_json::json!(int_val))
283 } else if let Ok(float_val) = value_str.parse::<f64>() {
284 Ok(serde_json::json!(float_val))
285 } else {
286 Err(anyhow::anyhow!(
287 "Expected numeric value, got '{}'",
288 value_str
289 ))
290 }
291 }
292 serde_json::Value::String(_) => Ok(serde_json::Value::String(value_str.to_string())),
293 serde_json::Value::Null => {
294 if value_str == "null" {
295 Ok(serde_json::Value::Null)
296 } else {
297 if let Ok(bool_val) = value_str.parse::<bool>() {
299 Ok(serde_json::Value::Bool(bool_val))
300 } else if let Ok(int_val) = value_str.parse::<i64>() {
301 Ok(serde_json::json!(int_val))
302 } else {
303 Ok(serde_json::Value::String(value_str.to_string()))
304 }
305 }
306 }
307 _ => {
308 serde_json::from_str(value_str).with_context(|| {
310 format!(
311 "Cannot set complex type from string. Expected JSON, got '{}'",
312 value_str
313 )
314 })
315 }
316 }
317 }
318
319 fn format_value(value: &serde_json::Value) -> String {
321 match value {
322 serde_json::Value::String(s) => s.clone(),
323 serde_json::Value::Null => "null".to_string(),
324 serde_json::Value::Bool(b) => b.to_string(),
325 serde_json::Value::Number(n) => n.to_string(),
326 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
327 serde_json::to_string_pretty(value).unwrap_or_else(|_| "{}".to_string())
328 }
329 }
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::MetadataManager;
336 use crate::models::common::enums::{Channel, Filetype, Provider};
337 use crate::models::upstream::Package;
338 use crate::services::storage::{
339 metadata_storage::MetadataStorage, package_storage::PackageStorage,
340 };
341 use std::path::{Path, PathBuf};
342 use std::time::{SystemTime, UNIX_EPOCH};
343 use std::{fs, io};
344
345 fn temp_packages_file(name: &str) -> PathBuf {
346 let nanos = SystemTime::now()
347 .duration_since(UNIX_EPOCH)
348 .map(|d| d.as_nanos())
349 .unwrap_or(0);
350 std::env::temp_dir()
351 .join(format!("upstream-metadata-test-{name}-{nanos}"))
352 .join("packages.json")
353 }
354
355 fn temp_metadata_file(name: &str) -> PathBuf {
356 let nanos = SystemTime::now()
357 .duration_since(UNIX_EPOCH)
358 .map(|d| d.as_nanos())
359 .unwrap_or(0);
360 std::env::temp_dir()
361 .join(format!("upstream-metadata-test-{name}-{nanos}"))
362 .join("metadata.json")
363 }
364
365 fn test_package(name: &str) -> Package {
366 Package::with_defaults(
367 name.to_string(),
368 format!("owner/{name}"),
369 Filetype::Archive,
370 None,
371 None,
372 Channel::Stable,
373 Provider::Github,
374 None,
375 )
376 }
377
378 fn cleanup(path: &Path) -> io::Result<()> {
379 if let Some(parent) = path.parent() {
380 fs::remove_dir_all(parent)?;
381 }
382 Ok(())
383 }
384
385 #[test]
386 fn parse_set_key_requires_key_value_pair() {
387 assert!(MetadataManager::parse_set_key("is_pinned=true").is_ok());
388 assert!(MetadataManager::parse_set_key("invalid").is_err());
389 assert!(MetadataManager::parse_set_key("=value").is_err());
390 }
391
392 #[test]
393 fn pin_and_unpin_update_package_state() {
394 let path = temp_packages_file("pin");
395 fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
396 let metadata_path = temp_metadata_file("pin");
397 let mut storage = PackageStorage::new(&path).expect("create storage");
398 let mut metadata_storage = MetadataStorage::new(&metadata_path).expect("metadata");
399 storage
400 .add_or_update_package(test_package("fd"))
401 .expect("store package");
402 let mut manager = MetadataManager::new(&mut storage, &mut metadata_storage);
403
404 manager.pin_package("fd", None).expect("pin package");
405 assert!(
406 manager
407 .package_storage
408 .get_package_by_name("fd")
409 .expect("package")
410 .is_pinned
411 );
412
413 manager.unpin_package("fd").expect("unpin package");
414 assert!(
415 !manager
416 .package_storage
417 .get_package_by_name("fd")
418 .expect("package")
419 .is_pinned
420 );
421
422 cleanup(&path).expect("cleanup");
423 let _ = cleanup(&metadata_path);
424 }
425
426 #[test]
427 fn set_key_and_get_key_support_nested_and_typed_values() {
428 let path = temp_packages_file("set-get");
429 fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
430 let metadata_path = temp_metadata_file("set-get");
431 let mut storage = PackageStorage::new(&path).expect("create storage");
432 let mut metadata_storage = MetadataStorage::new(&metadata_path).expect("metadata");
433 storage
434 .add_or_update_package(test_package("rg"))
435 .expect("store package");
436 let mut manager = MetadataManager::new(&mut storage, &mut metadata_storage);
437
438 manager
439 .set_key("rg", "is_pinned=true")
440 .expect("set bool key");
441 manager
442 .set_key("rg", "version.major=12")
443 .expect("set nested numeric key");
444
445 assert_eq!(
446 manager.get_key("rg", "is_pinned").expect("get bool"),
447 "true"
448 );
449 assert_eq!(
450 manager.get_key("rg", "version.major").expect("get nested"),
451 "12"
452 );
453
454 cleanup(&path).expect("cleanup");
455 let _ = cleanup(&metadata_path);
456 }
457
458 #[test]
459 fn rename_package_rejects_duplicates_and_updates_alias() {
460 let path = temp_packages_file("rename");
461 fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
462 let metadata_path = temp_metadata_file("rename");
463 let mut storage = PackageStorage::new(&path).expect("create storage");
464 let mut metadata_storage = MetadataStorage::new(&metadata_path).expect("metadata");
465 storage
466 .add_or_update_package(test_package("old"))
467 .expect("store old");
468 storage
469 .add_or_update_package(test_package("taken"))
470 .expect("store taken");
471 let mut manager = MetadataManager::new(&mut storage, &mut metadata_storage);
472
473 assert!(manager.rename_package("old", "taken").is_err());
474 manager
475 .rename_package("old", "new")
476 .expect("rename package");
477 assert!(manager.package_storage.get_package_by_name("new").is_some());
478 assert!(manager.package_storage.get_package_by_name("old").is_none());
479
480 cleanup(&path).expect("cleanup");
481 let _ = cleanup(&metadata_path);
482 }
483
484 #[test]
485 fn remove_package_deletes_metadata_and_errors_when_missing() {
486 let path = temp_packages_file("remove");
487 fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
488 let metadata_path = temp_metadata_file("remove");
489 let mut storage = PackageStorage::new(&path).expect("create storage");
490 let mut metadata_storage = MetadataStorage::new(&metadata_path).expect("metadata");
491 storage
492 .add_or_update_package(test_package("fd"))
493 .expect("store package");
494 let mut manager = MetadataManager::new(&mut storage, &mut metadata_storage);
495
496 manager
497 .remove_package("fd")
498 .expect("remove package metadata");
499 assert!(manager.package_storage.get_package_by_name("fd").is_none());
500
501 let err = manager
502 .remove_package("fd")
503 .expect_err("missing package should error");
504 assert!(err.to_string().contains("Package 'fd' not found"));
505
506 cleanup(&path).expect("cleanup");
507 let _ = cleanup(&metadata_path);
508 }
509}