Skip to main content

canic_host/subnet_registry/
mod.rs

1//! Module: subnet_registry
2//!
3//! Responsibility: query one root canister's maintained subnet registry.
4//! Does not own: registry state, endpoint DTOs, or deployment discovery policy.
5//! Boundary: selects one transport and returns validated canonical host entries.
6
7use crate::{
8    icp::{IcpCli, IcpCommandError},
9    registry::{RegistryEntry, RegistryParseError, parse_registry_entries},
10    replica_query::{self, ReplicaQueryError},
11};
12use std::{error::Error, fmt, path::Path};
13
14const CANIC_SUBNET_REGISTRY_METHOD: &str = "canic_subnet_registry";
15const ICP_JSON_OUTPUT: &str = "json";
16
17///
18/// SubnetRegistryQuery
19///
20
21#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct SubnetRegistryQuery {
23    pub(crate) source: SubnetRegistryQuerySource,
24    pub entries: Vec<RegistryEntry>,
25}
26
27///
28/// SubnetRegistryQuerySource
29///
30
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub(crate) enum SubnetRegistryQuerySource {
33    IcpCli,
34    LocalReplica,
35}
36
37///
38/// SubnetRegistryQueryError
39///
40
41#[derive(Debug)]
42pub enum SubnetRegistryQueryError {
43    Icp(IcpCommandError),
44    Registry(RegistryParseError),
45    Replica(ReplicaQueryError),
46}
47
48impl fmt::Display for SubnetRegistryQueryError {
49    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::Icp(err) => write!(formatter, "{err}"),
52            Self::Registry(err) => write!(formatter, "{err}"),
53            Self::Replica(err) => write!(formatter, "{err}"),
54        }
55    }
56}
57
58impl Error for SubnetRegistryQueryError {
59    fn source(&self) -> Option<&(dyn Error + 'static)> {
60        match self {
61            Self::Icp(err) => Some(err),
62            Self::Registry(err) => Some(err),
63            Self::Replica(err) => Some(err),
64        }
65    }
66}
67
68impl From<IcpCommandError> for SubnetRegistryQueryError {
69    fn from(err: IcpCommandError) -> Self {
70        Self::Icp(err)
71    }
72}
73
74impl From<RegistryParseError> for SubnetRegistryQueryError {
75    fn from(err: RegistryParseError) -> Self {
76        Self::Registry(err)
77    }
78}
79
80impl From<ReplicaQueryError> for SubnetRegistryQueryError {
81    fn from(err: ReplicaQueryError) -> Self {
82        Self::Replica(err)
83    }
84}
85
86/// Query `canic_subnet_registry`, using the local replica API for local targets.
87pub fn query_subnet_registry(
88    icp: &IcpCli,
89    root: &str,
90    network: &str,
91    icp_root: Option<&Path>,
92    candid_path: Option<&Path>,
93) -> Result<SubnetRegistryQuery, SubnetRegistryQueryError> {
94    if replica_query::should_use_local_replica_query(Some(network)) {
95        return query_local_subnet_registry(root, network, icp_root);
96    }
97
98    let output = icp.canister_query_output_with_candid(
99        root,
100        CANIC_SUBNET_REGISTRY_METHOD,
101        Some(ICP_JSON_OUTPUT),
102        candid_path,
103    )?;
104    Ok(SubnetRegistryQuery {
105        source: SubnetRegistryQuerySource::IcpCli,
106        entries: parse_registry_entries(&output)?,
107    })
108}
109
110fn query_local_subnet_registry(
111    root: &str,
112    network: &str,
113    icp_root: Option<&Path>,
114) -> Result<SubnetRegistryQuery, SubnetRegistryQueryError> {
115    let entries = icp_root.map_or_else(
116        || replica_query::query_subnet_registry_entries(Some(network), root),
117        |root_path| {
118            replica_query::query_subnet_registry_entries_from_root(Some(network), root, root_path)
119        },
120    )?;
121    Ok(SubnetRegistryQuery {
122        source: SubnetRegistryQuerySource::LocalReplica,
123        entries,
124    })
125}