Skip to content

Commit

Permalink
refactor: unified error type
Browse files Browse the repository at this point in the history
  • Loading branch information
benpueschel committed Jul 7, 2024
1 parent fb49b12 commit 9c278d7
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 183 deletions.
128 changes: 24 additions & 104 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use core::fmt;
use std::{collections::HashMap, env, error::Error, fmt::Display, fs, path::Path};
use crate::error::{Error, Result};
use std::{collections::HashMap, env, fs, path::Path};

#[cfg(feature = "keyring")]
use keyring::Entry;
Expand All @@ -10,63 +10,6 @@ use crate::{
remote::{Auth, CloneProtocol, Provider, RemoteConfig},
};

pub type Result<T> = std::result::Result<T, ConfigError>;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ConfigError {
pub message: String,
pub kind: ConfigErrorKind,
}
impl Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl Error for ConfigError {}
impl From<std::io::Error> for ConfigError {
fn from(value: std::io::Error) -> Self {
Self {
message: value.to_string(),
kind: ConfigErrorKind::ConfigParseError,
}
}
}
impl From<toml::de::Error> for ConfigError {
fn from(value: toml::de::Error) -> Self {
Self {
message: value.message().to_string(),
kind: ConfigErrorKind::ConfigParseError,
}
}
}
impl From<toml::ser::Error> for ConfigError {
fn from(value: toml::ser::Error) -> Self {
Self {
message: value.to_string(),
kind: ConfigErrorKind::ConfigParseError,
}
}
}
#[cfg(feature = "keyring")]
impl From<keyring::Error> for ConfigError {
fn from(value: keyring::Error) -> Self {
Self {
message: value.to_string(),
kind: ConfigErrorKind::KeyringError,
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ConfigErrorKind {
ConfigNotFound,
ConfigParseError,
RemoteNotFound,
AuthNotFound,
SecretsFileNotFound,
#[cfg(feature = "keyring")]
KeyringError,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Config {
/// A list of remotes.
Expand Down Expand Up @@ -130,12 +73,9 @@ impl Config {
config_path
} else {
if !Path::new(&fallback).exists() {
return Err(ConfigError {
message: format!(
"Could not find config at '{config_path}' or '{fallback}'."
),
kind: ConfigErrorKind::ConfigNotFound,
});
return Err(Error::not_found(format!(
"Could not find config at '{config_path}' or '{fallback}'."
)));
}
fallback
}
Expand All @@ -151,10 +91,7 @@ impl Config {
if let Some(remote) = self.remotes.get(name) {
return Ok(remote.provider.clone());
}
Err(ConfigError {
message: format!("Could not find remote '{name}'"),
kind: ConfigErrorKind::RemoteNotFound,
})
Err(Error::not_found(format!("Could not find remote '{name}'")))
}
pub fn get_remote_config(&self, name: &str) -> Result<RemoteConfig> {
if let Some(remote) = self.remotes.get(name) {
Expand All @@ -165,17 +102,11 @@ impl Config {
auth: self.get_auth(&self.secrets, name)?,
});
}
Err(ConfigError {
message: format!("Could not find remote '{name}'"),
kind: ConfigErrorKind::RemoteNotFound,
})
Err(Error::not_found(format!("Could not find remote '{name}'")))
}
pub fn store_token(&mut self, name: &str, token: &str) -> Result<()> {
if !self.remotes.contains_key(name) {
return Err(ConfigError {
message: format!("Could not find remote '{name}'"),
kind: ConfigErrorKind::RemoteNotFound,
});
return Err(Error::not_found(format!("Could not find remote '{name}'")));
}

match &mut self.secrets {
Expand Down Expand Up @@ -236,24 +167,19 @@ impl Config {
password: auth.password.clone().unwrap_or_default(),
});
}
return Err(ConfigError {
message: format!(
r#"Could not find auth for remote '{name}'.
Did you forget to add it to the config?
You need to set either a username/password combination,
or an api token. "#
),
kind: ConfigErrorKind::AuthNotFound,
});
return Err(Error::authentication(format!(
r#"Could not find auth for remote '{name}'.
Did you forget to add it to the config?
You need to set either a username/password combination, or an api token. "#
)));
}
}
Secrets::SecretsFile(file) => {
let file = file.replace('~', env::var("HOME").unwrap().as_str());
if !Path::new(&file).exists() {
return Err(ConfigError {
message: format!("Could not find secrets file '{file}'."),
kind: ConfigErrorKind::SecretsFileNotFound,
});
return Err(Error::not_found(format!(
"Could not find secrets file '{file}'."
)));
}
let contents = fs::read_to_string(file)?;
let secrets: InlineSecrets = toml::from_str(&contents)?;
Expand All @@ -267,22 +193,16 @@ impl Config {
if let Ok(token) = entry.get_password() {
return Ok(Auth::Token { token });
}
return Err(ConfigError {
message: format!(
r#"Could not find auth for remote '{name}'.
Did you forget to add it to the keyring?"#
),
kind: ConfigErrorKind::AuthNotFound,
});
return Err(Error::authentication(format!(
r#"Could not find auth for remote '{name}'.
Did you forget to add it to the keyring?"#
)));
}
}
Err(ConfigError {
message: format!(
r#"Could not find auth for remote '{name}'.
Did you forget to add it to the config?"#
),
kind: ConfigErrorKind::AuthNotFound,
})
Err(Error::authentication(format!(
r#"Could not find auth for remote '{name}'.
Did you forget to add it to the config?"#
)))
}
}

Expand Down
117 changes: 117 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use std::fmt::{self, Display};

use serde::{Deserialize, Serialize};
use tokio::io;

pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Error {
pub message: String,
pub kind: ErrorKind,
pub status: Option<u16>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ErrorKind {
NotFound,
Serialization,
Deserialization,
Authentication,
Other,
}

impl Error {
pub fn not_found(message: impl Display) -> Self {
Self {
message: message.to_string(),
kind: ErrorKind::NotFound,
status: None,
}
}
pub fn serialization(message: impl Display) -> Self {
Self {
message: message.to_string(),
kind: ErrorKind::Serialization,
status: None,
}
}
pub fn deserialization(message: impl Display) -> Self {
Self {
message: message.to_string(),
kind: ErrorKind::Deserialization,
status: None,
}
}
pub fn authentication(message: impl Display) -> Self {
Self {
message: message.to_string(),
kind: ErrorKind::Authentication,
status: None,
}
}
pub fn other(message: impl Display) -> Self {
Self {
message: message.to_string(),
kind: ErrorKind::Other,
status: None,
}
}
}

impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for Error {}

impl From<std::io::Error> for Error {
fn from(value: std::io::Error) -> Self {
let kind = match value.kind() {
io::ErrorKind::NotFound => ErrorKind::NotFound,
_ => ErrorKind::Other,
};
Self {
message: format!("{value}"),
status: None,
kind,
}
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self {
message: format!("{value}"),
kind: ErrorKind::Deserialization,
status: None,
}
}
}
impl From<toml::de::Error> for Error {
fn from(value: toml::de::Error) -> Self {
Self {
message: value.message().to_string(),
kind: ErrorKind::Deserialization,
status: None,
}
}
}
impl From<toml::ser::Error> for Error {
fn from(value: toml::ser::Error) -> Self {
Self {
message: format!("{value}"),
kind: ErrorKind::Serialization,
status: None,
}
}
}
#[cfg(feature = "keyring")]
impl From<keyring::Error> for Error {
fn from(value: keyring::Error) -> Self {
Self {
message: format!("{value}"),
kind: ErrorKind::Authentication,
status: None,
}
}
}
17 changes: 8 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use std::{
error::Error,
io::{stdin, stdout, Write},
};
use std::io::{stdin, stdout, Write};

use config::{Config, ConfigErrorKind};
use config::Config;
use error::{ErrorKind, Result};
use remote::{create_remote, Remote, RepoCreateInfo};
use structopt::StructOpt;

pub mod config;
pub mod error;
pub mod log;
pub mod remote;

Expand Down Expand Up @@ -55,11 +54,11 @@ pub enum Args {
},
}

fn load_config() -> Result<Config, Box<dyn Error>> {
fn load_config() -> Result<Config> {
match Config::load_from_file(None) {
Ok(config) => Ok(config),
Err(err) => match err.kind {
ConfigErrorKind::ConfigNotFound => {
ErrorKind::NotFound => {
eprintln!("{}", err.message);
log::info("Creating default config...");
log::end_line();
Expand All @@ -74,15 +73,15 @@ fn load_config() -> Result<Config, Box<dyn Error>> {
}
}

async fn load_remote(remote_name: &str) -> Result<Box<dyn Remote>, Box<dyn Error>> {
async fn load_remote(remote_name: &str) -> Result<Box<dyn Remote>> {
let config = load_config()?;
let provider = config.get_remote_provider(remote_name)?;
let remote_config = config.get_remote_config(remote_name)?;
Ok(create_remote(&remote_config, provider).await)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
async fn main() -> Result<()> {
// Parse command line arguments
let command = Args::from_args();

Expand Down
Loading

0 comments on commit 9c278d7

Please sign in to comment.