Skip to main content

canic_host/replica_query/
mod.rs

1//! Module: replica_query
2//!
3//! Responsibility: query maintained Canic endpoints through a direct local replica transport.
4//! Does not own: endpoint DTOs, registry projection, or ICP CLI command execution.
5//! Boundary: decodes canonical Candid responses and preserves typed transport and endpoint errors.
6
7mod cbor;
8mod status;
9mod transport;
10mod wire;
11
12use self::{
13    transport::local_query,
14    wire::{
15        decode_bootstrap_status_response, decode_cycle_balance_response,
16        decode_subnet_registry_response,
17    },
18};
19use crate::registry::{RegistryEntry, RegistryParseError, registry_entries_from_response};
20use std::path::Path;
21
22use candid::Decode;
23use canic_core::dto::{error::Error as CanicError, state::BootstrapStatusResponse};
24use thiserror::Error as ThisError;
25
26pub use self::status::local_replica_status_reachable_from_root;
27pub(crate) use self::{
28    status::local_replica_root_key_from_root, transport::local_replica_endpoint_from_root,
29};
30
31///
32/// ReplicaQueryError
33///
34
35#[derive(Debug, ThisError)]
36pub enum ReplicaQueryError {
37    #[error(transparent)]
38    Candid(candid::Error),
39
40    #[error("{0}")]
41    Canister(CanicError),
42
43    #[error("{0}")]
44    Cbor(String),
45
46    #[error(transparent)]
47    Io(#[from] std::io::Error),
48
49    #[error("{0}")]
50    Query(String),
51
52    #[error(transparent)]
53    Registry(#[from] RegistryParseError),
54
55    #[error("local replica rejected query: code={code} message={message}")]
56    Rejected { code: u64, message: String },
57}
58
59impl From<cbor::CborError> for ReplicaQueryError {
60    // Convert CBOR encode/decode failures.
61    fn from(err: cbor::CborError) -> Self {
62        Self::Cbor(err.to_string())
63    }
64}
65
66/// Return whether the selected environment should use direct local replica queries.
67#[must_use]
68pub fn should_use_local_replica_query(environment: Option<&str>) -> bool {
69    environment
70        .is_none_or(|environment| environment == "local" || environment.starts_with("http://"))
71}
72
73/// Query `canic_ready` directly through the local replica HTTP API.
74pub(crate) fn query_ready(
75    environment: Option<&str>,
76    canister: &str,
77    icp_root: Option<&Path>,
78) -> Result<bool, ReplicaQueryError> {
79    let bytes = local_query(environment, canister, "canic_ready", icp_root)?;
80    Decode!(&bytes, bool).map_err(ReplicaQueryError::Candid)
81}
82
83/// Query `canic_bootstrap_status` using the configured port from one ICP root.
84pub(crate) fn query_bootstrap_status_from_root(
85    environment: Option<&str>,
86    canister: &str,
87    icp_root: &Path,
88) -> Result<BootstrapStatusResponse, ReplicaQueryError> {
89    let bytes = local_query(
90        environment,
91        canister,
92        "canic_bootstrap_status",
93        Some(icp_root),
94    )?;
95    decode_bootstrap_status_response(&bytes)
96}
97
98/// Query `canic_cycle_balance` directly through the local replica HTTP API.
99pub(crate) fn query_cycle_balance(
100    environment: Option<&str>,
101    canister: &str,
102    icp_root: Option<&Path>,
103) -> Result<u128, ReplicaQueryError> {
104    let bytes = local_query(environment, canister, "canic_cycle_balance", icp_root)?;
105    decode_cycle_balance_response(&bytes)
106}
107
108/// Query `canic_subnet_registry` and return validated host entries.
109pub(crate) fn query_subnet_registry_entries(
110    environment: Option<&str>,
111    root: &str,
112    icp_root: Option<&Path>,
113) -> Result<Vec<RegistryEntry>, ReplicaQueryError> {
114    let bytes = local_query(environment, root, "canic_subnet_registry", icp_root)?;
115    let response = decode_subnet_registry_response(&bytes)?;
116    registry_entries_from_response(response).map_err(Into::into)
117}
118
119/// Query `canic_subnet_registry` using the configured port from one ICP root and return roles.
120pub(crate) fn query_subnet_registry_roles_from_root(
121    environment: Option<&str>,
122    root: &str,
123    icp_root: &Path,
124) -> Result<Vec<String>, ReplicaQueryError> {
125    Ok(
126        query_subnet_registry_entries(environment, root, Some(icp_root))?
127            .into_iter()
128            .filter_map(|entry| entry.role)
129            .collect(),
130    )
131}