canic_backup/discovery/
mod.rs1#[cfg(test)]
8mod tests;
9
10use crate::registry::RegistryEntry;
11use std::collections::{BTreeMap, BTreeSet, VecDeque};
12use thiserror::Error as ThisError;
13
14#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct SnapshotTarget {
23 pub canister_id: String,
24 pub role: Option<String>,
25 pub parent_canister_id: Option<String>,
26 pub module_hash: Option<String>,
27}
28
29#[derive(Debug, ThisError)]
37pub enum DiscoveryError {
38 #[error("registry JSON did not contain the requested canister {0}")]
39 CanisterNotInRegistry(String),
40}
41
42pub fn targets_from_registry(
44 registry: &[RegistryEntry],
45 canister_id: &str,
46 recursive: bool,
47) -> Result<Vec<SnapshotTarget>, DiscoveryError> {
48 let by_pid = registry
49 .iter()
50 .map(|entry| (entry.pid.as_str(), entry))
51 .collect::<BTreeMap<_, _>>();
52
53 let root = by_pid
54 .get(canister_id)
55 .ok_or_else(|| DiscoveryError::CanisterNotInRegistry(canister_id.to_string()))?;
56
57 let mut targets = Vec::new();
58 let mut seen = BTreeSet::new();
59 targets.push(SnapshotTarget {
60 canister_id: root.pid.clone(),
61 role: root.role.clone(),
62 parent_canister_id: root.parent_pid.clone(),
63 module_hash: root.module_hash.clone(),
64 });
65 seen.insert(root.pid.clone());
66
67 let mut queue = VecDeque::from([root.pid.clone()]);
68 while let Some(parent) = queue.pop_front() {
69 for child in registry
70 .iter()
71 .filter(|entry| entry.parent_pid.as_deref() == Some(parent.as_str()))
72 {
73 if seen.insert(child.pid.clone()) {
74 targets.push(SnapshotTarget {
75 canister_id: child.pid.clone(),
76 role: child.role.clone(),
77 parent_canister_id: child.parent_pid.clone(),
78 module_hash: child.module_hash.clone(),
79 });
80 if recursive {
81 queue.push_back(child.pid.clone());
82 }
83 }
84 }
85 }
86
87 Ok(targets)
88}