use snafu::prelude::*;
use std::path::PathBuf;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
#[non_exhaustive]
pub enum Error {
#[snafu(display("Crate name is required"))]
MissingCrateParameter,
#[snafu(display("Repository format must be 'owner/repo', got '{repo}'"))]
InvalidRepoFormat { repo: String },
#[snafu(display(
"Git selectors (--branch, --tag, --rev) can only be used with git sources (--git, --github, \
--gitlab)"
))]
GitSelectorWithoutGitSource,
#[snafu(display("Invalid version requirement '{version}': {source}"))]
InvalidVersionReq { version: String, source: semver::Error },
#[snafu(display("Invalid URL '{url}': {source}"))]
InvalidUrl { url: String, source: url::ParseError },
#[snafu(display(
"Conflicting version specifications: @{at_version} in crate name vs --version {flag_version}. \
Prefer using the @VERSION suffix in the crate name."
))]
ConflictingVersions {
at_version: String,
flag_version: String,
},
#[snafu(display("Crate '{name}' not found in registry"))]
CrateNotFoundInRegistry { name: String },
#[snafu(display("No version of crate '{name}' matches requirement '{requirement}'"))]
NoMatchingVersion { name: String, requirement: String },
#[snafu(display(
"Package '{}' not found in workspace. Available packages: {}",
name,
available.join(", ")
))]
PackageNotFoundInWorkspace { name: String, available: Vec<String> },
#[snafu(display(
"Ambiguous package name: found {count} packages in workspace, but no name was specified. Specify \
which package to use with the 'name' field."
))]
AmbiguousPackageName { count: usize },
#[snafu(display("The crate '{krate}' does not have any binary targets so it cannot be executed"))]
NoPackageBinaries { krate: String },
#[snafu(display(
"Package '{}' has multiple binary targets [{}], but no default was specified. Use --bin to \
specify which binary to build, or set 'default-run' in Cargo.toml",
package,
available.join(", ")
))]
AmbiguousBinaryTarget { package: String, available: Vec<String> },
#[snafu(display(
"Package '{package}' does not contain a {kind} target named '{target}'. Available {kind} targets: {}",
available.join(", ")
))]
RunnableTargetNotFound {
kind: &'static str,
package: String,
target: String,
available: Vec<String>,
},
#[snafu(display("Version mismatch: required version '{requirement}' but found '{found}'"))]
VersionMismatch {
requirement: String,
found: semver::Version,
},
#[snafu(transparent)]
Git {
source: Box<dyn std::error::Error + Send + Sync>,
},
#[snafu(display("Failed to query registry: {source}"))]
Registry { source: tame_index::Error },
#[snafu(display("Error invoking `{}` to read metadata from source dir `{}`: {}",
cargo_path.display(),
source_dir.display(),
source
))]
CargoMetadata {
cargo_path: PathBuf,
source_dir: PathBuf,
source: cargo_metadata::Error,
},
#[snafu(display("Cargo.toml not found in {}", source_dir.display()))]
CargoTomlNotFound { source_dir: PathBuf },
#[snafu(display("Failed to parse version '{version}': {source}"))]
InvalidVersion { version: String, source: semver::Error },
#[snafu(display("{}: {}", path.display(), source))]
Io { path: PathBuf, source: std::io::Error },
#[snafu(display("Failed to rename {} to {}: {}", src.display(), dst.display(), source))]
RenameFile {
src: PathBuf,
dst: PathBuf,
source: std::io::Error,
},
#[snafu(display("Failed to copy binary from {} to {}: {}", src.display(), dst.display(), source))]
CopyBinary {
src: PathBuf,
dst: PathBuf,
source: std::io::Error,
},
#[snafu(display("Failed to create temporary directory in {}: {}", parent.display(), source))]
TempDirCreation { parent: PathBuf, source: std::io::Error },
#[snafu(display("Failed to execute command: {}", source))]
CommandExecution { source: std::io::Error },
#[snafu(display("Failed to build SBOM component: {}", message))]
SbomBuilder { message: String },
#[snafu(display("Tokio runtime error: {source}"))]
TokioRuntime { source: std::io::Error },
#[snafu(display("Tokio task join error: {source}"))]
TokioJoin { source: tokio::task::JoinError },
#[snafu(display("JSON serialization error: {source}"))]
Json { source: serde_json::Error },
#[snafu(display("Cannot download '{name}' v{version}: network required but offline mode enabled"))]
OfflineMode { name: String, version: String },
#[snafu(display("Failed to download registry crate: {source}"))]
RegistryDownload { source: reqwest::Error },
#[snafu(display("Failed to extract crate tarball: {source}"))]
TarExtraction { source: std::io::Error },
#[snafu(display("Download URL not available for crate '{name}' version '{version}'"))]
DownloadUrlUnavailable { name: String, version: String },
#[snafu(display("Executable '{name}' not found in PATH or standard locations"))]
ExecutableNotFound { name: String },
#[snafu(display("Toolchain '{toolchain}' specified but rustup not found"))]
RustupNotFound { toolchain: String },
#[snafu(display("Expected binary not found in cargo build output"))]
BinaryNotFoundInOutput,
#[snafu(display(
"cargo build failed with exit code {}: {}",
exit_code.map(|c| c.to_string()).unwrap_or_else(|| "unknown".to_string()),
stderr
))]
CargoBuildFailed { exit_code: Option<i32>, stderr: String },
#[snafu(display("Failed to copy source tree from {} to {}: {}", src.display(), dst.display(), source))]
CopySourceTree {
src: PathBuf,
dst: PathBuf,
source: Box<dyn std::error::Error + Send + Sync + 'static>,
},
#[snafu(display("Failed to load configuration from {}: {}", path.display(), source))]
ConfigLoad { path: PathBuf, source: figment::Error },
#[snafu(display("Invalid configuration value for '{}': {}", field, message))]
InvalidConfigValue { field: String, message: String },
#[snafu(display("Failed to extract configuration: {}", source))]
ConfigExtract { source: figment::Error },
#[snafu(display("Failed to execute binary at {}: {source}", path.display()))]
ExecFailed { path: PathBuf, source: std::io::Error },
#[snafu(display("Failed to spawn process at {}: {source}", path.display()))]
SpawnFailed { path: PathBuf, source: std::io::Error },
#[snafu(display("Failed to wait for child process: {source}"))]
WaitFailed { source: std::io::Error },
#[cfg(windows)]
#[snafu(display("Failed to set up Windows console control handler"))]
ConsoleHandlerFailed { source: ctrlc::Error },
#[snafu(display("Error determining home directory"))]
Etcetera { source: etcetera::HomeDirError },
}
impl From<crate::git::Error> for Error {
fn from(e: crate::git::Error) -> Self {
Self::Git {
source: Box::new(e) as Box<dyn std::error::Error + Send + Sync>,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;