One type of error should be enough

This commit is contained in:
Bodo Junglas
2020-02-02 14:57:27 +01:00
parent 6e268d2264
commit 7c034b79be
11 changed files with 46 additions and 103 deletions

View File

@@ -1,10 +1,9 @@
use super::OutputFormat;
use crate::error::AppError;
use acari_lib::{Account, Client, User};
use acari_lib::{AcariError, Account, Client, User};
use prettytable::{cell, format, row, table};
use serde_json::json;
pub fn check(client: &dyn Client, output_format: OutputFormat) -> Result<(), AppError> {
pub fn check(client: &dyn Client, output_format: OutputFormat) -> Result<(), AcariError> {
let account = client.get_account()?;
let user = client.get_myself()?;
@@ -47,7 +46,7 @@ fn print_pretty(account: Account, user: User) {
user_table.printstd();
}
fn print_json(account: Account, user: User) -> Result<(), AppError> {
fn print_json(account: Account, user: User) -> Result<(), AcariError> {
println!(
"{}",
serde_json::to_string_pretty(&json!({

View File

@@ -1,9 +1,8 @@
use super::OutputFormat;
use crate::error::AppError;
use acari_lib::{Client, Customer};
use acari_lib::{AcariError, Client, Customer};
use prettytable::{cell, format, row, Table};
pub fn customers(client: &dyn Client, output_format: OutputFormat) -> Result<(), AppError> {
pub fn customers(client: &dyn Client, output_format: OutputFormat) -> Result<(), AcariError> {
let mut customers = client.get_customers()?;
customers.sort_by(|c1, c2| c1.name.cmp(&c2.name));
@@ -28,7 +27,7 @@ fn print_pretty(customers: Vec<Customer>) {
customers_table.printstd();
}
fn print_json(customers: Vec<Customer>) -> Result<(), AppError> {
fn print_json(customers: Vec<Customer>) -> Result<(), AcariError> {
println!("{}", serde_json::to_string_pretty(&customers)?);
Ok(())

View File

@@ -1,11 +1,10 @@
use super::OutputFormat;
use crate::error::AppError;
use acari_lib::{Client, DateSpan, TimeEntry};
use acari_lib::{AcariError, Client, DateSpan, TimeEntry};
use chrono::NaiveDate;
use itertools::Itertools;
use prettytable::{cell, row, table};
pub fn entries(client: &dyn Client, output_format: OutputFormat, date_span: DateSpan) -> Result<(), AppError> {
pub fn entries(client: &dyn Client, output_format: OutputFormat, date_span: DateSpan) -> Result<(), AcariError> {
let mut time_entries = client.get_time_entries(date_span)?;
time_entries.sort_by(|t1, t2| t1.date_at.cmp(&t2.date_at));
@@ -33,7 +32,7 @@ fn print_pretty(entries: Vec<(&NaiveDate, Vec<&TimeEntry>)>) {
entries_table.printstd();
}
fn print_json(entries: Vec<TimeEntry>) -> Result<(), AppError> {
fn print_json(entries: Vec<TimeEntry>) -> Result<(), AcariError> {
println!("{}", serde_json::to_string_pretty(&entries)?);
Ok(())

View File

@@ -1,15 +1,15 @@
use crate::config::Config;
use crate::error::AppError;
use acari_lib::AcariError;
use std::io::{stdout, Write};
use text_io::try_read;
pub fn init() -> Result<(), AppError> {
pub fn init() -> Result<(), AcariError> {
print!("Mite domain: ");
stdout().flush()?;
let domain: String = try_read!("{}\n")?;
let domain: String = try_read!("{}\n").map_err(|e| AcariError::InternalError(format!("IO: {}", e)))?;
print!("API Token: ");
stdout().flush()?;
let token: String = try_read!("{}\n")?;
let token: String = try_read!("{}\n").map_err(|e| AcariError::InternalError(format!("IO: {}", e)))?;
Config {
domain,

View File

@@ -12,7 +12,7 @@ pub use init::*;
pub use projects::*;
pub use services::*;
use crate::error::AppError;
use acari_lib::AcariError;
pub enum OutputFormat {
Pretty,
@@ -21,12 +21,12 @@ pub enum OutputFormat {
}
impl OutputFormat {
pub fn from_string(format: &str) -> Result<OutputFormat, AppError> {
pub fn from_string(format: &str) -> Result<OutputFormat, AcariError> {
match format {
"pretty" => Ok(OutputFormat::Pretty),
"json" => Ok(OutputFormat::Json),
"flat" => Ok(OutputFormat::Flat),
format => Err(AppError::UserError(format!("Invalid output format: {}", format))),
format => Err(AcariError::UserError(format!("Invalid output format: {}", format))),
}
}
}

View File

@@ -1,10 +1,9 @@
use super::OutputFormat;
use crate::error::AppError;
use acari_lib::{Client, Project};
use acari_lib::{AcariError, Client, Project};
use itertools::Itertools;
use prettytable::{cell, format, row, Table};
pub fn all_projects(client: &dyn Client, output_format: OutputFormat) -> Result<(), AppError> {
pub fn all_projects(client: &dyn Client, output_format: OutputFormat) -> Result<(), AcariError> {
let mut projects = client.get_projects()?;
projects.sort_by(|p1, p2| p1.customer_name.cmp(&p2.customer_name));
@@ -36,7 +35,7 @@ fn print_pretty(projects: Vec<(&str, Vec<&Project>)>) {
projects_table.printstd();
}
fn print_json(projects: Vec<Project>) -> Result<(), AppError> {
fn print_json(projects: Vec<Project>) -> Result<(), AcariError> {
println!("{}", serde_json::to_string_pretty(&projects)?);
Ok(())

View File

@@ -1,10 +1,9 @@
use super::OutputFormat;
use crate::error::AppError;
use acari_lib::{Client, Service};
use acari_lib::{AcariError, Client, Service};
use itertools::Itertools;
use prettytable::{cell, row, table};
pub fn services(client: &dyn Client, output_format: OutputFormat) -> Result<(), AppError> {
pub fn services(client: &dyn Client, output_format: OutputFormat) -> Result<(), AcariError> {
let mut services = client.get_services()?;
services.sort_by(|s1, s2| s1.name.cmp(&s2.name));
@@ -28,7 +27,7 @@ fn print_pretty(services: Vec<Service>) {
service_table.printstd();
}
fn print_json(services: Vec<Service>) -> Result<(), AppError> {
fn print_json(services: Vec<Service>) -> Result<(), AcariError> {
println!("{}", serde_json::to_string_pretty(&services)?);
Ok(())

View File

@@ -1,5 +1,4 @@
use crate::error::AppError;
use acari_lib::{CachedClient, Client, StdClient};
use acari_lib::{AcariError, CachedClient, Client, StdClient};
use serde::{Deserialize, Serialize};
use std::fs::{self, File};
use std::io::{self, Read, Write};
@@ -15,20 +14,22 @@ pub struct Config {
}
impl Config {
pub fn read() -> Result<Option<Config>, AppError> {
pub fn read() -> Result<Option<Config>, AcariError> {
let config_file = config_file();
match File::open(&config_file) {
Ok(mut file) => {
let mut content = vec![];
file.read_to_end(&mut content)?;
Ok(Some(toml::from_slice::<Config>(&content)?))
Ok(Some(
toml::from_slice::<Config>(&content).map_err(|e| AcariError::InternalError(format!("Configration invalid: {}", e)))?,
))
}
Err(ref err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err.into()),
}
}
pub fn client(&self, cached: bool) -> Result<Box<dyn Client>, AppError> {
pub fn client(&self, cached: bool) -> Result<Box<dyn Client>, AcariError> {
if cached {
Ok(Box::new(CachedClient::new(
&self.domain,
@@ -40,11 +41,15 @@ impl Config {
}
}
pub fn write(&self) -> Result<(), AppError> {
let content = toml::to_string_pretty(self)?;
pub fn write(&self) -> Result<(), AcariError> {
let content = toml::to_string_pretty(self).map_err(|e| AcariError::InternalError(format!("Configration invalid: {}", e)))?;
let config_file = config_file();
fs::create_dir_all(&config_file.parent().ok_or_else(|| AppError::InternalError("Invalid config path".to_string()))?)?;
fs::create_dir_all(
&config_file
.parent()
.ok_or_else(|| AcariError::InternalError("Invalid config path".to_string()))?,
)?;
let mut file = File::create(&config_file)?;

View File

@@ -1,61 +0,0 @@
use std::error::Error;
use std::fmt;
use std::io;
#[derive(Debug)]
pub enum AppError {
IO(io::Error),
TextIO(text_io::Error),
TomlRead(toml::de::Error),
TomlWrite(toml::ser::Error),
Json(serde_json::Error),
AcariError(acari_lib::AcariError),
UserError(String),
InternalError(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::IO(err) => write!(f, "IO error: {}", err),
AppError::TextIO(err) => write!(f, "IO error: {}", err),
AppError::TomlRead(err) => write!(f, "Toml error: {}", err),
AppError::TomlWrite(err) => write!(f, "Toml error: {}", err),
AppError::Json(err) => write!(f, "Json error: {}", err),
AppError::AcariError(err) => write!(f, "Error: {}", err),
AppError::UserError(s) => write!(f, "User error: {}", s),
AppError::InternalError(s) => write!(f, "Internal error: {}", s),
}
}
}
impl Error for AppError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
AppError::IO(err) => Some(err),
AppError::TextIO(err) => Some(err),
AppError::TomlRead(err) => Some(err),
AppError::TomlWrite(err) => Some(err),
AppError::Json(err) => Some(err),
AppError::AcariError(err) => Some(err),
_ => None,
}
}
}
macro_rules! app_error_from {
($error: ty, $app_error: ident) => {
impl From<$error> for AppError {
fn from(err: $error) -> AppError {
AppError::$app_error(err)
}
}
};
}
app_error_from!(io::Error, IO);
app_error_from!(text_io::Error, TextIO);
app_error_from!(toml::de::Error, TomlRead);
app_error_from!(toml::ser::Error, TomlWrite);
app_error_from!(serde_json::Error, Json);
app_error_from!(acari_lib::AcariError, AcariError);

View File

@@ -1,15 +1,13 @@
use acari_lib::DateSpan;
use acari_lib::{AcariError, DateSpan};
use clap::{App, Arg, SubCommand};
mod commands;
mod config;
mod error;
use commands::OutputFormat;
use config::Config;
use error::AppError;
fn main() -> Result<(), AppError> {
fn main() -> Result<(), AcariError> {
let app = App::new("acarid")
.version("0.1")
.about("Commandline interface for mite")
@@ -49,15 +47,17 @@ fn main() -> Result<(), AppError> {
("projects", _) => commands::all_projects(client.as_ref(), output_format),
("services", _) => commands::services(client.as_ref(), output_format),
("entries", Some(sub_matches)) => {
let span_arg = sub_matches.value_of("span").ok_or(AppError::UserError("Missing <span> argument".to_string()))?;
let span_arg = sub_matches
.value_of("span")
.ok_or(AcariError::UserError("Missing <span> argument".to_string()))?;
commands::entries(client.as_ref(), output_format, DateSpan::from_string(span_arg)?)
}
(invalid, _) => Err(AppError::UserError(format!("Unknown command: {}", invalid))),
(invalid, _) => Err(AcariError::UserError(format!("Unknown command: {}", invalid))),
}
}
None => match matches.subcommand() {
("init", _) => commands::init(),
(_, _) => Err(AppError::UserError("Missing configuration, run init first".to_string())),
(_, _) => Err(AcariError::UserError("Missing configuration, run init first".to_string())),
},
}
}

View File

@@ -10,6 +10,8 @@ pub enum AcariError {
Request(reqwest::Error),
Json(serde_json::Error),
Mite(u16, String),
UserError(String),
InternalError(String),
}
impl fmt::Display for AcariError {
@@ -21,6 +23,8 @@ impl fmt::Display for AcariError {
AcariError::Request(err) => write!(f, "Request error: {}", err),
AcariError::Json(err) => write!(f, "Json error: {}", err),
AcariError::Mite(status, error) => write!(f, "Mite error ({}): {}", status, error),
AcariError::UserError(s) => write!(f, "User error: {}", s),
AcariError::InternalError(s) => write!(f, "Internal error: {}", s),
}
}
}