1use crate::utils::{extract_backticked_after, extract_backticked_before};
2use std::process::ExitStatus;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum CargoInstallError {
8 #[error("`cargo` command is not installed or not found in PATH")]
10 CargoNotInstalled,
11 #[error("failed to execute `cargo install`: {0}")]
13 Io(#[from] std::io::Error),
14 #[error("package is already installed: {package}")]
18 AlreadyInstalled { package: String, stderr: String },
19 #[error("binary already exists in destination: {binary}")]
23 BinaryAlreadyExists { binary: String, stderr: String },
24 #[error("package has no installable binaries or examples")]
28 NoInstallableTargets { stderr: String },
29 #[error("failed to compile package: {package}")]
33 CompileFailed { package: String, stderr: String },
34 #[error("unknown `cargo install` error with status {status}: {stderr}")]
38 UnknownCargoError { status: ExitStatus, stderr: String },
39}
40
41impl CargoInstallError {
42 pub(crate) fn from_spawn_error(error: std::io::Error) -> Self {
43 if error.kind() == std::io::ErrorKind::NotFound {
44 Self::CargoNotInstalled
45 } else {
46 Self::Io(error)
47 }
48 }
49
50 pub(crate) fn from_output(status: ExitStatus, stderr: Vec<u8>) -> Self {
51 let stderr = String::from_utf8_lossy(&stderr).trim().to_owned();
52
53 if let Some(package) = extract_backticked_before(&stderr, "is already installed") {
54 return Self::AlreadyInstalled { package, stderr };
55 }
56
57 if let Some(binary) = extract_backticked_after(&stderr, "binary `") {
58 return Self::BinaryAlreadyExists { binary, stderr };
59 }
60
61 if stderr.contains("specified package has no binaries")
62 || stderr.contains("no packages found with binaries or examples")
63 {
64 return Self::NoInstallableTargets { stderr };
65 }
66
67 if let Some(package) = extract_backticked_after(&stderr, "failed to compile `") {
68 return Self::CompileFailed { package, stderr };
69 }
70
71 Self::UnknownCargoError { status, stderr }
72 }
73}