Skip to main content

canic_host/canister_ready/
mod.rs

1//! Module: canister_ready
2//!
3//! Responsibility: query the maintained Canic readiness endpoint.
4//! Does not own: readiness state, local replica transport, or install orchestration.
5//! Boundary: selects one transport and decodes the canonical boolean response.
6
7use crate::{
8    icp::{IcpCli, IcpCommandError, IcpJsonResponseError, decode_json_response},
9    replica_query::{self, ReplicaQueryError},
10};
11use std::{error::Error, fmt, path::Path};
12
13const CANIC_READY_METHOD: &str = "canic_ready";
14const ICP_JSON_OUTPUT: &str = "json";
15
16///
17/// CanisterReadyQueryError
18///
19
20#[derive(Debug)]
21pub enum CanisterReadyQueryError {
22    Icp(IcpCommandError),
23    Replica(ReplicaQueryError),
24    Response(IcpJsonResponseError),
25}
26
27impl fmt::Display for CanisterReadyQueryError {
28    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            Self::Icp(err) => write!(formatter, "{err}"),
31            Self::Replica(err) => write!(formatter, "{err}"),
32            Self::Response(err) => write!(formatter, "{err}"),
33        }
34    }
35}
36
37impl Error for CanisterReadyQueryError {
38    fn source(&self) -> Option<&(dyn Error + 'static)> {
39        match self {
40            Self::Icp(err) => Some(err),
41            Self::Replica(err) => Some(err),
42            Self::Response(err) => Some(err),
43        }
44    }
45}
46
47impl From<IcpCommandError> for CanisterReadyQueryError {
48    fn from(err: IcpCommandError) -> Self {
49        Self::Icp(err)
50    }
51}
52
53impl From<ReplicaQueryError> for CanisterReadyQueryError {
54    fn from(err: ReplicaQueryError) -> Self {
55        Self::Replica(err)
56    }
57}
58
59impl From<IcpJsonResponseError> for CanisterReadyQueryError {
60    fn from(err: IcpJsonResponseError) -> Self {
61        Self::Response(err)
62    }
63}
64
65/// Query `canic_ready`, using the local replica API for local network targets.
66pub fn query_canister_ready(
67    icp: &IcpCli,
68    canister_id: &str,
69    network: &str,
70    icp_root: Option<&Path>,
71    candid_path: Option<&Path>,
72) -> Result<bool, CanisterReadyQueryError> {
73    if replica_query::should_use_local_replica_query(Some(network)) {
74        return query_local_canister_ready(network, canister_id, icp_root).map_err(Into::into);
75    }
76
77    query_canister_ready_with_icp(icp, canister_id, candid_path)
78}
79
80/// Query `canic_ready` directly through the local replica API.
81pub fn query_local_canister_ready(
82    network: &str,
83    canister_id: &str,
84    icp_root: Option<&Path>,
85) -> Result<bool, ReplicaQueryError> {
86    icp_root.map_or_else(
87        || replica_query::query_ready(Some(network), canister_id),
88        |root| replica_query::query_ready_from_root(Some(network), canister_id, root),
89    )
90}
91
92fn query_canister_ready_with_icp(
93    icp: &IcpCli,
94    canister_id: &str,
95    candid_path: Option<&Path>,
96) -> Result<bool, CanisterReadyQueryError> {
97    let output = icp.canister_query_output_with_candid(
98        canister_id,
99        CANIC_READY_METHOD,
100        Some(ICP_JSON_OUTPUT),
101        candid_path,
102    )?;
103    decode_json_response(&output).map_err(Into::into)
104}