1use std::{error::Error, fmt};
2
3use super::model::{ICP_CLI_SUPPORTED_VERSION_RANGE, REQUIRED_ICP_CLI_VERSION};
4
5#[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 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 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 fn from(err: std::io::Error) -> Self {
98 Self::Io(err)
99 }
100}