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::{decode_cycle_balance_response, decode_subnet_registry_response},
15};
16use crate::registry::{RegistryEntry, RegistryParseError, registry_entries_from_response};
17use std::path::Path;
18
19use candid::Decode;
20use canic_core::dto::error::Error as CanicError;
21use thiserror::Error as ThisError;
22
23pub use self::status::local_replica_status_reachable_from_root;
24pub(crate) use self::{
25    status::local_replica_root_key_from_root, transport::local_replica_endpoint_from_root,
26};
27
28///
29/// ReplicaQueryError
30///
31
32#[derive(Debug, ThisError)]
33pub enum ReplicaQueryError {
34    #[error(transparent)]
35    Candid(candid::Error),
36
37    #[error("{0}")]
38    Canister(CanicError),
39
40    #[error("{0}")]
41    Cbor(String),
42
43    #[error(transparent)]
44    Io(#[from] std::io::Error),
45
46    #[error("{0}")]
47    Query(String),
48
49    #[error(transparent)]
50    Registry(#[from] RegistryParseError),
51
52    #[error("local replica rejected query: code={code} message={message}")]
53    Rejected { code: u64, message: String },
54}
55
56impl From<cbor::CborError> for ReplicaQueryError {
57    // Convert CBOR encode/decode failures.
58    fn from(err: cbor::CborError) -> Self {
59        Self::Cbor(err.to_string())
60    }
61}
62
63/// Return whether the selected environment should use direct local replica queries.
64#[must_use]
65pub fn should_use_local_replica_query(environment: Option<&str>) -> bool {
66    environment
67        .is_none_or(|environment| environment == "local" || environment.starts_with("http://"))
68}
69
70/// Query `canic_ready` directly through the local replica HTTP API.
71pub(crate) fn query_ready(
72    environment: Option<&str>,
73    canister: &str,
74    icp_root: Option<&Path>,
75) -> Result<bool, ReplicaQueryError> {
76    let bytes = local_query(environment, canister, "canic_ready", icp_root)?;
77    Decode!(&bytes, bool).map_err(ReplicaQueryError::Candid)
78}
79
80/// Query `canic_cycle_balance` directly through the local replica HTTP API.
81pub(crate) fn query_cycle_balance(
82    environment: Option<&str>,
83    canister: &str,
84    icp_root: Option<&Path>,
85) -> Result<u128, ReplicaQueryError> {
86    let bytes = local_query(environment, canister, "canic_cycle_balance", icp_root)?;
87    decode_cycle_balance_response(&bytes)
88}
89
90/// Query `canic_subnet_registry` and return validated host entries.
91pub(crate) fn query_subnet_registry_entries(
92    environment: Option<&str>,
93    root: &str,
94    icp_root: Option<&Path>,
95) -> Result<Vec<RegistryEntry>, ReplicaQueryError> {
96    let bytes = local_query(environment, root, "canic_subnet_registry", icp_root)?;
97    let response = decode_subnet_registry_response(&bytes)?;
98    registry_entries_from_response(response).map_err(Into::into)
99}