canic_host/replica_query/
mod.rs1pub use self::{
2 status::{local_replica_root_key_from_root, local_replica_status_reachable_from_root},
3 transport::local_replica_endpoint_from_root,
4};
5use self::{
6 transport::{local_query, local_query_from_root},
7 wire::{
8 SubnetRegistryResponseWire, decode_bootstrap_status_response,
9 decode_cycle_balance_response, decode_subnet_registry_response,
10 },
11};
12use candid::Decode;
13use canic_core::dto::state::BootstrapStatusResponse;
14use std::{error::Error, fmt, path::Path};
15
16mod status;
17mod transport;
18mod wire;
19
20#[derive(Debug)]
25pub enum ReplicaQueryError {
26 Io(std::io::Error),
27 Cbor(serde_cbor::Error),
28 Json(serde_json::Error),
29 Query(String),
30 Rejected { code: u64, message: String },
31}
32
33impl fmt::Display for ReplicaQueryError {
34 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Self::Io(err) => write!(formatter, "{err}"),
38 Self::Cbor(err) => write!(formatter, "{err}"),
39 Self::Json(err) => write!(formatter, "{err}"),
40 Self::Query(message) => write!(formatter, "{message}"),
41 Self::Rejected { code, message } => {
42 write!(
43 formatter,
44 "local replica rejected query: code={code} message={message}"
45 )
46 }
47 }
48 }
49}
50
51impl Error for ReplicaQueryError {
52 fn source(&self) -> Option<&(dyn Error + 'static)> {
54 match self {
55 Self::Io(err) => Some(err),
56 Self::Cbor(err) => Some(err),
57 Self::Json(err) => Some(err),
58 Self::Query(_) | Self::Rejected { .. } => None,
59 }
60 }
61}
62
63impl From<std::io::Error> for ReplicaQueryError {
64 fn from(err: std::io::Error) -> Self {
66 Self::Io(err)
67 }
68}
69
70impl From<serde_cbor::Error> for ReplicaQueryError {
71 fn from(err: serde_cbor::Error) -> Self {
73 Self::Cbor(err)
74 }
75}
76
77impl From<serde_json::Error> for ReplicaQueryError {
78 fn from(err: serde_json::Error) -> Self {
80 Self::Json(err)
81 }
82}
83
84#[must_use]
86pub fn should_use_local_replica_query(network: Option<&str>) -> bool {
87 network.is_none_or(|network| network == "local" || network.starts_with("http://"))
88}
89
90pub fn query_ready(network: Option<&str>, canister: &str) -> Result<bool, ReplicaQueryError> {
92 let bytes = local_query(network, canister, "canic_ready")?;
93 Decode!(&bytes, bool).map_err(|err| ReplicaQueryError::Query(err.to_string()))
94}
95
96pub fn query_ready_from_root(
98 network: Option<&str>,
99 canister: &str,
100 icp_root: &Path,
101) -> Result<bool, ReplicaQueryError> {
102 let bytes = local_query_from_root(network, canister, "canic_ready", icp_root)?;
103 Decode!(&bytes, bool).map_err(|err| ReplicaQueryError::Query(err.to_string()))
104}
105
106pub fn query_bootstrap_status_from_root(
108 network: Option<&str>,
109 canister: &str,
110 icp_root: &Path,
111) -> Result<BootstrapStatusResponse, ReplicaQueryError> {
112 let bytes = local_query_from_root(network, canister, "canic_bootstrap_status", icp_root)?;
113 decode_bootstrap_status_response(&bytes)
114}
115
116pub fn query_cycle_balance_from_root(
118 network: Option<&str>,
119 canister: &str,
120 icp_root: &Path,
121) -> Result<u128, ReplicaQueryError> {
122 let bytes = local_query_from_root(network, canister, "canic_cycle_balance", icp_root)?;
123 decode_cycle_balance_response(&bytes)
124}
125
126#[must_use]
128pub fn parse_ready_json_value(data: &serde_json::Value) -> bool {
129 match data {
130 serde_json::Value::Bool(value) => *value,
131 serde_json::Value::Array(values) => values.iter().any(parse_ready_json_value),
132 serde_json::Value::Object(map) => map.get("Ok").is_some_and(parse_ready_json_value),
133 serde_json::Value::String(_) | serde_json::Value::Number(_) | serde_json::Value::Null => {
134 false
135 }
136 }
137}
138
139pub fn query_subnet_registry_json(
141 network: Option<&str>,
142 root: &str,
143) -> Result<String, ReplicaQueryError> {
144 let response = query_subnet_registry_response(network, root)?;
145 serde_json::to_string(&response.to_cli_json()).map_err(ReplicaQueryError::from)
146}
147
148pub fn query_subnet_registry_json_from_root(
150 network: Option<&str>,
151 root: &str,
152 icp_root: &Path,
153) -> Result<String, ReplicaQueryError> {
154 let response = query_subnet_registry_response_from_root(network, root, icp_root)?;
155 serde_json::to_string(&response.to_cli_json()).map_err(ReplicaQueryError::from)
156}
157
158pub fn query_subnet_registry_roles_from_root(
160 network: Option<&str>,
161 root: &str,
162 icp_root: &Path,
163) -> Result<Vec<String>, ReplicaQueryError> {
164 Ok(query_subnet_registry_response_from_root(network, root, icp_root)?.roles())
165}
166
167fn query_subnet_registry_response(
168 network: Option<&str>,
169 root: &str,
170) -> Result<SubnetRegistryResponseWire, ReplicaQueryError> {
171 let bytes = local_query(network, root, "canic_subnet_registry")?;
172 decode_subnet_registry_response(&bytes)
173}
174
175fn query_subnet_registry_response_from_root(
176 network: Option<&str>,
177 root: &str,
178 icp_root: &Path,
179) -> Result<SubnetRegistryResponseWire, ReplicaQueryError> {
180 let bytes = local_query_from_root(network, root, "canic_subnet_registry", icp_root)?;
181 decode_subnet_registry_response(&bytes)
182}
183
184#[cfg(test)]
185mod tests;