mirror of https://github.com/Trivernis/nenv
Improve error handling by using miette everywhere
parent
bf750af4cf
commit
cb24de8a19
@ -1,84 +1,128 @@
|
|||||||
use std::io;
|
use miette::{Diagnostic, NamedSource, SourceSpan};
|
||||||
|
|
||||||
use miette::Diagnostic;
|
|
||||||
use semver::VersionReq;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{mapper::error::MapperError, repository::extract::ExtractError};
|
||||||
mapper::error::MapperError,
|
|
||||||
repository::{config::ConfigError, extract::ExtractError},
|
|
||||||
web_api::error::ApiError,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) type LibResult<T> = Result<T>;
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[diagnostic(code(nenv::web))]
|
|
||||||
#[error("Failed to call nodejs.com api.")]
|
|
||||||
Web(
|
|
||||||
#[from]
|
|
||||||
#[source]
|
|
||||||
#[diagnostic_source]
|
|
||||||
ApiError,
|
|
||||||
),
|
|
||||||
|
|
||||||
#[diagnostic(code(nenv::extract))]
|
#[diagnostic(code(nenv::extract))]
|
||||||
#[error("The node archive could not be extracted")]
|
#[error("The node archive could not be extracted")]
|
||||||
Extract(
|
Extract(#[from] ExtractError),
|
||||||
#[from]
|
|
||||||
#[source]
|
|
||||||
#[diagnostic_source]
|
|
||||||
ExtractError,
|
|
||||||
),
|
|
||||||
|
|
||||||
#[diagnostic(code(nenv::config))]
|
|
||||||
#[error("The config file could not be loaded")]
|
|
||||||
Config(
|
|
||||||
#[from]
|
|
||||||
#[diagnostic_source]
|
|
||||||
ConfigError,
|
|
||||||
),
|
|
||||||
|
|
||||||
#[diagnostic(code(nenv::mapper))]
|
#[diagnostic(code(nenv::mapper))]
|
||||||
#[error("Mapping failed")]
|
#[error("Mapping failed")]
|
||||||
Mapper(
|
Mapper(#[from] MapperError),
|
||||||
#[from]
|
|
||||||
#[source]
|
|
||||||
#[diagnostic_source]
|
|
||||||
MapperError,
|
|
||||||
),
|
|
||||||
|
|
||||||
#[diagnostic(code(nenv::version))]
|
#[diagnostic(code(nenv::version))]
|
||||||
#[error("The passed version is invalid")]
|
#[error("The passed version is invalid")]
|
||||||
Version(
|
Version(#[from] VersionError),
|
||||||
#[from]
|
}
|
||||||
#[diagnostic_source]
|
|
||||||
VersionError,
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
),
|
#[error("{detail}")]
|
||||||
|
#[diagnostic(code(nenv::version), help("Make sure there's no typo in the version."))]
|
||||||
#[diagnostic(code(nenv::json))]
|
pub struct VersionError {
|
||||||
#[error("Failed to work with json")]
|
#[source_code]
|
||||||
Json(#[from] serde_json::Error),
|
src: String,
|
||||||
|
|
||||||
#[diagnostic(code(nenv::io))]
|
#[label("this version")]
|
||||||
#[error("Error during IO operation")]
|
pos: SourceSpan,
|
||||||
Io(#[from] io::Error),
|
|
||||||
|
detail: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionError {
|
||||||
|
pub fn new<S1: ToString, S2: ToString>(src: S1, detail: S2) -> Self {
|
||||||
|
let src = src.to_string();
|
||||||
|
let pos = (0, src.len()).into();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
src,
|
||||||
|
detail: detail.to_string(),
|
||||||
|
pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unknown_version<S: ToString>(src: S) -> Self {
|
||||||
|
Self::new(src, "unknown version")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unfulfillable_version<S: ToString>(src: S) -> Self {
|
||||||
|
Self::new(src, "the version requirement cannot be fulfilled")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_installed<S: ToString>(src: S) -> Self {
|
||||||
|
Self::new(src, "the version is not installed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum VersionError {
|
#[error("failed to parse json")]
|
||||||
#[error("Invalid version string `{0}`")]
|
#[diagnostic(code(nenv::json::deserialize))]
|
||||||
ParseVersion(#[source_code] String),
|
pub struct ParseJsonError {
|
||||||
|
#[source_code]
|
||||||
|
pub src: NamedSource,
|
||||||
|
|
||||||
#[error("Unknown Version `{0}`")]
|
#[label]
|
||||||
UnkownVersion(#[source_code] String),
|
pub pos: SourceSpan,
|
||||||
|
|
||||||
#[error("The version `{0}` is not installed")]
|
#[source]
|
||||||
NotInstalled(#[source_code] String),
|
pub caused_by: serde_json::Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
|
#[diagnostic(code(nenv::json::serialize))]
|
||||||
|
#[error("failed to serialize value to json string")]
|
||||||
|
pub struct SerializeJsonError {
|
||||||
|
#[from]
|
||||||
|
caused_by: serde_json::Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
|
#[diagnostic(code(nenv::toml::deserialize))]
|
||||||
|
#[error("failed to parse toml value")]
|
||||||
|
pub struct ParseTomlError {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
|
||||||
|
#[label]
|
||||||
|
pos: Option<SourceSpan>,
|
||||||
|
|
||||||
#[error("The version requirement `{0}` cannot be fulfilled")]
|
#[source]
|
||||||
Unfulfillable(VersionReq),
|
caused_by: toml::de::Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ParseTomlError {
|
||||||
|
pub fn new(file_name: &str, src: String, caused_by: toml::de::Error) -> Self {
|
||||||
|
let abs_pos = caused_by
|
||||||
|
.line_col()
|
||||||
|
.map(|(l, c)| {
|
||||||
|
src.lines()
|
||||||
|
.into_iter()
|
||||||
|
.take(l)
|
||||||
|
.map(|line| line.len() + 1)
|
||||||
|
.sum::<usize>()
|
||||||
|
+ c
|
||||||
|
})
|
||||||
|
.map(|p| SourceSpan::new(p.into(), 0.into()));
|
||||||
|
Self {
|
||||||
|
src: NamedSource::new(file_name, src),
|
||||||
|
pos: abs_pos.into(),
|
||||||
|
caused_by,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
|
#[diagnostic(code(nenv::toml::serialize))]
|
||||||
|
#[error("failed to serialize value to toml string")]
|
||||||
|
pub struct SerializeTomlError {
|
||||||
|
#[from]
|
||||||
|
caused_by: toml::ser::Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
|
#[diagnostic(code(nenv::http))]
|
||||||
|
#[error("http request failed")]
|
||||||
|
pub struct ReqwestError(#[from] reqwest::Error);
|
||||||
|
@ -1,120 +0,0 @@
|
|||||||
use std::ffi::OsString;
|
|
||||||
|
|
||||||
use consts::VERSION_FILE_PATH;
|
|
||||||
use crossterm::style::Stylize;
|
|
||||||
use mapper::Mapper;
|
|
||||||
use repository::{config::Config, NodeVersion, Repository};
|
|
||||||
|
|
||||||
mod consts;
|
|
||||||
pub mod error;
|
|
||||||
pub mod mapper;
|
|
||||||
pub mod repository;
|
|
||||||
mod utils;
|
|
||||||
mod web_api;
|
|
||||||
use dialoguer::Confirm;
|
|
||||||
use error::Result;
|
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use crate::error::VersionError;
|
|
||||||
|
|
||||||
pub async fn install_version(version: NodeVersion) -> Result<()> {
|
|
||||||
if VERSION_FILE_PATH.exists() {
|
|
||||||
fs::remove_file(&*VERSION_FILE_PATH).await?;
|
|
||||||
}
|
|
||||||
let repo = get_repository().await?;
|
|
||||||
|
|
||||||
if repo.is_installed(&version)? {
|
|
||||||
if !Confirm::new()
|
|
||||||
.with_prompt("The version {version} is already installed. Reinstall?")
|
|
||||||
.default(false)
|
|
||||||
.interact()
|
|
||||||
.unwrap()
|
|
||||||
{
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
repo.install_version(&version).await?;
|
|
||||||
println!("Installed {}", version.to_string().bold());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn set_default_version(version: NodeVersion) -> Result<()> {
|
|
||||||
let mut mapper = get_mapper().await?;
|
|
||||||
|
|
||||||
if !mapper.repository().is_installed(&version)?
|
|
||||||
&& Confirm::new()
|
|
||||||
.with_prompt(format!(
|
|
||||||
"The version {version} is not installed. Do you want to install it?"
|
|
||||||
))
|
|
||||||
.default(false)
|
|
||||||
.interact()
|
|
||||||
.unwrap()
|
|
||||||
{
|
|
||||||
mapper.repository().install_version(&version).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
mapper.set_default_version(&version).await?;
|
|
||||||
println!("Now using {}", version.to_string().bold());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub async fn exec(command: String, args: Vec<OsString>) -> Result<i32> {
|
|
||||||
let mapper = get_mapper().await?;
|
|
||||||
let active_version = mapper.active_version();
|
|
||||||
|
|
||||||
if !mapper.repository().is_installed(active_version)? {
|
|
||||||
mapper.repository().install_version(&active_version).await?;
|
|
||||||
}
|
|
||||||
let exit_status = mapper.exec(command, args).await?;
|
|
||||||
|
|
||||||
Ok(exit_status.code().unwrap_or(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn refresh() -> Result<()> {
|
|
||||||
get_mapper().await?.remap().await?;
|
|
||||||
fs::remove_file(&*VERSION_FILE_PATH).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_versions() -> Result<()> {
|
|
||||||
let mapper = get_mapper().await?;
|
|
||||||
let versions = mapper.repository().installed_versions().await?;
|
|
||||||
let active_version = mapper
|
|
||||||
.repository()
|
|
||||||
.lookup_version(mapper.active_version())?;
|
|
||||||
|
|
||||||
println!("{}", "Installed versions:".bold());
|
|
||||||
|
|
||||||
for version in versions {
|
|
||||||
let info = mapper
|
|
||||||
.repository()
|
|
||||||
.all_versions()
|
|
||||||
.get(&version)
|
|
||||||
.ok_or_else(|| VersionError::UnkownVersion(version.to_string()))?;
|
|
||||||
let lts = info
|
|
||||||
.lts
|
|
||||||
.as_ref()
|
|
||||||
.map(|l| format!(" ({})", l.to_owned().green()))
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
if version == active_version.version {
|
|
||||||
println!(" {}{} [current]", version.to_string().blue().bold(), lts)
|
|
||||||
} else {
|
|
||||||
println!(" {}{}", version.to_string().blue(), lts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_repository() -> Result<Repository> {
|
|
||||||
Repository::init(Config::load().await?).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_mapper() -> Result<Mapper> {
|
|
||||||
Ok(Mapper::load(get_repository().await?).await)
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
use std::io;
|
|
||||||
|
|
||||||
use miette::Diagnostic;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
pub type ApiResult<T> = Result<T, ApiError>;
|
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
|
||||||
pub enum ApiError {
|
|
||||||
#[error(transparent)]
|
|
||||||
Reqwest(#[from] reqwest::Error),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
Io(#[from] io::Error),
|
|
||||||
|
|
||||||
#[error("{0}")]
|
|
||||||
Other(#[help] String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApiError {
|
|
||||||
pub fn other<S: ToString>(error: S) -> Self {
|
|
||||||
Self::Other(error.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue