From ba73786a3803b358ace198557f85b742fcf56a82 Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Wed, 10 Feb 2021 01:13:27 +0300 Subject: [PATCH] feat(cli): add error struct --- migra-cli/src/config.rs | 28 +++++------ migra-cli/src/error.rs | 101 ++++++++++++++++++++++++++++++++++++++++ migra-cli/src/main.rs | 50 ++++++++++++++------ 3 files changed, 151 insertions(+), 28 deletions(-) create mode 100644 migra-cli/src/error.rs diff --git a/migra-cli/src/config.rs b/migra-cli/src/config.rs index eb3ca16..42dfd13 100644 --- a/migra-cli/src/config.rs +++ b/migra-cli/src/config.rs @@ -1,3 +1,4 @@ +use crate::error::{Error, ErrorKind}; use crate::migration::Migration; use crate::path::PathBuilder; use serde::{Deserialize, Serialize}; @@ -118,21 +119,17 @@ impl Config { .build() } - pub fn database_connection(&self) -> String { + pub fn database_connection_string(&self) -> crate::error::Result { let connection = self .database .connection .clone() .unwrap_or_else(|| String::from(DEFAULT_DATABASE_CONNECTION_ENV)); if let Some(connection_env) = connection.strip_prefix("$") { - env::var(connection_env).unwrap_or_else(|_| { - panic!( - r#"You need to provide "{}" environment variable"#, - connection_env - ) - }) + env::var(connection_env) + .map_err(|e| Error::new(ErrorKind::MissedEnvVar(connection_env.to_string()), e)) } else { - connection + Ok(connection) } } @@ -143,11 +140,16 @@ impl Config { } pub fn migrations(&self) -> io::Result> { - let mut entries = self - .migration_dir_path() - .read_dir()? - .map(|res| res.map(|e| e.path())) - .collect::, io::Error>>()?; + let mut entries = match self.migration_dir_path().read_dir() { + Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()), + entries => entries? + .map(|res| res.map(|e| e.path())) + .collect::, io::Error>>()?, + }; + + if entries.is_empty() { + return Ok(vec![]); + } entries.sort(); diff --git a/migra-cli/src/error.rs b/migra-cli/src/error.rs new file mode 100644 index 0000000..c5c7811 --- /dev/null +++ b/migra-cli/src/error.rs @@ -0,0 +1,101 @@ +use std::error::Error as StdError; +use std::fmt; +use std::mem; +use std::result; + +pub type Result = result::Result; + +#[derive(Debug)] +pub struct Error { + repr: Repr, +} + +enum Repr { + Simple(ErrorKind), + Custom(Box), +} + +#[derive(Debug)] +struct Custom { + kind: ErrorKind, + error: Box, +} + +#[derive(Debug, Clone)] +pub enum ErrorKind { + MissedEnvVar(String), +} + +impl fmt::Display for ErrorKind { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + ErrorKind::MissedEnvVar(ref name) => { + write!(fmt, r#"Missed "{}" environment variable"#, name) + } + } + } +} + +impl PartialEq for ErrorKind { + fn eq(&self, other: &Self) -> bool { + mem::discriminant(self) == mem::discriminant(other) + } +} + +impl From for Error { + #[inline] + fn from(kind: ErrorKind) -> Error { + Error { + repr: Repr::Simple(kind), + } + } +} + +impl Error { + pub fn new(kind: ErrorKind, error: E) -> Error + where + E: Into>, + { + Self::_new(kind, error.into()) + } + + fn _new(kind: ErrorKind, error: Box) -> Error { + Error { + repr: Repr::Custom(Box::new(Custom { kind, error })), + } + } + + pub fn kind(&self) -> &ErrorKind { + match &self.repr { + Repr::Custom(ref c) => &c.kind, + Repr::Simple(kind) => &kind, + } + } +} + +impl fmt::Debug for Repr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Repr::Custom(ref c) => fmt::Debug::fmt(&c, fmt), + Repr::Simple(kind) => fmt.debug_tuple("Kind").field(&kind).finish(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.repr { + Repr::Custom(ref c) => c.error.fmt(fmt), + Repr::Simple(kind) => write!(fmt, "{}", kind), + } + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + match self.repr { + Repr::Simple(..) => None, + Repr::Custom(ref c) => c.error.source(), + } + } +} diff --git a/migra-cli/src/main.rs b/migra-cli/src/main.rs index 5fe5237..33189c7 100644 --- a/migra-cli/src/main.rs +++ b/migra-cli/src/main.rs @@ -2,16 +2,20 @@ mod config; mod database; +mod error; mod migration; mod opts; mod path; use chrono::Local; use config::Config; +use error::ErrorKind; use opts::{AppOpt, ApplyCommandOpt, Command, MakeCommandOpt, StructOpt}; use path::PathBuilder; use std::fs; +const EM_DASH: char = '—'; + fn main() -> Result<(), Box> { let opt = AppOpt::from_args(); @@ -22,7 +26,8 @@ fn main() -> Result<(), Box> { Command::Apply(ApplyCommandOpt { file_name }) => { let config = Config::read(opt.config)?; - let mut client = database::connect(&config.database_connection())?; + let database_connection_string = &config.database_connection_string()?; + let mut client = database::connect(database_connection_string)?; let file_path = PathBuilder::from(config.directory_path()) .append(file_name) @@ -81,18 +86,31 @@ fn main() -> Result<(), Box> { Command::List => { let config = Config::read(opt.config)?; - let mut client = database::connect(&config.database_connection())?; - let applied_migrations = database::applied_migrations(&mut client)?; + let applied_migrations = match config.database_connection_string() { + Ok(ref database_connection_string) => { + let mut client = database::connect(database_connection_string)?; + let applied_migrations = database::applied_migrations(&mut client)?; - println!("Applied migrations:"); - if applied_migrations.is_empty() { - println!("–") - } else { - applied_migrations - .iter() - .rev() - .for_each(|name| println!("{}", name)); - } + println!("Applied migrations:"); + if applied_migrations.is_empty() { + println!("{}", EM_DASH); + } else { + applied_migrations + .iter() + .rev() + .for_each(|name| println!("{}", name)); + } + + applied_migrations + } + Err(e) if *e.kind() == ErrorKind::MissedEnvVar(String::new()) => { + println!("{}", e.kind()); + println!("No connection to database"); + + Vec::new() + } + Err(e) => panic!(e), + }; println!(); @@ -103,7 +121,7 @@ fn main() -> Result<(), Box> { .collect::>(); println!("Pending migrations:"); if pending_migrations.is_empty() { - println!("–"); + println!("{}", EM_DASH); } else { pending_migrations.iter().for_each(|m| { println!("{}", m.name()); @@ -113,7 +131,8 @@ fn main() -> Result<(), Box> { Command::Upgrade => { let config = Config::read(opt.config)?; - let mut client = database::connect(&config.database_connection())?; + let database_connection_string = &config.database_connection_string()?; + let mut client = database::connect(database_connection_string)?; let applied_migrations = database::applied_migrations(&mut client)?; @@ -136,7 +155,8 @@ fn main() -> Result<(), Box> { Command::Downgrade => { let config = Config::read(opt.config)?; - let mut client = database::connect(&config.database_connection())?; + let database_connection_string = &config.database_connection_string()?; + let mut client = database::connect(database_connection_string)?; let applied_migrations = database::applied_migrations(&mut client)?; let migrations = config.migrations()?;