diff --git a/src/bin/timer.rs b/src/bin/timer.rs index 5918184..bb46afc 100644 --- a/src/bin/timer.rs +++ b/src/bin/timer.rs @@ -70,25 +70,32 @@ async fn main() { let json = matches.get_one::("json").unwrap(); let settings = Settings { project: project.to_string(), url: url.to_string(), json: *json }; - match matches.subcommand() { + let result = match matches.subcommand() { Some(("start", sub_matches)) => { let description = sub_matches.get_one::("DESCRIPTION"); - start(settings, description).await.unwrap(); + start(settings, description).await } Some(("stop", sub_matches)) => { let id = sub_matches.get_one::("ID"); - stop(settings, id).await.unwrap(); + stop(settings, id).await } Some(("edit", sub_matches)) => { let since = sub_matches.get_one::("since"); let until = sub_matches.get_one::("until"); let num = sub_matches.get_one::("NUM"); - edit(settings, since, until, num).await.unwrap(); + edit(settings, since, until, num).await } Some(("status", _)) => { - status(settings).await.unwrap(); + status(settings).await } - _ => cli().print_help().unwrap(), - } + _ => { + cli().print_help().unwrap(); + Ok(()) + } + }; + if let Err(e) = result { + eprintln!("{}", e); + std::process::exit(1); + } } diff --git a/src/commands.rs b/src/commands.rs index a62e964..eefc0c8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -11,6 +11,7 @@ use std::{ process::Command, }; use colored::*; +use crate::error::CliError; pub struct Settings { pub url: String, @@ -27,7 +28,7 @@ pub struct WorkPeriod { pub description: Option, } -pub async fn start(settings: Settings, description: Option<&String>) -> Result<(), reqwest::Error> { +pub async fn start(settings: Settings, description: Option<&String>) -> Result<(), CliError> { let mut map = HashMap::new(); map.insert("project", settings.project); if let Some(description) = description { @@ -40,12 +41,12 @@ pub async fn start(settings: Settings, description: Option<&String>) -> Result<( .await?; if !response.status().is_success() { - println!("{:?}", response.text().await); + return Err(CliError::Server(response.text().await?)); } Ok(()) } -pub async fn stop(settings: Settings, id: Option<&i32>) -> Result<(), reqwest::Error> { +pub async fn stop(settings: Settings, id: Option<&i32>) -> Result<(), CliError> { let mut map = HashMap::new(); map.insert("project", settings.project); if let Some(id) = id { @@ -59,7 +60,7 @@ pub async fn stop(settings: Settings, id: Option<&i32>) -> Result<(), reqwest::E .await?; if !response.status().is_success() { - println!("{:?}", response.text().await); + return Err(CliError::Server(response.text().await?)); } Ok(()) @@ -115,7 +116,7 @@ fn edit_periods(periods: Vec) -> Result, std::io::Er Ok(periods) } -pub async fn update_period(settings: &Settings, period: WorkPeriod) -> Result<(), reqwest::Error> { +pub async fn update_period(settings: &Settings, period: WorkPeriod) -> Result<(), CliError> { let client = Client::new(); let response = client.put(settings.url.to_string() + "/api/history/" + &period.id.unwrap().to_string()) .json(&period) @@ -123,12 +124,12 @@ pub async fn update_period(settings: &Settings, period: WorkPeriod) -> Result<() .await?; if !response.status().is_success() { - println!("{:?}", response.text().await); + return Err(CliError::Server(response.text().await?)); } Ok(()) } -pub async fn add_period(settings: &Settings, period: &mut WorkPeriod) -> Result<(), reqwest::Error> { +pub async fn add_period(settings: &Settings, period: &mut WorkPeriod) -> Result<(), CliError> { let client = Client::new(); period.id = None; let response = client.post(settings.url.to_string() + "/api/history") @@ -137,24 +138,24 @@ pub async fn add_period(settings: &Settings, period: &mut WorkPeriod) -> Result< .await?; if !response.status().is_success() { - println!("{:?}", response.text().await); + return Err(CliError::Server(response.text().await?)); } Ok(()) } -pub async fn delete_period(settings: &Settings, id: i32) -> Result<(), reqwest::Error> { +pub async fn delete_period(settings: &Settings, id: i32) -> Result<(), CliError> { let client = Client::new(); let response = client.delete(settings.url.to_string() + "/api/history/" + id.to_string().as_str()) .send() .await?; if !response.status().is_success() { - println!("{:?}", response.text().await); + return Err(CliError::Server(response.text().await?)); } Ok(()) } -pub async fn edit(settings: Settings, since: Option<&String>, until: Option<&String>, num: Option<&String>) -> Result<(), reqwest::Error> { +pub async fn edit(settings: Settings, since: Option<&String>, until: Option<&String>, num: Option<&String>) -> Result<(), CliError> { let mut params = vec![ ("project", settings.project.to_owned()), ]; @@ -175,7 +176,7 @@ pub async fn edit(settings: Settings, since: Option<&String>, until: Option<&Str .await?; if !response.status().is_success() { - println!("{:?}", response.text().await); + return Err(CliError::Server(response.text().await?)); } else { let body = response.text().await.unwrap(); let periods = parse_periods(body).unwrap(); @@ -234,13 +235,19 @@ fn format_bar(seconds: i64, max_value: i64, width: usize) -> String { format!("│{}", colored_bar) } -pub async fn status(settings: Settings) -> Result<(), reqwest::Error> { +pub async fn status(settings: Settings) -> Result<(), CliError> { // Get additional metrics from status endpoint let client = Client::new(); let status_response = client.get(settings.url.to_string() + "/") + .timeout(std::time::Duration::from_secs(10)) .send() .await?; - let status: Status = serde_json::from_str(&status_response.text().await?).unwrap(); + + if !status_response.status().is_success() { + return Err(CliError::Server(status_response.text().await?)); + } + + let status: Status = serde_json::from_str(&status_response.text().await?)?; // Compute current week boundaries (Monday to Sunday) let now = chrono::Local::now(); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..3fd58b1 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,44 @@ +use std::fmt; +use colored::*; + +#[derive(Debug)] +pub enum CliError { + Network(String), + Server(String), + Parse(String), + Other(String), +} + +impl std::error::Error for CliError {} + +impl fmt::Display for CliError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (label, message) = match self { + CliError::Network(msg) => ("Network Error".red(), msg), + CliError::Server(msg) => ("Server Error".red(), msg), + CliError::Parse(msg) => ("Parse Error".red(), msg), + CliError::Other(msg) => ("Error".red(), msg), + }; + write!(f, "{}: {}", label, message) + } +} + +impl From for CliError { + fn from(err: reqwest::Error) -> Self { + if err.is_connect() { + CliError::Network("Could not connect to server".to_string()) + } else if err.is_timeout() { + CliError::Network("Connection timed out".to_string()) + } else if err.is_status() { + CliError::Server(format!("Server returned error: {}", err.status().unwrap_or_default())) + } else { + CliError::Network(err.to_string()) + } + } +} + +impl From for CliError { + fn from(err: serde_json::Error) -> Self { + CliError::Parse(format!("Failed to parse server response: {}", err)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 82b6da3..feffe02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,2 @@ pub mod commands; +pub mod error;