canic/ops/model/memory/directory/
subnet.rs

1use crate::{
2    Error, ThisError,
3    model::memory::directory::{DirectoryView, PrincipalList, SubnetDirectory},
4    ops::{
5        config::ConfigOps,
6        model::memory::{
7            MemoryOpsError,
8            directory::{DirectoryPageDto, paginate},
9            env::EnvOps,
10            topology::SubnetCanisterRegistryOps,
11        },
12    },
13    types::CanisterType,
14};
15use std::collections::BTreeMap;
16
17///
18/// SubnetDirectoryOpsError
19///
20
21#[derive(Debug, ThisError)]
22pub enum SubnetDirectoryOpsError {
23    #[error("canister type {0} not found in subnet directory")]
24    NotFound(CanisterType),
25}
26
27impl From<SubnetDirectoryOpsError> for Error {
28    fn from(err: SubnetDirectoryOpsError) -> Self {
29        MemoryOpsError::from(err).into()
30    }
31}
32
33///
34/// SubnetDirectoryOps
35///
36
37pub struct SubnetDirectoryOps;
38
39impl SubnetDirectoryOps {
40    /// Single source of truth: where do we get the directory?
41    fn resolve_view() -> DirectoryView {
42        if EnvOps::is_root() {
43            Self::root_build_view()
44        } else {
45            SubnetDirectory::view()
46        }
47    }
48
49    pub fn try_get(ty: &CanisterType) -> Result<PrincipalList, Error> {
50        let view = Self::resolve_view();
51
52        view.iter()
53            .find_map(|(t, pids)| (t == ty).then_some(pids.clone()))
54            .ok_or_else(|| SubnetDirectoryOpsError::NotFound(ty.clone()).into())
55    }
56
57    pub fn page(offset: u64, limit: u64) -> Result<DirectoryPageDto, Error> {
58        Ok(paginate(Self::resolve_view(), offset, limit))
59    }
60
61    #[must_use]
62    pub fn export() -> DirectoryView {
63        Self::resolve_view()
64    }
65
66    pub fn import(view: DirectoryView) {
67        SubnetDirectory::import(view);
68    }
69
70    /// Build SubnetDirectory for the current subnet from the registry.
71    #[must_use]
72    pub fn root_build_view() -> DirectoryView {
73        let Ok(subnet_cfg) = ConfigOps::current_subnet() else {
74            return Vec::new();
75        };
76
77        let entries = SubnetCanisterRegistryOps::export();
78        let mut map: BTreeMap<CanisterType, PrincipalList> = BTreeMap::new();
79
80        for entry in entries {
81            let ty = entry.ty.clone();
82
83            if subnet_cfg.subnet_directory.contains(&ty) {
84                map.entry(ty).or_default().0.push(entry.pid);
85            }
86        }
87
88        map.into_iter().collect()
89    }
90}