Browse Source

Replace `snafu` with `thiserror` (#630)

remotes/origin/feat/support-install-latest
Gal Schlezinger 3 years ago committed by GitHub
parent
commit
b24ed660b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 38
      Cargo.lock
  2. 2
      Cargo.toml
  3. 24
      src/choose_version_for_user_input.rs
  4. 19
      src/commands/alias.rs
  5. 10
      src/commands/completions.rs
  6. 13
      src/commands/env.rs
  7. 48
      src/commands/exec.rs
  8. 63
      src/commands/install.rs
  9. 15
      src/commands/ls_local.rs
  10. 12
      src/commands/ls_remote.rs
  11. 13
      src/commands/unalias.rs
  12. 67
      src/commands/uninstall.rs
  13. 59
      src/commands/use.rs
  14. 18
      src/current_version.rs
  15. 59
      src/downloader.rs
  16. 28
      src/installed_versions.rs

38
Cargo.lock generated

@ -371,12 +371,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]] [[package]]
name = "dtoa" name = "dtoa"
version = "0.4.8" version = "0.4.8"
@ -498,12 +492,12 @@ dependencies = [
"serde_json", "serde_json",
"serial_test", "serial_test",
"shell-escape", "shell-escape",
"snafu",
"structopt", "structopt",
"sysinfo", "sysinfo",
"tar", "tar",
"tempfile", "tempfile",
"test-log", "test-log",
"thiserror",
"url", "url",
"xz2", "xz2",
"zip", "zip",
@ -1477,28 +1471,6 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "snafu"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
dependencies = [
"backtrace",
"doc-comment",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.4.2" version = "0.4.2"
@ -1637,18 +1609,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.29" version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.29" version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

2
Cargo.toml

@ -22,7 +22,6 @@ colored = "2.0.0"
zip = "0.5.13" zip = "0.5.13"
tempfile = "3.2.0" tempfile = "3.2.0"
indoc = "1.0.3" indoc = "1.0.3"
snafu = { version = "0.6.10", features = ["backtrace"] }
log = "0.4.14" log = "0.4.14"
env_logger = "0.9.0" env_logger = "0.9.0"
atty = "0.2.14" atty = "0.2.14"
@ -30,6 +29,7 @@ encoding_rs_io = "0.1.7"
reqwest = { version = "0.11.8", features = ["blocking", "json", "rustls-tls", "rustls-tls-native-roots", "brotli"], default-features = false } reqwest = { version = "0.11.8", features = ["blocking", "json", "rustls-tls", "rustls-tls-native-roots", "brotli"], default-features = false }
url = "2.2.2" url = "2.2.2"
sysinfo = "0.22.4" sysinfo = "0.22.4"
thiserror = "1.0.30"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"

24
src/choose_version_for_user_input.rs

@ -6,8 +6,8 @@ use crate::user_version::UserVersion;
use crate::version::Version; use crate::version::Version;
use colored::Colorize; use colored::Colorize;
use log::info; use log::info;
use snafu::{ensure, ResultExt, Snafu};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Debug)] #[derive(Debug)]
pub struct ApplicableVersion { pub struct ApplicableVersion {
@ -29,8 +29,8 @@ pub fn choose_version_for_user_input<'a>(
requested_version: &'a UserVersion, requested_version: &'a UserVersion,
config: &'a FnmConfig, config: &'a FnmConfig,
) -> Result<Option<ApplicableVersion>, Error> { ) -> Result<Option<ApplicableVersion>, Error> {
let all_versions = let all_versions = installed_versions::list(config.installations_dir())
installed_versions::list(config.installations_dir()).context(VersionListing)?; .map_err(|source| Error::VersionListing { source })?;
let result = if let UserVersion::Full(Version::Bypassed) = requested_version { let result = if let UserVersion::Full(Version::Bypassed) = requested_version {
info!( info!(
@ -54,18 +54,16 @@ pub fn choose_version_for_user_input<'a>(
path: alias_path, path: alias_path,
version: Version::Bypassed, version: Version::Bypassed,
}) })
} else { } else if alias_path.exists() {
ensure!(
alias_path.exists(),
CantFindVersion {
requested_version: requested_version.clone()
}
);
info!("Using Node for alias {}", alias_name.cyan()); info!("Using Node for alias {}", alias_name.cyan());
Some(ApplicableVersion { Some(ApplicableVersion {
path: alias_path, path: alias_path,
version: Version::Alias(alias_name), version: Version::Alias(alias_name),
}) })
} else {
return Err(Error::CantFindVersion {
requested_version: requested_version.clone(),
});
} }
} else { } else {
let current_version = requested_version.to_version(&all_versions, config); let current_version = requested_version.to_version(&all_versions, config);
@ -86,10 +84,10 @@ pub fn choose_version_for_user_input<'a>(
Ok(result) Ok(result)
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display("Can't find requested version: {}", requested_version))] #[error("Can't find requested version: {}", requested_version)]
CantFindVersion { requested_version: UserVersion }, CantFindVersion { requested_version: UserVersion },
#[snafu(display("Can't list local installed versions: {}", source))] #[error("Can't list local installed versions: {}", source)]
VersionListing { source: installed_versions::Error }, VersionListing { source: installed_versions::Error },
} }

19
src/commands/alias.rs

@ -4,10 +4,9 @@ use crate::choose_version_for_user_input::{
choose_version_for_user_input, Error as ApplicableVersionError, choose_version_for_user_input, Error as ApplicableVersionError,
}; };
use crate::config::FnmConfig; use crate::config::FnmConfig;
use crate::installed_versions;
use crate::user_version::UserVersion; use crate::user_version::UserVersion;
use snafu::{OptionExt, ResultExt, Snafu};
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub struct Alias { pub struct Alias {
@ -20,26 +19,24 @@ impl Command for Alias {
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let applicable_version = choose_version_for_user_input(&self.to_version, config) let applicable_version = choose_version_for_user_input(&self.to_version, config)
.context(CantUnderstandVersion)? .map_err(|source| Error::CantUnderstandVersion { source })?
.context(VersionNotFound { .ok_or(Error::VersionNotFound {
version: self.to_version, version: self.to_version,
})?; })?;
create_alias(config, &self.name, applicable_version.version()) create_alias(config, &self.name, applicable_version.version())
.context(CantCreateSymlink)?; .map_err(|source| Error::CantCreateSymlink { source })?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display("Can't create symlink for alias: {}", source))] #[error("Can't create symlink for alias: {}", source)]
CantCreateSymlink { source: std::io::Error }, CantCreateSymlink { source: std::io::Error },
#[snafu(display("Can't list local installed versions: {}", source))] #[error("Version {} not found locally", version)]
VersionListingError { source: installed_versions::Error },
#[snafu(display("Version {} not found locally", version))]
VersionNotFound { version: UserVersion }, VersionNotFound { version: UserVersion },
#[snafu(display("{}", source))] #[error(transparent)]
CantUnderstandVersion { source: ApplicableVersionError }, CantUnderstandVersion { source: ApplicableVersionError },
} }

10
src/commands/completions.rs

@ -2,9 +2,9 @@ use super::command::Command;
use crate::cli::Cli; use crate::cli::Cli;
use crate::config::FnmConfig; use crate::config::FnmConfig;
use crate::shell::{infer_shell, AVAILABLE_SHELLS}; use crate::shell::{infer_shell, AVAILABLE_SHELLS};
use snafu::{OptionExt, Snafu};
use structopt::clap::Shell; use structopt::clap::Shell;
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub struct Completions { pub struct Completions {
@ -21,21 +21,21 @@ impl Command for Completions {
let shell = self let shell = self
.shell .shell
.or_else(|| infer_shell().map(Into::into)) .or_else(|| infer_shell().map(Into::into))
.context(CantInferShell)?; .ok_or(Error::CantInferShell)?;
Cli::clap().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut stdio); Cli::clap().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut stdio);
Ok(()) Ok(())
} }
} }
#[derive(Snafu, Debug)] #[derive(Error, Debug)]
pub enum Error { pub enum Error {
#[snafu(display( #[error(
"{}\n{}\n{}\n{}", "{}\n{}\n{}\n{}",
"Can't infer shell!", "Can't infer shell!",
"fnm can't infer your shell based on the process tree.", "fnm can't infer your shell based on the process tree.",
"Maybe it is unsupported? we support the following shells:", "Maybe it is unsupported? we support the following shells:",
shells_as_string() shells_as_string()
))] )]
CantInferShell, CantInferShell,
} }

13
src/commands/env.rs

@ -5,9 +5,9 @@ use crate::outln;
use crate::path_ext::PathExt; use crate::path_ext::PathExt;
use crate::shell::{infer_shell, Shell, AVAILABLE_SHELLS}; use crate::shell::{infer_shell, Shell, AVAILABLE_SHELLS};
use colored::Colorize; use colored::Colorize;
use snafu::{OptionExt, Snafu};
use std::fmt::Debug; use std::fmt::Debug;
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug, Default)] #[derive(StructOpt, Debug, Default)]
pub struct Env { pub struct Env {
@ -59,7 +59,10 @@ impl Command for Env {
); );
} }
let shell: Box<dyn Shell> = self.shell.or_else(&infer_shell).context(CantInferShell)?; let shell: Box<dyn Shell> = self
.shell
.or_else(&infer_shell)
.ok_or(Error::CantInferShell)?;
let multishell_path = make_symlink(config); let multishell_path = make_symlink(config);
let binary_path = if cfg!(windows) { let binary_path = if cfg!(windows) {
multishell_path.clone() multishell_path.clone()
@ -104,15 +107,15 @@ impl Command for Env {
} }
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display( #[error(
"{}\n{}\n{}\n{}", "{}\n{}\n{}\n{}",
"Can't infer shell!", "Can't infer shell!",
"fnm can't infer your shell based on the process tree.", "fnm can't infer your shell based on the process tree.",
"Maybe it is unsupported? we support the following shells:", "Maybe it is unsupported? we support the following shells:",
shells_as_string() shells_as_string()
))] )]
CantInferShell, CantInferShell,
} }

48
src/commands/exec.rs

@ -7,9 +7,9 @@ use crate::outln;
use crate::user_version::UserVersion; use crate::user_version::UserVersion;
use crate::user_version_reader::UserVersionReader; use crate::user_version_reader::UserVersionReader;
use colored::Colorize; use colored::Colorize;
use snafu::{OptionExt, ResultExt, Snafu};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(setting = structopt::clap::AppSettings::TrailingVarArg)] #[structopt(setting = structopt::clap::AppSettings::TrailingVarArg)]
@ -38,7 +38,10 @@ impl Cmd for Exec {
); );
} }
let (binary, arguments) = self.arguments.split_first().context(NoBinaryProvided)?; let (binary, arguments) = self
.arguments
.split_first()
.ok_or(Error::NoBinaryProvided)?;
let version = self let version = self
.version .version
@ -47,11 +50,11 @@ impl Cmd for Exec {
UserVersionReader::Path(current_dir) UserVersionReader::Path(current_dir)
}) })
.into_user_version(config) .into_user_version(config)
.context(CantInferVersion)?; .ok_or(Error::CantInferVersion)?;
let applicable_version = choose_version_for_user_input(&version, config) let applicable_version = choose_version_for_user_input(&version, config)
.context(ApplicableVersionError)? .map_err(|source| Error::ApplicableVersionError { source })?
.context(VersionNotFound { version })?; .ok_or(Error::VersionNotFound { version })?;
#[cfg(windows)] #[cfg(windows)]
let bin_path = applicable_version.path().to_path_buf(); let bin_path = applicable_version.path().to_path_buf();
@ -60,10 +63,11 @@ impl Cmd for Exec {
let bin_path = applicable_version.path().join("bin"); let bin_path = applicable_version.path().join("bin");
let path_env = { let path_env = {
let paths_env = std::env::var_os("PATH").context(CantReadPathVariable)?; let paths_env = std::env::var_os("PATH").ok_or(Error::CantReadPathVariable)?;
let mut paths: Vec<_> = std::env::split_paths(&paths_env).collect(); let mut paths: Vec<_> = std::env::split_paths(&paths_env).collect();
paths.insert(0, bin_path); paths.insert(0, bin_path);
std::env::join_paths(paths).context(CantAddPathToEnvironment)? std::env::join_paths(paths)
.map_err(|source| Error::CantAddPathToEnvironment { source })?
}; };
let exit_status = Command::new(&binary) let exit_status = Command::new(&binary)
@ -77,34 +81,28 @@ impl Cmd for Exec {
.wait() .wait()
.expect("Failed to grab exit code"); .expect("Failed to grab exit code");
let code = exit_status.code().context(CantReadProcessExitCode)?; let code = exit_status.code().ok_or(Error::CantReadProcessExitCode)?;
std::process::exit(code); std::process::exit(code);
} }
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display("Can't read path environment variable"))] #[error("Can't read path environment variable")]
CantReadPathVariable, CantReadPathVariable,
#[snafu(display("Can't add path to environment variable: {}", source))] #[error("Can't add path to environment variable: {}", source)]
CantAddPathToEnvironment { CantAddPathToEnvironment { source: std::env::JoinPathsError },
source: std::env::JoinPathsError, #[error("Can't find version in dotfiles. Please provide a version manually to the command.")]
},
#[snafu(display(
"Can't find version in dotfiles. Please provide a version manually to the command."
))]
CantInferVersion, CantInferVersion,
#[snafu(display("Requested version {} is not currently installed", version))] #[error("Requested version {} is not currently installed", version)]
VersionNotFound { VersionNotFound { version: UserVersion },
version: UserVersion, #[error(transparent)]
},
ApplicableVersionError { ApplicableVersionError {
#[from]
source: UserInputError, source: UserInputError,
}, },
#[snafu(display( #[error("Can't read exit code from process.\nMaybe the process was killed using a signal?")]
"Can't read exit code from process.\nMaybe the process was killed using a signal?"
))]
CantReadProcessExitCode, CantReadProcessExitCode,
#[snafu(display("command not provided. Please provide a command to run as an argument, like {} or {}.\n{} {}", "node".italic(), "bash".italic(), "example:".yellow().bold(), "fnm exec --using=12 node --version".italic().yellow()))] #[error("command not provided. Please provide a command to run as an argument, like {} or {}.\n{} {}", "node".italic(), "bash".italic(), "example:".yellow().bold(), "fnm exec --using=12 node --version".italic().yellow())]
NoBinaryProvided, NoBinaryProvided,
} }

63
src/commands/install.rs

@ -10,8 +10,8 @@ use crate::version::Version;
use crate::version_files::get_user_version_for_directory; use crate::version_files::get_user_version_for_directory;
use colored::Colorize; use colored::Colorize;
use log::debug; use log::debug;
use snafu::{ensure, OptionExt, ResultExt, Snafu};
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug, Default)] #[derive(StructOpt, Debug, Default)]
pub struct Install { pub struct Install {
@ -51,20 +51,19 @@ impl super::command::Command for Install {
let current_version = self let current_version = self
.version()? .version()?
.or_else(|| get_user_version_for_directory(current_dir, config)) .or_else(|| get_user_version_for_directory(current_dir, config))
.context(CantInferVersion)?; .ok_or(Error::CantInferVersion)?;
let version = match current_version.clone() { let version = match current_version.clone() {
UserVersion::Full(Version::Semver(actual_version)) => Version::Semver(actual_version), UserVersion::Full(Version::Semver(actual_version)) => Version::Semver(actual_version),
UserVersion::Full(v @ (Version::Bypassed | Version::Alias(_))) => { UserVersion::Full(v @ (Version::Bypassed | Version::Alias(_))) => {
ensure!(false, UninstallableVersion { version: v }); return Err(Error::UninstallableVersion { version: v });
unreachable!();
} }
UserVersion::Full(Version::Lts(lts_type)) => { UserVersion::Full(Version::Lts(lts_type)) => {
let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror) let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror)
.context(CantListRemoteVersions)?; .map_err(|source| Error::CantListRemoteVersions { source })?;
let picked_version = lts_type let picked_version = lts_type
.pick_latest(&available_versions) .pick_latest(&available_versions)
.with_context(|| CantFindRelevantLts { .ok_or_else(|| Error::CantFindRelevantLts {
lts_type: lts_type.clone(), lts_type: lts_type.clone(),
})? })?
.version .version
@ -78,14 +77,14 @@ impl super::command::Command for Install {
} }
current_version => { current_version => {
let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror) let available_versions: Vec<_> = remote_node_index::list(&config.node_dist_mirror)
.context(CantListRemoteVersions)? .map_err(|source| Error::CantListRemoteVersions { source })?
.drain(..) .drain(..)
.map(|x| x.version) .map(|x| x.version)
.collect(); .collect();
current_version current_version
.to_version(&available_versions, config) .to_version(&available_versions, config)
.context(CantFindNodeVersion { .ok_or(Error::CantFindNodeVersion {
requested_version: current_version, requested_version: current_version,
})? })?
.clone() .clone()
@ -113,7 +112,7 @@ impl super::command::Command for Install {
Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => { Err(err @ DownloaderError::VersionAlreadyInstalled { .. }) => {
outln!(config, Error, "{} {}", "warning:".bold().yellow(), err); outln!(config, Error, "{} {}", "warning:".bold().yellow(), err);
} }
other_err => other_err.context(DownloadError)?, other_err => other_err.map_err(|source| Error::DownloadError { source })?,
}; };
if let UserVersion::Full(Version::Lts(lts_type)) = current_version { if let UserVersion::Full(Version::Lts(lts_type)) = current_version {
@ -123,51 +122,41 @@ impl super::command::Command for Install {
alias_name.cyan(), alias_name.cyan(),
version.v_str().cyan() version.v_str().cyan()
); );
create_alias(config, &alias_name, &version).context(IoError)?; create_alias(config, &alias_name, &version)?;
} }
if !config.default_version_dir().exists() { if !config.default_version_dir().exists() {
debug!("Tagging {} as the default version", version.v_str().cyan()); debug!("Tagging {} as the default version", version.v_str().cyan());
create_alias(config, "default", &version).context(IoError)?; create_alias(config, "default", &version)?;
} }
Ok(()) Ok(())
} }
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display("Can't download the requested binary: {}", source))] #[error("Can't download the requested binary: {}", source)]
DownloadError { DownloadError { source: DownloaderError },
source: DownloaderError, #[error(transparent)]
},
IoError { IoError {
#[from]
source: std::io::Error, source: std::io::Error,
}, },
#[snafu(display( #[error("Can't find version in dotfiles. Please provide a version manually to the command.")]
"Can't find version in dotfiles. Please provide a version manually to the command."
))]
CantInferVersion, CantInferVersion,
#[snafu(display("Having a hard time listing the remote versions: {}", source))] #[error("Having a hard time listing the remote versions: {}", source)]
CantListRemoteVersions { CantListRemoteVersions { source: crate::http::Error },
source: crate::http::Error, #[error(
},
#[snafu(display(
"Can't find a Node version that matches {} in remote", "Can't find a Node version that matches {} in remote",
requested_version requested_version
))] )]
CantFindNodeVersion { CantFindNodeVersion { requested_version: UserVersion },
requested_version: UserVersion, #[error("Can't find relevant LTS named {}", lts_type)]
}, CantFindRelevantLts { lts_type: crate::lts::LtsType },
#[snafu(display("Can't find relevant LTS named {}", lts_type))] #[error("The requested version is not installable: {}", version.v_str())]
CantFindRelevantLts { UninstallableVersion { version: Version },
lts_type: crate::lts::LtsType, #[error("Too many versions provided. Please don't use --lts with a version string.")]
},
#[snafu(display("The requested version is not installable: {}", version.v_str()))]
UninstallableVersion {
version: Version,
},
#[snafu(display("Too many versions provided. Please don't use --lts with a version string."))]
TooManyVersionsProvided, TooManyVersionsProvided,
} }

15
src/commands/ls_local.rs

@ -3,9 +3,9 @@ use crate::config::FnmConfig;
use crate::current_version::current_version; use crate::current_version::current_version;
use crate::version::Version; use crate::version::Version;
use colored::Colorize; use colored::Colorize;
use snafu::{ResultExt, Snafu};
use std::collections::HashMap; use std::collections::HashMap;
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub struct LsLocal {} pub struct LsLocal {}
@ -15,11 +15,12 @@ impl super::command::Command for LsLocal {
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let base_dir = config.installations_dir(); let base_dir = config.installations_dir();
let mut versions = let mut versions = crate::installed_versions::list(base_dir)
crate::installed_versions::list(base_dir).context(CantListLocallyInstalledVersion)?; .map_err(|source| Error::CantListLocallyInstalledVersion { source })?;
versions.insert(0, Version::Bypassed); versions.insert(0, Version::Bypassed);
versions.sort(); versions.sort();
let aliases_hash = generate_aliases_hash(config).context(CantReadAliases)?; let aliases_hash =
generate_aliases_hash(config).map_err(|source| Error::CantReadAliases { source })?;
let curr_version = current_version(config).ok().flatten(); let curr_version = current_version(config).ok().flatten();
for version in versions { for version in versions {
@ -60,12 +61,12 @@ fn generate_aliases_hash(config: &FnmConfig) -> std::io::Result<HashMap<String,
Ok(hashmap) Ok(hashmap)
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display("Can't list locally installed versions: {}", source))] #[error("Can't list locally installed versions: {}", source)]
CantListLocallyInstalledVersion { CantListLocallyInstalledVersion {
source: crate::installed_versions::Error, source: crate::installed_versions::Error,
}, },
#[snafu(display("Can't read aliases: {}", source))] #[error("Can't read aliases: {}", source)]
CantReadAliases { source: std::io::Error }, CantReadAliases { source: std::io::Error },
} }

12
src/commands/ls_remote.rs

@ -1,7 +1,7 @@
use crate::config::FnmConfig; use crate::config::FnmConfig;
use crate::remote_node_index; use crate::remote_node_index;
use snafu::{ResultExt, Snafu};
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub struct LsRemote {} pub struct LsRemote {}
@ -10,7 +10,7 @@ impl super::command::Command for LsRemote {
type Error = Error; type Error = Error;
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let all_versions = remote_node_index::list(&config.node_dist_mirror).context(HttpError)?; let all_versions = remote_node_index::list(&config.node_dist_mirror)?;
for version in all_versions { for version in all_versions {
print!("{}", version.version); print!("{}", version.version);
@ -24,7 +24,11 @@ impl super::command::Command for LsRemote {
} }
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
HttpError { source: crate::http::Error }, #[error(transparent)]
HttpError {
#[from]
source: crate::http::Error,
},
} }

13
src/commands/unalias.rs

@ -3,8 +3,8 @@ use crate::fs::remove_symlink_dir;
use crate::user_version::UserVersion; use crate::user_version::UserVersion;
use crate::version::Version; use crate::version::Version;
use crate::{choose_version_for_user_input, config::FnmConfig}; use crate::{choose_version_for_user_input, config::FnmConfig};
use snafu::{OptionExt, ResultExt, Snafu};
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub struct Unalias { pub struct Unalias {
@ -21,20 +21,21 @@ impl Command for Unalias {
) )
.ok() .ok()
.flatten() .flatten()
.with_context(|| AliasNotFound { .ok_or(Error::AliasNotFound {
requested_alias: self.requested_alias, requested_alias: self.requested_alias,
})?; })?;
remove_symlink_dir(&requested_version.path()).context(CantDeleteSymlink)?; remove_symlink_dir(&requested_version.path())
.map_err(|source| Error::CantDeleteSymlink { source })?;
Ok(()) Ok(())
} }
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display("Can't delete symlink: {}", source))] #[error("Can't delete symlink: {}", source)]
CantDeleteSymlink { source: std::io::Error }, CantDeleteSymlink { source: std::io::Error },
#[snafu(display("Requested alias {} not found", requested_alias))] #[error("Requested alias {} not found", requested_alias)]
AliasNotFound { requested_alias: String }, AliasNotFound { requested_alias: String },
} }

67
src/commands/uninstall.rs

@ -8,8 +8,8 @@ use crate::version::Version;
use crate::version_files::get_user_version_for_directory; use crate::version_files::get_user_version_for_directory;
use colored::Colorize; use colored::Colorize;
use log::debug; use log::debug;
use snafu::{ensure, OptionExt, ResultExt, Snafu};
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub struct Uninstall { pub struct Uninstall {
@ -20,49 +20,48 @@ impl Command for Uninstall {
type Error = Error; type Error = Error;
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let all_versions = let all_versions = installed_versions::list(config.installations_dir())
installed_versions::list(config.installations_dir()).context(VersionListingError)?; .map_err(|source| Error::VersionListingError { source })?;
let requested_version = self let requested_version = self
.version .version
.or_else(|| { .or_else(|| {
let current_dir = std::env::current_dir().unwrap(); let current_dir = std::env::current_dir().unwrap();
get_user_version_for_directory(current_dir, config) get_user_version_for_directory(current_dir, config)
}) })
.context(CantInferVersion)?; .ok_or(Error::CantInferVersion)?;
ensure!( if matches!(requested_version, UserVersion::Full(Version::Bypassed)) {
!matches!(requested_version, UserVersion::Full(Version::Bypassed)), return Err(Error::CantUninstallSystemVersion);
CantUninstallSystemVersion }
);
let available_versions: Vec<&Version> = all_versions let available_versions: Vec<&Version> = all_versions
.iter() .iter()
.filter(|v| requested_version.matches(v, config)) .filter(|v| requested_version.matches(v, config))
.collect(); .collect();
ensure!( if available_versions.len() >= 2 {
available_versions.len() < 2, return Err(Error::PleaseBeMoreSpecificToDelete {
PleaseBeMoreSpecificToDelete {
matched_versions: available_versions matched_versions: available_versions
.iter() .iter()
.map(|x| x.v_str()) .map(std::string::ToString::to_string)
.collect::<Vec<_>>() .collect(),
} });
); }
let version = requested_version let version = requested_version
.to_version(&all_versions, config) .to_version(&all_versions, config)
.context(CantFindVersion)?; .ok_or(Error::CantFindVersion)?;
let matching_aliases = version.find_aliases(config).context(IoError)?; let matching_aliases = version.find_aliases(config)?;
let root_path = version let root_path = version
.root_path(config) .root_path(config)
.with_context(|| RootPathNotFound { .ok_or_else(|| Error::RootPathNotFound {
version: version.clone(), version: version.clone(),
})?; })?;
debug!("Removing Node version from {:?}", root_path); debug!("Removing Node version from {:?}", root_path);
std::fs::remove_dir_all(root_path).context(CantDeleteNodeVersion)?; std::fs::remove_dir_all(root_path)
.map_err(|source| Error::CantDeleteNodeVersion { source })?;
outln!( outln!(
config, config,
Info, Info,
@ -72,7 +71,8 @@ impl Command for Uninstall {
for alias in matching_aliases { for alias in matching_aliases {
debug!("Removing alias from {:?}", alias.path()); debug!("Removing alias from {:?}", alias.path());
remove_symlink_dir(alias.path()).context(CantDeleteSymlink)?; remove_symlink_dir(alias.path())
.map_err(|source| Error::CantDeleteSymlink { source })?;
outln!( outln!(
config, config,
Info, Info,
@ -85,26 +85,27 @@ impl Command for Uninstall {
} }
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display("Can't get locally installed versions: {}", source))] #[error("Can't get locally installed versions: {}", source)]
VersionListingError { source: installed_versions::Error }, VersionListingError { source: installed_versions::Error },
#[snafu(display( #[error("Can't find version in dotfiles. Please provide a version manually to the command.")]
"Can't find version in dotfiles. Please provide a version manually to the command."
))]
CantInferVersion, CantInferVersion,
#[snafu(display("Can't uninstall system version"))] #[error("Can't uninstall system version")]
CantUninstallSystemVersion, CantUninstallSystemVersion,
#[snafu(display("Too many versions had matched, please be more specific.\nFound {} matching versions, expected 1:\n{}", matched_versions.len(), matched_versions.iter().map(|v| format!("* {}", v)).collect::<Vec<_>>().join("\n")))] #[error("Too many versions had matched, please be more specific.\nFound {} matching versions, expected 1:\n{}", matched_versions.len(), matched_versions.iter().map(|v| format!("* {}", v)).collect::<Vec<_>>().join("\n"))]
PleaseBeMoreSpecificToDelete { matched_versions: Vec<String> }, PleaseBeMoreSpecificToDelete { matched_versions: Vec<String> },
#[snafu(display("Can't find a matching version"))] #[error("Can't find a matching version")]
CantFindVersion, CantFindVersion,
#[snafu(display("Root path not found for version {}", version))] #[error("Root path not found for version {}", version)]
RootPathNotFound { version: Version }, RootPathNotFound { version: Version },
#[snafu(display("io error: {}", source))] #[error("io error: {}", source)]
IoError { source: std::io::Error }, IoError {
#[snafu(display("Can't delete Node.js version: {}", source))] #[from]
source: std::io::Error,
},
#[error("Can't delete Node.js version: {}", source)]
CantDeleteNodeVersion { source: std::io::Error }, CantDeleteNodeVersion { source: std::io::Error },
#[snafu(display("Can't delete symlink: {}", source))] #[error("Can't delete symlink: {}", source)]
CantDeleteSymlink { source: std::io::Error }, CantDeleteSymlink { source: std::io::Error },
} }

59
src/commands/use.rs

@ -10,9 +10,9 @@ use crate::version::Version;
use crate::version_file_strategy::VersionFileStrategy; use crate::version_file_strategy::VersionFileStrategy;
use crate::{config::FnmConfig, user_version_reader::UserVersionReader}; use crate::{config::FnmConfig, user_version_reader::UserVersionReader};
use colored::Colorize; use colored::Colorize;
use snafu::{ensure, OptionExt, ResultExt, Snafu};
use std::path::Path; use std::path::Path;
use structopt::StructOpt; use structopt::StructOpt;
use thiserror::Error;
#[derive(StructOpt, Debug)] #[derive(StructOpt, Debug)]
pub struct Use { pub struct Use {
@ -31,11 +31,11 @@ impl Command for Use {
type Error = Error; type Error = Error;
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> { fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
let multishell_path = config.multishell_path().context(FnmEnvWasNotSourced)?; let multishell_path = config.multishell_path().ok_or(Error::FnmEnvWasNotSourced)?;
warn_if_multishell_path_not_in_path_env_var(multishell_path, config); warn_if_multishell_path_not_in_path_env_var(multishell_path, config);
let all_versions = let all_versions = installed_versions::list(config.installations_dir())
installed_versions::list(config.installations_dir()).context(VersionListingError)?; .map_err(|source| Error::VersionListingError { source })?;
let requested_version = self let requested_version = self
.version .version
.unwrap_or_else(|| { .unwrap_or_else(|| {
@ -47,7 +47,7 @@ impl Command for Use {
VersionFileStrategy::Local => InferVersionError::Local, VersionFileStrategy::Local => InferVersionError::Local,
VersionFileStrategy::Recursive => InferVersionError::Recursive, VersionFileStrategy::Recursive => InferVersionError::Recursive,
}) })
.context(CantInferVersion)?; .map_err(|source| Error::CantInferVersion { source })?;
let (message, version_path) = if let UserVersion::Full(Version::Bypassed) = let (message, version_path) = if let UserVersion::Full(Version::Bypassed) =
requested_version requested_version
@ -101,7 +101,8 @@ impl Command for Use {
})?; })?;
} }
replace_symlink(&version_path, multishell_path).context(SymlinkingCreationIssue)?; replace_symlink(&version_path, multishell_path)
.map_err(|source| Error::SymlinkingCreationIssue { source })?;
Ok(()) Ok(())
} }
@ -120,19 +121,18 @@ fn install_new_version(
config: &FnmConfig, config: &FnmConfig,
install_if_missing: bool, install_if_missing: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
ensure!( if !install_if_missing && !should_install_interactively(&requested_version) {
install_if_missing || should_install_interactively(&requested_version), return Err(Error::CantFindVersion {
CantFindVersion { version: requested_version,
version: requested_version });
} }
);
Install { Install {
version: Some(requested_version.clone()), version: Some(requested_version.clone()),
..Install::default() ..Install::default()
} }
.apply(config) .apply(config)
.context(InstallError)?; .map_err(|source| Error::InstallError { source })?;
Use { Use {
version: Some(UserVersionReader::Direct(requested_version)), version: Some(UserVersionReader::Direct(requested_version)),
@ -207,37 +207,36 @@ fn warn_if_multishell_path_not_in_path_env_var(
); );
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display("Can't create the symlink: {}", source))] #[error("Can't create the symlink: {}", source)]
SymlinkingCreationIssue { source: std::io::Error }, SymlinkingCreationIssue { source: std::io::Error },
#[snafu(display("Can't read the symlink: {}", source))] #[error(transparent)]
SymlinkReadFailed { source: std::io::Error },
#[snafu(display("{}", source))]
InstallError { source: <Install as Command>::Error }, InstallError { source: <Install as Command>::Error },
#[snafu(display("Can't get locally installed versions: {}", source))] #[error("Can't get locally installed versions: {}", source)]
VersionListingError { source: installed_versions::Error }, VersionListingError { source: installed_versions::Error },
#[snafu(display("Requested version {} is not currently installed", version))] #[error("Requested version {} is not currently installed", version)]
CantFindVersion { version: UserVersion }, CantFindVersion { version: UserVersion },
#[snafu(display("{}", source))] #[error(transparent)]
CantInferVersion { source: InferVersionError }, CantInferVersion {
#[snafu(display( #[from]
source: InferVersionError,
},
#[error(
"{}\n{}\n{}", "{}\n{}\n{}",
"We can't find the necessary environment variables to replace the Node version.", "We can't find the necessary environment variables to replace the Node version.",
"You should setup your shell profile to evaluate `fnm env`, see https://github.com/Schniz/fnm#shell-setup on how to do this", "You should setup your shell profile to evaluate `fnm env`, see https://github.com/Schniz/fnm#shell-setup on how to do this",
"Check out our documentation for more information: https://fnm.vercel.app" "Check out our documentation for more information: https://fnm.vercel.app"
))] )]
FnmEnvWasNotSourced, FnmEnvWasNotSourced,
#[snafu(display("Can't create the multishell directory: {}", path.display()))] #[error("Can't create the multishell directory: {}", path.display())]
MultishellDirectoryCreationIssue { path: std::path::PathBuf }, MultishellDirectoryCreationIssue { path: std::path::PathBuf },
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum InferVersionError { pub enum InferVersionError {
#[snafu(display( #[error("Can't find version in dotfiles. Please provide a version manually to the command.")]
"Can't find version in dotfiles. Please provide a version manually to the command."
))]
Local, Local,
#[snafu(display("Could not find any version to use. Maybe you don't have a default version set?\nTry running `fnm default <VERSION>` to set one,\nor create a .node-version file inside your project to declare a Node.js version."))] #[error("Could not find any version to use. Maybe you don't have a default version set?\nTry running `fnm default <VERSION>` to set one,\nor create a .node-version file inside your project to declare a Node.js version.")]
Recursive, Recursive,
} }

18
src/current_version.rs

@ -1,10 +1,11 @@
use thiserror::Error;
use crate::config::FnmConfig; use crate::config::FnmConfig;
use crate::system_version; use crate::system_version;
use crate::version::Version; use crate::version::Version;
use snafu::{OptionExt, ResultExt, Snafu};
pub fn current_version(config: &FnmConfig) -> Result<Option<Version>, Error> { pub fn current_version(config: &FnmConfig) -> Result<Option<Version>, Error> {
let multishell_path = config.multishell_path().context(EnvNotApplied)?; let multishell_path = config.multishell_path().ok_or(Error::EnvNotApplied)?;
if multishell_path.read_link().ok() == Some(system_version::path()) { if multishell_path.read_link().ok() == Some(system_version::path()) {
return Ok(Some(Version::Bypassed)); return Ok(Some(Version::Bypassed));
@ -19,20 +20,21 @@ pub fn current_version(config: &FnmConfig) -> Result<Option<Version>, Error> {
.expect("Can't get filename") .expect("Can't get filename")
.to_str() .to_str()
.expect("Invalid OS string"); .expect("Invalid OS string");
let version = Version::parse(file_name).context(VersionError { version: file_name })?; let version = Version::parse(file_name).map_err(|source| Error::VersionError {
source,
version: file_name.to_string(),
})?;
Ok(Some(version)) Ok(Some(version))
} else { } else {
Ok(None) Ok(None)
} }
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[snafu(display( #[error("`fnm env` was not applied in this context.\nCan't find fnm's environment variables")]
"`fnm env` was not applied in this context.\nCan't find fnm's environment variables"
))]
EnvNotApplied, EnvNotApplied,
#[snafu(display("Can't read the version as a valid semver"))] #[error("Can't read the version as a valid semver")]
VersionError { VersionError {
source: semver::Error, source: semver::Error,
version: String, version: String,

59
src/downloader.rs

@ -4,38 +4,34 @@ use crate::archive::{Error as ExtractError, Extract};
use crate::directory_portal::DirectoryPortal; use crate::directory_portal::DirectoryPortal;
use crate::version::Version; use crate::version::Version;
use log::debug; use log::debug;
use snafu::{ensure, OptionExt, ResultExt, Snafu};
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use thiserror::Error;
use url::Url; use url::Url;
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[error(transparent)]
HttpError { HttpError {
#[from]
source: crate::http::Error, source: crate::http::Error,
}, },
#[error(transparent)]
IoError { IoError {
#[from]
source: std::io::Error, source: std::io::Error,
}, },
#[snafu(display("Can't extract the file: {}", source))] #[error("Can't extract the file: {}", source)]
CantExtractFile { CantExtractFile {
#[from]
source: ExtractError, source: ExtractError,
}, },
#[snafu(display("The downloaded archive is empty"))] #[error("The downloaded archive is empty")]
TarIsEmpty, TarIsEmpty,
#[snafu(display( #[error("{} for {} not found upstream.\nYou can `fnm ls-remote` to see available versions or try a different `--arch`.", version, arch)]
"{} for {} not found upstream.\nYou can `fnm ls-remote` to see available versions or try a different `--arch`.", VersionNotFound { version: Version, arch: Arch },
version, #[error("Version already installed at {:?}", path)]
arch VersionAlreadyInstalled { path: PathBuf },
))]
VersionNotFound {
version: Version,
arch: Arch,
},
#[snafu(display("Version already installed at {:?}", path))]
VersionAlreadyInstalled {
path: PathBuf,
},
} }
#[cfg(unix)] #[cfg(unix)]
@ -75,7 +71,7 @@ pub fn extract_archive_into<P: AsRef<Path>>(
let extractor = archive::TarXz::new(response); let extractor = archive::TarXz::new(response);
#[cfg(windows)] #[cfg(windows)]
let extractor = archive::Zip::new(response); let extractor = archive::Zip::new(response);
extractor.extract_into(path).context(CantExtractFile)?; extractor.extract_into(path)?;
Ok(()) Ok(())
} }
@ -88,23 +84,22 @@ pub fn install_node_dist<P: AsRef<Path>>(
) -> Result<(), Error> { ) -> Result<(), Error> {
let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str()); let installation_dir = PathBuf::from(installations_dir.as_ref()).join(version.v_str());
ensure!( if installation_dir.exists() {
!installation_dir.exists(), return Err(Error::VersionAlreadyInstalled {
VersionAlreadyInstalled { path: installation_dir,
path: installation_dir });
} }
);
std::fs::create_dir_all(installations_dir.as_ref()).context(IoError)?; std::fs::create_dir_all(installations_dir.as_ref())?;
let temp_installations_dir = installations_dir.as_ref().join(".downloads"); let temp_installations_dir = installations_dir.as_ref().join(".downloads");
std::fs::create_dir_all(&temp_installations_dir).context(IoError)?; std::fs::create_dir_all(&temp_installations_dir)?;
let portal = DirectoryPortal::new_in(&temp_installations_dir, installation_dir); let portal = DirectoryPortal::new_in(&temp_installations_dir, installation_dir);
let url = download_url(node_dist_mirror, version, arch); let url = download_url(node_dist_mirror, version, arch);
debug!("Going to call for {}", &url); debug!("Going to call for {}", &url);
let response = crate::http::get(url.as_str()).context(HttpError)?; let response = crate::http::get(url.as_str())?;
if response.status() == 404 { if response.status() == 404 {
return Err(Error::VersionNotFound { return Err(Error::VersionNotFound {
@ -117,17 +112,15 @@ pub fn install_node_dist<P: AsRef<Path>>(
extract_archive_into(&portal, response)?; extract_archive_into(&portal, response)?;
debug!("Extraction completed"); debug!("Extraction completed");
let installed_directory = std::fs::read_dir(&portal) let installed_directory = std::fs::read_dir(&portal)?
.context(IoError)?
.next() .next()
.context(TarIsEmpty)? .ok_or(Error::TarIsEmpty)??;
.context(IoError)?;
let installed_directory = installed_directory.path(); let installed_directory = installed_directory.path();
let renamed_installation_dir = portal.join("installation"); let renamed_installation_dir = portal.join("installation");
std::fs::rename(installed_directory, renamed_installation_dir).context(IoError)?; std::fs::rename(installed_directory, renamed_installation_dir)?;
portal.teleport().context(IoError)?; portal.teleport()?;
Ok(()) Ok(())
} }

28
src/installed_versions.rs

@ -1,11 +1,11 @@
use crate::version::Version; use crate::version::Version;
use snafu::{ResultExt, Snafu};
use std::path::Path; use std::path::Path;
use thiserror::Error;
pub fn list<P: AsRef<Path>>(installations_dir: P) -> Result<Vec<Version>, Error> { pub fn list<P: AsRef<Path>>(installations_dir: P) -> Result<Vec<Version>, Error> {
let mut vec = vec![]; let mut vec = vec![];
for result_entry in installations_dir.as_ref().read_dir().context(IoError)? { for result_entry in installations_dir.as_ref().read_dir()? {
let entry = result_entry.context(IoError)?; let entry = result_entry?;
if entry if entry
.file_name() .file_name()
.to_str() .to_str()
@ -17,19 +17,25 @@ pub fn list<P: AsRef<Path>>(installations_dir: P) -> Result<Vec<Version>, Error>
let path = entry.path(); let path = entry.path();
let filename = path let filename = path
.file_name() .file_name()
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound)) .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound))?
.context(IoError)?
.to_str() .to_str()
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound)) .ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound))?;
.context(IoError)?; let version = Version::parse(filename)?;
let version = Version::parse(filename).context(SemverError)?;
vec.push(version); vec.push(version);
} }
Ok(vec) Ok(vec)
} }
#[derive(Debug, Snafu)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
IoError { source: std::io::Error }, #[error(transparent)]
SemverError { source: semver::Error }, IoError {
#[from]
source: std::io::Error,
},
#[error(transparent)]
SemverError {
#[from]
source: semver::Error,
},
} }

Loading…
Cancel
Save