Skip to main content

canic_host/icp/
error.rs

1use std::{error::Error, fmt};
2
3use super::model::{ICP_CLI_SUPPORTED_VERSION_RANGE, REQUIRED_ICP_CLI_VERSION};
4
5///
6/// IcpCommandError
7///
8
9#[derive(Debug)]
10pub enum IcpCommandError {
11    Io(std::io::Error),
12    MissingCli {
13        executable: String,
14    },
15    IncompatibleCliVersion {
16        executable: String,
17        found: String,
18    },
19    Failed {
20        command: String,
21        stderr: String,
22    },
23    Json {
24        command: String,
25        output: String,
26        source: serde_json::Error,
27    },
28    SnapshotIdUnavailable {
29        output: String,
30    },
31}
32
33impl fmt::Display for IcpCommandError {
34    // Render ICP CLI command failures with the command line and captured diagnostics.
35    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            Self::Io(err) => write!(formatter, "{err}"),
38            Self::MissingCli { executable } => {
39                write!(
40                    formatter,
41                    "icp-cli executable not found: {executable}\nrequired: icp-cli {ICP_CLI_SUPPORTED_VERSION_RANGE}\n{}",
42                    icp_cli_install_hint(),
43                )
44            }
45            Self::IncompatibleCliVersion { executable, found } => {
46                write!(
47                    formatter,
48                    "unsupported icp-cli version for {executable}\nfound: {found}\nrequired: icp-cli {ICP_CLI_SUPPORTED_VERSION_RANGE}\n{}",
49                    icp_cli_install_hint(),
50                )
51            }
52            Self::Failed { command, stderr } => {
53                write!(formatter, "icp command failed: {command}\n{stderr}")
54            }
55            Self::Json {
56                command,
57                output,
58                source,
59            } => {
60                write!(
61                    formatter,
62                    "could not parse icp json output for {command}: {source}\n{output}"
63                )
64            }
65            Self::SnapshotIdUnavailable { output } => {
66                write!(
67                    formatter,
68                    "could not parse snapshot id from icp output: {output}"
69                )
70            }
71        }
72    }
73}
74
75fn icp_cli_install_hint() -> String {
76    format!(
77        "next: install icp-cli {REQUIRED_ICP_CLI_VERSION} and ensure it is first on PATH\n  curl --proto '=https' --tlsv1.2 -LsSf https://github.com/dfinity/icp-cli/releases/download/v{REQUIRED_ICP_CLI_VERSION}/icp-cli-installer.sh | sh\nnote: `icp network update` updates the local network launcher, not the `icp` CLI binary; pass top-level --icp <path> to use a non-PATH install"
78    )
79}
80
81impl Error for IcpCommandError {
82    // Preserve the underlying I/O error as the source when command execution fails locally.
83    fn source(&self) -> Option<&(dyn Error + 'static)> {
84        match self {
85            Self::Io(err) => Some(err),
86            Self::Json { source, .. } => Some(source),
87            Self::Failed { .. }
88            | Self::IncompatibleCliVersion { .. }
89            | Self::MissingCli { .. }
90            | Self::SnapshotIdUnavailable { .. } => None,
91        }
92    }
93}
94
95impl From<std::io::Error> for IcpCommandError {
96    // Convert process-spawn failures into the shared ICP CLI command error type.
97    fn from(err: std::io::Error) -> Self {
98        Self::Io(err)
99    }
100}