upstream_rs/application/operations/
metadata_operation.rs1use crate::services::storage::package_storage::PackageStorage;
2use anyhow::{Context, Result};
3use console::style;
4
5macro_rules! message {
6 ($cb:expr, $($arg:tt)*) => {{
7 if let Some(cb) = $cb.as_mut() {
8 cb(&format!($($arg)*));
9 }
10 }};
11}
12
13pub struct MetadataManager<'a> {
14 package_storage: &'a mut PackageStorage,
15}
16
17impl<'a> MetadataManager<'a> {
18 pub fn new(package_storage: &'a mut PackageStorage) -> Self {
19 Self { package_storage }
20 }
21
22 pub fn pin_package<H>(&mut self, name: &str, message_callback: &mut Option<H>) -> Result<()>
24 where
25 H: FnMut(&str),
26 {
27 message!(message_callback, "Pinning package '{}'...", name);
28
29 let package = self
30 .package_storage
31 .get_mut_package_by_name(name)
32 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", name))?;
33
34 if package.is_pinned {
35 message!(
36 message_callback,
37 "{}",
38 style(format!("Package '{}' is already pinned", name)).yellow()
39 );
40 return Ok(());
41 }
42
43 let version = package.version.clone();
44 package.is_pinned = true;
45 self.package_storage.save_packages()?;
46
47 message!(
48 message_callback,
49 "{}",
50 style(format!("Package '{}' pinned at version {}", name, version)).green()
51 );
52
53 Ok(())
54 }
55
56 pub fn unpin_package<H>(&mut self, name: &str, message_callback: &mut Option<H>) -> Result<()>
58 where
59 H: FnMut(&str),
60 {
61 message!(message_callback, "Unpinning package '{}'...", name);
62
63 let package = self
64 .package_storage
65 .get_mut_package_by_name(name)
66 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", name))?;
67
68 if !package.is_pinned {
69 message!(
70 message_callback,
71 "{}",
72 style(format!("Package '{}' is not pinned", name)).yellow()
73 );
74 return Ok(());
75 }
76
77 package.is_pinned = false;
78 self.package_storage.save_packages()?;
79
80 message!(
81 message_callback,
82 "{}",
83 style(format!("Package '{}' unpinned", name)).green()
84 );
85
86 Ok(())
87 }
88
89 pub fn rename_package<H>(
91 &mut self,
92 old_name: &str,
93 new_name: &str,
94 message_callback: &mut Option<H>,
95 ) -> Result<()>
96 where
97 H: FnMut(&str),
98 {
99 let old_name = old_name.trim();
100 let new_name = new_name.trim();
101
102 if old_name.is_empty() || new_name.is_empty() {
103 return Err(anyhow::anyhow!("Package names cannot be empty"));
104 }
105
106 if old_name == new_name {
107 message!(
108 message_callback,
109 "{}",
110 style("Old and new package names are identical; no changes made").yellow()
111 );
112 return Ok(());
113 }
114
115 if self.package_storage.get_package_by_name(new_name).is_some() {
116 return Err(anyhow::anyhow!("Package '{}' already exists", new_name));
117 }
118
119 message!(
120 message_callback,
121 "Renaming package '{}' -> '{}' ...",
122 old_name,
123 new_name
124 );
125
126 let package = self
127 .package_storage
128 .get_mut_package_by_name(old_name)
129 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", old_name))?;
130
131 package.name = new_name.to_string();
132 self.package_storage.save_packages()?;
133
134 message!(
135 message_callback,
136 "{}",
137 style(format!("Package '{}' renamed to '{}'", old_name, new_name)).green()
138 );
139
140 Ok(())
141 }
142
143 pub fn set_key<H>(
146 &mut self,
147 name: &str,
148 set_key: &str,
149 message_callback: &mut Option<H>,
150 ) -> Result<()>
151 where
152 H: FnMut(&str),
153 {
154 let (key_path, value) = Self::parse_set_key(set_key)?;
155
156 message!(
157 message_callback,
158 "Setting '{}' for package '{}' = '{}'",
159 key_path,
160 name,
161 value
162 );
163
164 let package = self
166 .package_storage
167 .get_package_by_name(name)
168 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", name))?;
169
170 let mut json_value = serde_json::to_value(package)?;
172
173 Self::set_nested_value(&mut json_value, &key_path, &value)?;
175
176 let updated_package: crate::models::upstream::Package =
178 serde_json::from_value(json_value).context("Failed to deserialize updated package")?;
179
180 self.package_storage
182 .add_or_update_package(updated_package)?;
183
184 message!(
185 message_callback,
186 "{}",
187 style("Package metadata updated successfully").green()
188 );
189
190 Ok(())
191 }
192
193 pub fn get_key<H>(
196 &self,
197 name: &str,
198 get_key: &str,
199 message_callback: &mut Option<H>,
200 ) -> Result<String>
201 where
202 H: FnMut(&str),
203 {
204 let key_path = get_key.trim();
205 if key_path.is_empty() {
206 return Err(anyhow::anyhow!("Key path cannot be empty"));
207 }
208
209 message!(
210 message_callback,
211 "Getting value for '{}' from package '{}'",
212 key_path,
213 name
214 );
215
216 let package = self
218 .package_storage
219 .get_package_by_name(name)
220 .ok_or_else(|| anyhow::anyhow!("Package '{}' not found", name))?;
221
222 let json_value = serde_json::to_value(package)?;
224
225 let value = Self::get_nested_value(&json_value, key_path)?;
227
228 let value_str = Self::format_value(&value);
229 message!(
230 message_callback,
231 "{}.{} = {}",
232 name,
233 key_path,
234 style(&value_str).cyan()
235 );
236
237 Ok(value_str)
238 }
239
240 pub fn set_bulk<H>(
242 &mut self,
243 name: &str,
244 set_keys: &[String],
245 message_callback: &mut Option<H>,
246 ) -> Result<()>
247 where
248 H: FnMut(&str),
249 {
250 let mut failures = 0;
251
252 for set_key in set_keys {
253 match self.set_key(name, set_key, message_callback) {
254 Ok(_) => {}
255 Err(e) => {
256 message!(message_callback, "Failed to set '{}': {}", set_key, e);
257 failures += 1;
258 }
259 }
260 }
261
262 if failures > 0 {
263 message!(
264 message_callback,
265 "{} {}",
266 failures,
267 style("key(s) failed to be set").red()
268 );
269 }
270
271 Ok(())
272 }
273
274 pub fn get_bulk<H>(
276 &self,
277 name: &str,
278 get_keys: &[String],
279 message_callback: &mut Option<H>,
280 ) -> Result<Vec<(String, String)>>
281 where
282 H: FnMut(&str),
283 {
284 let mut results = Vec::new();
285
286 for get_key in get_keys {
287 match self.get_key(name, get_key, message_callback) {
288 Ok(value) => {
289 results.push((get_key.clone(), value));
290 }
291 Err(e) => {
292 message!(
293 message_callback,
294 "{} '{}': {}",
295 style("Failed to get").red(),
296 get_key,
297 e
298 );
299 }
300 }
301 }
302
303 Ok(results)
304 }
305
306 fn parse_set_key(set_key: &str) -> Result<(String, String)> {
308 let parts: Vec<&str> = set_key.splitn(2, '=').collect();
309 if parts.len() != 2 {
310 return Err(anyhow::anyhow!(
311 "Invalid set_key format. Expected 'key=value', got '{}'",
312 set_key
313 ));
314 }
315
316 let key_path = parts[0].trim();
317 let value = parts[1].trim();
318
319 if key_path.is_empty() {
320 return Err(anyhow::anyhow!("Key path cannot be empty"));
321 }
322
323 Ok((key_path.to_string(), value.to_string()))
324 }
325
326 fn get_nested_value(json: &serde_json::Value, path: &str) -> Result<serde_json::Value> {
328 let keys: Vec<&str> = path.split('.').collect();
329 let mut current = json;
330
331 for key in keys {
332 current = current
333 .get(key)
334 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found", key))?;
335 }
336
337 Ok(current.clone())
338 }
339
340 fn set_nested_value(json: &mut serde_json::Value, path: &str, value: &str) -> Result<()> {
342 let keys: Vec<&str> = path.split('.').collect();
343
344 if keys.is_empty() {
345 return Err(anyhow::anyhow!("Empty path"));
346 }
347
348 let mut current = json;
349
350 for key in &keys[..keys.len() - 1] {
352 current = current
353 .get_mut(key)
354 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found", key))?;
355 }
356
357 let final_key = keys[keys.len() - 1];
359 let target = current
360 .get_mut(final_key)
361 .ok_or_else(|| anyhow::anyhow!("Field '{}' not found", final_key))?;
362
363 *target = Self::parse_value_for_type(target, value)?;
365
366 Ok(())
367 }
368
369 fn parse_value_for_type(
371 existing: &serde_json::Value,
372 value_str: &str,
373 ) -> Result<serde_json::Value> {
374 match existing {
375 serde_json::Value::Bool(_) => {
376 let bool_val = value_str
377 .parse::<bool>()
378 .with_context(|| format!("Expected boolean value, got '{}'", value_str))?;
379 Ok(serde_json::Value::Bool(bool_val))
380 }
381 serde_json::Value::Number(_) => {
382 if let Ok(int_val) = value_str.parse::<i64>() {
383 Ok(serde_json::json!(int_val))
384 } else if let Ok(float_val) = value_str.parse::<f64>() {
385 Ok(serde_json::json!(float_val))
386 } else {
387 Err(anyhow::anyhow!(
388 "Expected numeric value, got '{}'",
389 value_str
390 ))
391 }
392 }
393 serde_json::Value::String(_) => Ok(serde_json::Value::String(value_str.to_string())),
394 serde_json::Value::Null => {
395 if value_str == "null" {
396 Ok(serde_json::Value::Null)
397 } else {
398 if let Ok(bool_val) = value_str.parse::<bool>() {
400 Ok(serde_json::Value::Bool(bool_val))
401 } else if let Ok(int_val) = value_str.parse::<i64>() {
402 Ok(serde_json::json!(int_val))
403 } else {
404 Ok(serde_json::Value::String(value_str.to_string()))
405 }
406 }
407 }
408 _ => {
409 serde_json::from_str(value_str).with_context(|| {
411 format!(
412 "Cannot set complex type from string. Expected JSON, got '{}'",
413 value_str
414 )
415 })
416 }
417 }
418 }
419
420 fn format_value(value: &serde_json::Value) -> String {
422 match value {
423 serde_json::Value::String(s) => s.clone(),
424 serde_json::Value::Null => "null".to_string(),
425 serde_json::Value::Bool(b) => b.to_string(),
426 serde_json::Value::Number(n) => n.to_string(),
427 serde_json::Value::Array(_) | serde_json::Value::Object(_) => {
428 serde_json::to_string_pretty(value).unwrap_or_else(|_| "{}".to_string())
429 }
430 }
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::MetadataManager;
437 use crate::models::common::enums::{Channel, Filetype, Provider};
438 use crate::models::upstream::Package;
439 use crate::services::storage::package_storage::PackageStorage;
440 use std::path::{Path, PathBuf};
441 use std::time::{SystemTime, UNIX_EPOCH};
442 use std::{fs, io};
443
444 fn temp_packages_file(name: &str) -> PathBuf {
445 let nanos = SystemTime::now()
446 .duration_since(UNIX_EPOCH)
447 .map(|d| d.as_nanos())
448 .unwrap_or(0);
449 std::env::temp_dir()
450 .join(format!("upstream-metadata-test-{name}-{nanos}"))
451 .join("packages.json")
452 }
453
454 fn test_package(name: &str) -> Package {
455 Package::with_defaults(
456 name.to_string(),
457 format!("owner/{name}"),
458 Filetype::Archive,
459 None,
460 None,
461 Channel::Stable,
462 Provider::Github,
463 None,
464 )
465 }
466
467 fn cleanup(path: &Path) -> io::Result<()> {
468 if let Some(parent) = path.parent() {
469 fs::remove_dir_all(parent)?;
470 }
471 Ok(())
472 }
473
474 #[test]
475 fn parse_set_key_requires_key_value_pair() {
476 assert!(MetadataManager::parse_set_key("is_pinned=true").is_ok());
477 assert!(MetadataManager::parse_set_key("invalid").is_err());
478 assert!(MetadataManager::parse_set_key("=value").is_err());
479 }
480
481 #[test]
482 fn pin_and_unpin_update_package_state() {
483 let path = temp_packages_file("pin");
484 fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
485 let mut storage = PackageStorage::new(&path).expect("create storage");
486 storage
487 .add_or_update_package(test_package("fd"))
488 .expect("store package");
489 let mut manager = MetadataManager::new(&mut storage);
490 let mut messages: Option<fn(&str)> = None;
491
492 manager
493 .pin_package("fd", &mut messages)
494 .expect("pin package");
495 assert!(
496 manager
497 .package_storage
498 .get_package_by_name("fd")
499 .expect("package")
500 .is_pinned
501 );
502
503 manager
504 .unpin_package("fd", &mut messages)
505 .expect("unpin package");
506 assert!(
507 !manager
508 .package_storage
509 .get_package_by_name("fd")
510 .expect("package")
511 .is_pinned
512 );
513
514 cleanup(&path).expect("cleanup");
515 }
516
517 #[test]
518 fn set_key_and_get_key_support_nested_and_typed_values() {
519 let path = temp_packages_file("set-get");
520 fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
521 let mut storage = PackageStorage::new(&path).expect("create storage");
522 storage
523 .add_or_update_package(test_package("rg"))
524 .expect("store package");
525 let mut manager = MetadataManager::new(&mut storage);
526 let mut messages: Option<fn(&str)> = None;
527
528 manager
529 .set_key("rg", "is_pinned=true", &mut messages)
530 .expect("set bool key");
531 manager
532 .set_key("rg", "version.major=12", &mut messages)
533 .expect("set nested numeric key");
534
535 assert_eq!(
536 manager
537 .get_key("rg", "is_pinned", &mut messages)
538 .expect("get bool"),
539 "true"
540 );
541 assert_eq!(
542 manager
543 .get_key("rg", "version.major", &mut messages)
544 .expect("get nested"),
545 "12"
546 );
547
548 cleanup(&path).expect("cleanup");
549 }
550
551 #[test]
552 fn rename_package_rejects_duplicates_and_updates_alias() {
553 let path = temp_packages_file("rename");
554 fs::create_dir_all(path.parent().expect("parent")).expect("create parent");
555 let mut storage = PackageStorage::new(&path).expect("create storage");
556 storage
557 .add_or_update_package(test_package("old"))
558 .expect("store old");
559 storage
560 .add_or_update_package(test_package("taken"))
561 .expect("store taken");
562 let mut manager = MetadataManager::new(&mut storage);
563 let mut messages: Option<fn(&str)> = None;
564
565 assert!(
566 manager
567 .rename_package("old", "taken", &mut messages)
568 .is_err()
569 );
570 manager
571 .rename_package("old", "new", &mut messages)
572 .expect("rename package");
573 assert!(manager.package_storage.get_package_by_name("new").is_some());
574 assert!(manager.package_storage.get_package_by_name("old").is_none());
575
576 cleanup(&path).expect("cleanup");
577 }
578}