Skip to main content

ic_host_tools/icp/
status.rs

1//! Installation projection over successful ICP CLI status JSON.
2
3use std::fmt;
4
5/// Status JSON does not establish an installed or uninstalled canister.
6#[derive(Debug)]
7pub enum InstallationStatusError {
8    /// The complete input is not valid JSON.
9    Json(serde_json::Error),
10    /// Successful status output did not include the installation fact.
11    MissingModuleHash,
12    /// The installation fact is neither null nor a string.
13    InvalidModuleHash,
14}
15
16impl fmt::Display for InstallationStatusError {
17    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            Self::Json(source) => write!(formatter, "parse icp canister status JSON: {source}"),
20            Self::MissingModuleHash => {
21                formatter.write_str("icp canister status JSON omitted module_hash")
22            }
23            Self::InvalidModuleHash => {
24                formatter.write_str("icp canister status JSON has an invalid module_hash")
25            }
26        }
27    }
28}
29
30impl std::error::Error for InstallationStatusError {
31    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32        match self {
33            Self::Json(source) => Some(source),
34            Self::MissingModuleHash | Self::InvalidModuleHash => None,
35        }
36    }
37}
38
39/// Project the installation fact from one successful, caller-captured status.
40///
41/// A null `module_hash` means created but uninstalled; any string means installed.
42/// Missing, malformed or failed probes never establish absence. The caller must
43/// check command success first and owns input capture/byte limits and JSON schema
44/// requirements beyond this single fact. Unknown fields are ignored.
45///
46/// # Errors
47/// Returns typed JSON, missing-field or invalid-field failure without guessing.
48pub fn decode_installation_status(input: &[u8]) -> Result<bool, InstallationStatusError> {
49    let status: serde_json::Value =
50        serde_json::from_slice(input).map_err(InstallationStatusError::Json)?;
51    match status.get("module_hash") {
52        Some(serde_json::Value::Null) => Ok(false),
53        Some(serde_json::Value::String(_)) => Ok(true),
54        Some(_) => Err(InstallationStatusError::InvalidModuleHash),
55        None => Err(InstallationStatusError::MissingModuleHash),
56    }
57}