#[derive(Clone, Debug, Eq, PartialEq)]
pub enum IcpDiagnostic {
AlreadyInstalled,
ProjectManifestMissing,
LocalNetworkNotRunning,
MethodMissing,
CanisterIdMissing,
CanisterWasmMissing,
ForeignLocalReplicaOwner { network: String, project: String },
}
#[must_use]
pub fn classify_icp_diagnostic(message: &str) -> Option<IcpDiagnostic> {
if let Some((network, project)) = foreign_local_replica_owner(message) {
return Some(IcpDiagnostic::ForeignLocalReplicaOwner { network, project });
}
if message.contains("failed to locate project directory")
|| message.contains("project manifest not found")
{
return Some(IcpDiagnostic::ProjectManifestMissing);
}
if message.contains("network 'local' is not running")
|| message.contains("the local network for this project is not running")
{
return Some(IcpDiagnostic::LocalNetworkNotRunning);
}
if message.contains("has no query method")
|| message.contains("method not found")
|| message.contains("Canister has no query method")
{
return Some(IcpDiagnostic::MethodMissing);
}
if message.contains("Cannot find canister id")
|| message.contains("failed to lookup canister ID")
|| message.contains("could not find ID for canister")
|| message.contains("Canister ID is missing")
{
return Some(IcpDiagnostic::CanisterIdMissing);
}
if message.contains("contains no Wasm module") || message.contains("wasm-module-not-found") {
return Some(IcpDiagnostic::CanisterWasmMissing);
}
let lower = message.to_ascii_lowercase();
if lower.contains("already")
&& (lower.contains("install") || lower.contains("installed") || lower.contains("canister"))
{
return Some(IcpDiagnostic::AlreadyInstalled);
}
None
}
fn foreign_local_replica_owner(message: &str) -> Option<(String, String)> {
let marker = " network of the project at '";
let marker_start = message.find(marker)?;
let network = message[..marker_start]
.split_whitespace()
.last()?
.to_string();
let project_start = marker_start + marker.len();
let rest = &message[project_start..];
let project_end = rest.find('\'')?;
Some((network, rest[..project_end].to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classifies_supported_icp_diagnostics() {
let cases = [
(
"Error: canister is already installed",
IcpDiagnostic::AlreadyInstalled,
),
(
"Error: failed to locate project directory\nproject manifest not found",
IcpDiagnostic::ProjectManifestMissing,
),
(
"Error: the local network for this project is not running",
IcpDiagnostic::LocalNetworkNotRunning,
),
(
"Canister has no query method 'canic_metrics'",
IcpDiagnostic::MethodMissing,
),
(
"Cannot find canister id for root",
IcpDiagnostic::CanisterIdMissing,
),
(
"failed to lookup canister ID for canister 'root' in environment 'local'",
IcpDiagnostic::CanisterIdMissing,
),
(
"could not find ID for canister 'root' in environment 'local'",
IcpDiagnostic::CanisterIdMissing,
),
(
"Canister ID is missing for canister 'root'",
IcpDiagnostic::CanisterIdMissing,
),
(
"canister contains no Wasm module",
IcpDiagnostic::CanisterWasmMissing,
),
];
for (message, expected) in cases {
assert_eq!(classify_icp_diagnostic(message), Some(expected));
}
}
#[test]
fn classifies_foreign_local_replica_owner() {
assert_eq!(
classify_icp_diagnostic(
"Error: port 8000 is in use by the demo network of the project at '/projects/toko'"
),
Some(IcpDiagnostic::ForeignLocalReplicaOwner {
network: "demo".to_string(),
project: "/projects/toko".to_string(),
})
);
}
#[test]
fn leaves_unknown_diagnostics_unclassified() {
assert_eq!(
classify_icp_diagnostic("unexpected transport failure"),
None
);
}
}