diff options
| author | Naz <ndpm13@ch-naseem.com> | 2025-08-03 10:08:45 +0100 |
|---|---|---|
| committer | Naz <ndpm13@ch-naseem.com> | 2025-08-03 10:08:45 +0100 |
| commit | 57ffe6ff51a8f3ccc41607f18dcf3c9039e85d94 (patch) | |
| tree | 7ab8e10bf2fce88ca3cdfb2a6b13c0717c638407 | |
| parent | 6b3f5d37fbce2e880a454e66d8fbd78269f1c867 (diff) | |
| parent | dc0ee6ce99a0480b7a6c228492936b16ceaf60cd (diff) | |
Merge pull request 'Add comprehensive error types' (#12) from feat/issue-5 into main
Reviewed-on: https://git.ch-naseem.com/ndpm13/zap-rs/pulls/12
| -rw-r--r-- | Cargo.lock | 25 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/downloader.rs | 30 | ||||
| -rw-r--r-- | src/error.rs | 63 | ||||
| -rw-r--r-- | src/index.rs | 24 | ||||
| -rw-r--r-- | src/lib.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 13 | ||||
| -rw-r--r-- | src/manager.rs | 21 | ||||
| -rw-r--r-- | src/paths.rs | 16 | ||||
| -rw-r--r-- | src/symlink.rs | 6 | ||||
| -rw-r--r-- | src/tui.rs | 9 |
11 files changed, 152 insertions, 58 deletions
@@ -209,6 +209,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -247,7 +267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1023,7 +1043,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1838,6 +1858,7 @@ name = "zap-rs" version = "0.1.0" dependencies = [ "clap", + "derive_more", "futures-util", "indicatif", "reqwest", @@ -5,6 +5,7 @@ edition = "2024" [dependencies] clap = { version = "4.5.41", features = ["derive"] } +derive_more = { version = "2.0.1", features = ["from"] } futures-util = "0.3.31" indicatif = { version = "0.18.0", features = ["improved_unicode"] } reqwest = { version = "0.12.22", features = ["blocking", "json", "stream"] } diff --git a/src/downloader.rs b/src/downloader.rs index 2196e25..0141424 100644 --- a/src/downloader.rs +++ b/src/downloader.rs @@ -2,7 +2,7 @@ use futures_util::StreamExt; use std::path::PathBuf; use tokio::{fs, io::AsyncWriteExt}; -use crate::{appimages_dir, make_progress_bar}; +use crate::{Error, Result, appimages_dir, make_progress_bar}; #[derive(Debug, Default)] pub struct Downloader {} @@ -11,32 +11,36 @@ impl Downloader { pub fn new() -> Self { Self {} } - pub fn prepare_path(&self, url: &str, executable: &str) -> PathBuf { + pub fn prepare_path(&self, url: &str, executable: &str) -> Result<PathBuf> { // Try to extract filename from URL or use default let filename = match url.split('/').next_back() { Some(name) => name.to_string(), None => format!("{executable}.AppImage"), }; - appimages_dir().join(filename) + Ok(appimages_dir()?.join(filename)) } - pub async fn download_with_progress( - &self, - url: &str, - path: &PathBuf, - ) -> Result<(), Box<dyn std::error::Error>> { - fs::create_dir_all(&appimages_dir()).await?; - - let resp = reqwest::get(&url.to_string()).await?; + pub async fn download_with_progress(&self, url: &str, path: &PathBuf) -> Result<()> { + fs::create_dir_all(&appimages_dir()?).await?; + + let resp = reqwest::get(&url.to_string()) + .await + .map_err(|source| Error::Download { + url: url.to_string(), + source, + })?; let total_size = resp.content_length().unwrap_or(0); - let bar = make_progress_bar(total_size); + let bar = make_progress_bar(total_size)?; let mut out = tokio::fs::File::create(&path).await?; // Stream download with progress updates let mut stream = resp.bytes_stream(); while let Some(chunk) = stream.next().await { - let chunk = chunk?; + let chunk = chunk.map_err(|source| Error::Download { + url: url.to_string(), + source, + })?; let len = chunk.len() as u64; out.write_all(&chunk).await?; bar.inc(len); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..b473469 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,63 @@ +use derive_more::From; + +pub type Result<T> = core::result::Result<T, Error>; + +#[derive(Debug, From)] +pub enum Error { + InvalidPath, + NotFound(String), + Download { + url: String, + source: reqwest::Error, + }, + + #[from] + Io(std::io::Error), + + #[from] + Json(serde_json::Error), + + #[from] + Http(reqwest::Error), + + #[from] + EnvVar(std::env::VarError), + + #[from] + IndicatifTemplate(indicatif::style::TemplateError), +} + +impl core::fmt::Display for Error { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::result::Result<(), std::fmt::Error> { + match self { + Error::Io(e) => match e.kind() { + std::io::ErrorKind::NotFound => write!(fmt, "File or directory not found"), + _ => write!(fmt, "IO error: {e}"), + }, + Error::NotFound(name) => write!(fmt, "Application '{name}' not found"), + Error::Json(e) => write!(fmt, "JSON error: {e}"), + Error::Http(e) => write!(fmt, "HTTP error: {e}"), + Error::EnvVar(e) => write!(fmt, "Environment variable error: {e}"), + Error::InvalidPath => write!(fmt, "Invalid path provided"), + Error::IndicatifTemplate(e) => write!(fmt, "Progress bar template error: {e}"), + Error::Download { url, source } => { + if source.is_timeout() { + write!(fmt, "Download timed out from: {url}") + } else if source.is_connect() { + write!(fmt, "Failed to connect to: {url}") + } else if let Some(status) = source.status() { + match status.as_u16() { + 404 => write!(fmt, "AppImage not found at: {url}"), + 403 => write!(fmt, "Access denied at: {url}"), + 500..=599 => write!(fmt, "Server error when downloading from: {url}"), + _ => write!(fmt, "HTTP {status} error downloading from: {url}"), + } + } else { + write!(fmt, "Failed to download from {url}: {source}") + } + } + } + } +} + +impl std::error::Error for Error {} diff --git a/src/index.rs b/src/index.rs index 069068a..8f2521a 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1,6 +1,6 @@ use tokio::fs; -use crate::{AppImage, index_dir}; +use crate::{AppImage, Result, index_dir}; #[derive(Debug, Default)] pub struct Index {} @@ -9,32 +9,28 @@ impl Index { pub fn new() -> Self { Self {} } - pub async fn get(&self, appname: &str) -> Result<AppImage, Box<dyn std::error::Error>> { - let index_file_path = index_dir().join(format!("{appname}.json")); + pub async fn get(&self, appname: &str) -> Result<AppImage> { + let index_file_path = index_dir()?.join(format!("{appname}.json")); let index_file_content = fs::read_to_string(&index_file_path).await?; let appimage: AppImage = serde_json::from_str(&index_file_content)?; Ok(appimage) } - pub fn exists(&self, executable: &str) -> bool { - index_dir().join(format!("{}.json", &executable)).exists() + pub fn exists(&self, executable: &str) -> Result<bool> { + Ok(index_dir()?.join(format!("{}.json", &executable)).exists()) } - pub async fn add( - &self, - appimage: &AppImage, - appname: &str, - ) -> Result<(), Box<dyn std::error::Error>> { - fs::create_dir_all(&index_dir()).await?; + pub async fn add(&self, appimage: &AppImage, appname: &str) -> Result<()> { + fs::create_dir_all(&index_dir()?).await?; - let index_file = &index_dir().join(format!("{appname}.json")); + let index_file = &index_dir()?.join(format!("{appname}.json")); let json = serde_json::to_string_pretty(appimage)?; fs::write(index_file, json).await?; Ok(()) } - pub async fn remove(&self, appname: &str) -> Result<(), Box<dyn std::error::Error>> { - let index_file_path = index_dir().join(format!("{appname}.json")); + pub async fn remove(&self, appname: &str) -> Result<()> { + let index_file_path = index_dir()?.join(format!("{appname}.json")); fs::remove_file(index_file_path).await?; Ok(()) @@ -6,6 +6,7 @@ mod manager; mod paths; mod symlink; mod tui; +mod error; pub use crate::appimage::*; pub use crate::args::*; @@ -15,3 +16,4 @@ pub use crate::manager::*; pub use crate::paths::*; pub use crate::symlink::*; pub use crate::tui::*; +pub use crate::error::*; diff --git a/src/main.rs b/src/main.rs index 1d9505b..7840fae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,9 @@ use std::path::PathBuf; use clap::Parser; -use zap_rs::{AppImage, Cli, Command, PackageManager, Source, SourceMetadata}; +use zap_rs::{AppImage, Cli, Command, PackageManager, Result, Source, SourceMetadata}; -#[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error>> { +async fn run() -> Result<()> { let args = Cli::parse(); let pm = PackageManager::new(); @@ -32,3 +31,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { Ok(()) } + +#[tokio::main] +async fn main() { + if let Err(e) = run().await { + eprintln!("Error: {e}"); + std::process::exit(1); + } +} diff --git a/src/manager.rs b/src/manager.rs index 66ed6f6..6f3cfac 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -1,6 +1,6 @@ use tokio::fs; -use crate::{AppImage, Downloader, Index, SymlinkManager, index_dir}; +use crate::{AppImage, Downloader, Index, Result, SymlinkManager, index_dir}; #[derive(Debug, Default)] pub struct PackageManager { @@ -17,18 +17,15 @@ impl PackageManager { symlink_manager: SymlinkManager::new(), } } - pub async fn install( - &self, - appimage: &mut AppImage, - appname: &str, - ) -> Result<(), Box<dyn std::error::Error>> { - if self.index.exists(&appimage.executable) { - return Err(format!("{} is already installed.", &appimage.executable).into()); + pub async fn install(&self, appimage: &mut AppImage, appname: &str) -> Result<()> { + if self.index.exists(&appimage.executable)? { + println!("{} is already installed.", appimage.executable); + return Ok(()); } appimage.file_path = self .downloader - .prepare_path(&appimage.source.meta.url, &appimage.executable); + .prepare_path(&appimage.source.meta.url, &appimage.executable)?; self.downloader .download_with_progress(&appimage.source.meta.url, &appimage.file_path) .await?; @@ -37,7 +34,7 @@ impl PackageManager { self.symlink_manager.create(appimage).await?; Ok(()) } - pub async fn remove(&self, appname: &str) -> Result<(), Box<dyn std::error::Error>> { + pub async fn remove(&self, appname: &str) -> Result<()> { let appimage = self.index.get(appname).await?; fs::remove_file(&appimage.file_path).await?; @@ -46,8 +43,8 @@ impl PackageManager { Ok(()) } - pub async fn list(&self) -> Result<(), Box<dyn std::error::Error>> { - let mut appimages = fs::read_dir(index_dir()).await?; + pub async fn list(&self) -> Result<()> { + let mut appimages = fs::read_dir(index_dir()?).await?; while let Some(appimage) = appimages.next_entry().await? { if let Some(stem) = appimage.path().file_stem().and_then(|s| s.to_str()) { diff --git a/src/paths.rs b/src/paths.rs index 172cae6..7bbc8f8 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -1,14 +1,16 @@ use std::path::PathBuf; -pub fn zap_rs_home() -> PathBuf { - let home = std::env::var("HOME").expect("HOME not set"); - PathBuf::from(home).join(".local/share/zap-rs") +use crate::Result; + +pub fn zap_rs_home() -> Result<PathBuf> { + let home = std::env::var("HOME")?; + Ok(PathBuf::from(home).join(".local/share/zap-rs")) } -pub fn index_dir() -> PathBuf { - zap_rs_home().join("index") +pub fn index_dir() -> Result<PathBuf> { + Ok(zap_rs_home()?.join("index")) } -pub fn appimages_dir() -> PathBuf { - zap_rs_home().join("appimages") +pub fn appimages_dir() -> Result<PathBuf> { + Ok(zap_rs_home()?.join("appimages")) } diff --git a/src/symlink.rs b/src/symlink.rs index 843115c..68a804f 100644 --- a/src/symlink.rs +++ b/src/symlink.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use tokio::fs; -use crate::AppImage; +use crate::{AppImage, Result}; #[derive(Debug, Default)] pub struct SymlinkManager {} @@ -10,7 +10,7 @@ impl SymlinkManager { pub fn new() -> Self { Self {} } - pub async fn remove(&self, executable: &str) -> Result<(), Box<dyn std::error::Error>> { + pub async fn remove(&self, executable: &str) -> Result<()> { let home = std::env::var("HOME")?; let symlink_path = PathBuf::from(home).join(".local/bin").join(executable); @@ -18,7 +18,7 @@ impl SymlinkManager { Ok(()) } - pub async fn create(&self, appimage: &AppImage) -> Result<(), Box<dyn std::error::Error>> { + pub async fn create(&self, appimage: &AppImage) -> Result<()> { let home = std::env::var("HOME")?; let local_bin = PathBuf::from(home).join(".local/bin"); @@ -1,14 +1,15 @@ use indicatif::{ProgressBar, ProgressStyle}; -pub fn make_progress_bar(size: u64) -> ProgressBar { +use crate::Result; + +pub fn make_progress_bar(size: u64) -> Result<ProgressBar> { let bar = ProgressBar::new(size); bar.set_style( ProgressStyle::with_template( "{elapsed_precise:.white.dim} {wide_bar:.cyan} {bytes}/{total_bytes} ({bytes_per_sec}, {eta})", - ) - .unwrap() + )? .progress_chars("█▉▊▋▌▍▎▏ "), ); - bar + Ok(bar) } |
