diff --git a/lib/core/conf/src/keypairs/cli.rs b/lib/core/conf/src/keypairs/cli.rs index 39ca0d3041641bc88e3cb09bcbe9a4fe8bdd2a1e..e1524a0cf611d5050b0ab25799a024cc004b911c 100644 --- a/lib/core/conf/src/keypairs/cli.rs +++ b/lib/core/conf/src/keypairs/cli.rs @@ -27,80 +27,63 @@ unused_qualifications )] +use crate::ui::ui_trait::*; +use crate::ui::UiError; use crate::*; -#[cfg(test)] -use mockall::*; -use std::io; - -#[cfg_attr(test, automock)] -trait UserPasswordInput { - fn get_password(&self, prompt: &str) -> std::io::Result<String>; -} - -impl UserPasswordInput for std::io::Stdin { - #[inline] - fn get_password(&self, prompt: &str) -> std::io::Result<String> { - Ok(rpassword::prompt_password_stdout(prompt)?) - } -} - -#[derive(Debug, Copy, Clone)] -/// Errors encountered by the user interaction -pub enum CliError { - /// Canceled - Canceled, - - /// Bad input - BadInput, -} - -impl From<std::io::Error> for CliError { - fn from(_e: std::io::Error) -> Self { - CliError::BadInput - } -} #[inline] /// Modify network keys command -pub fn modify_network_keys(key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> { - inner_modify_network_keys(std::io::stdin(), key_pairs) +pub fn modify_network_keys<UI: UserInteractionTrait>( + user_interaction: &UI, + key_pairs: DuniterKeyPairs, +) -> Result<DuniterKeyPairs, UiError> { + inner_modify_network_keys(user_interaction, key_pairs) } /// Private function to modify network keys -fn inner_modify_network_keys<T: UserPasswordInput>( - stdin: T, +fn inner_modify_network_keys<UI: UserInteractionTrait>( + user_interaction: &UI, mut key_pairs: DuniterKeyPairs, -) -> Result<DuniterKeyPairs, CliError> { - key_pairs.network_keypair = salt_password_prompt(stdin)?; +) -> Result<DuniterKeyPairs, UiError> { + key_pairs.network_keypair = salt_password_prompt(user_interaction)?; Ok(key_pairs) } #[inline] /// Modify member keys command -pub fn modify_member_keys(key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> { - inner_modify_member_keys(std::io::stdin(), key_pairs) +pub fn modify_member_keys<UI: UserInteractionTrait>( + user_interaction: &UI, + key_pairs: DuniterKeyPairs, +) -> Result<DuniterKeyPairs, UiError> { + inner_modify_member_keys(user_interaction, key_pairs) } /// Private function to modify network keys -fn inner_modify_member_keys<T: UserPasswordInput>( - stdin: T, +fn inner_modify_member_keys<UI: UserInteractionTrait>( + user_interaction: &UI, mut key_pairs: DuniterKeyPairs, -) -> Result<DuniterKeyPairs, CliError> { - key_pairs.member_keypair = Some(salt_password_prompt(stdin)?); +) -> Result<DuniterKeyPairs, UiError> { + key_pairs.member_keypair = Some(salt_password_prompt(user_interaction)?); Ok(key_pairs) } /// Ask user for confirmation and Clear keys command -pub fn clear_keys(network: bool, member: bool, mut key_pairs: DuniterKeyPairs) -> DuniterKeyPairs { +pub fn clear_keys<UI: UserInteractionTrait>( + user_interaction: &UI, + network: bool, + member: bool, + mut key_pairs: DuniterKeyPairs, +) -> DuniterKeyPairs { if network { - if let Ok("y") = question_prompt("Clear your network keypair?", &["y", "n"]) { - println!("Generating a new network keypair!"); + if let Ok(0) = user_interaction.question_prompt("Clear your network keypair?", &["y", "n"]) + { + user_interaction.message("Generating a new network keypair!"); clear_network_key(&mut key_pairs); } } if member { - if let Ok("y") = question_prompt("Clear your member keypair?", &["y", "n"]) { - println!("Deleting member keypair!"); + if let Ok(0) = user_interaction.question_prompt("Clear your member keypair?", &["y", "n"]) { + user_interaction.message("Deleting member keypair!"); clear_member_key(&mut key_pairs); } } @@ -120,23 +103,29 @@ fn clear_member_key(key_pairs: &mut DuniterKeyPairs) { } /// Show keys command -pub fn show_keys(key_pairs: DuniterKeyPairs) { - show_network_keys(&key_pairs); - show_member_keys(&key_pairs); +pub fn show_keys<UI: UserInteractionTrait>(user_interaction: &UI, key_pairs: DuniterKeyPairs) { + show_network_keys(user_interaction, &key_pairs); + show_member_keys(user_interaction, &key_pairs); } #[inline] /// Show network keys -pub fn show_network_keys(key_pairs: &DuniterKeyPairs) { - println!("Network key: {}", key_pairs.network_keypair); +pub fn show_network_keys<UI: UserInteractionTrait>( + user_interaction: &UI, + key_pairs: &DuniterKeyPairs, +) { + user_interaction.message(&format!("Network key: {}", key_pairs.network_keypair)); } #[inline] /// Show member keys -pub fn show_member_keys(key_pairs: &DuniterKeyPairs) { +pub fn show_member_keys<UI: UserInteractionTrait>( + user_interaction: &UI, + key_pairs: &DuniterKeyPairs, +) { match &key_pairs.member_keypair { - None => println!("No member key configured"), - Some(key) => println!("Member key: {}", key), + None => user_interaction.message("No member key configured"), + Some(key) => user_interaction.message(&format!("Member key: {}", key)), } } @@ -156,27 +145,12 @@ pub fn save_keypairs( super::write_keypairs_file(&conf_keys_path, &key_pairs) } -fn question_prompt<'a>(question: &str, answers: &[&'a str]) -> Result<&'a str, CliError> { - let mut buf = String::new(); - - println!("{} ({}):", question, answers.join("/")); - let res = io::stdin().read_line(&mut buf); - - if res.is_ok() { - answers - .iter() - .find(|x| **x == buf.trim()) - .copied() - .ok_or(CliError::Canceled) - } else { - Err(CliError::Canceled) - } -} - -fn salt_password_prompt<T: UserPasswordInput>(stdin: T) -> Result<KeyPairEnum, CliError> { - let salt = stdin.get_password("Salt: ")?; +fn salt_password_prompt<UI: UserInteractionTrait>( + user_interaction: &UI, +) -> Result<KeyPairEnum, UiError> { + let salt = user_interaction.get_password("Salt: ")?; if !salt.is_empty() { - let password = stdin.get_password("Password: ")?; + let password = user_interaction.get_password("Password: ")?; if !password.is_empty() { let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters(); let key_pairs = KeyPairEnum::Ed25519( @@ -184,25 +158,29 @@ fn salt_password_prompt<T: UserPasswordInput>(stdin: T) -> Result<KeyPairEnum, C ); Ok(key_pairs) } else { - Err(CliError::BadInput) + Err(UiError::BadInput) } } else { - Err(CliError::BadInput) + Err(UiError::BadInput) } } /// The wizard key function -pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> { - let mut answer = question_prompt("Modify your network keypair?", &["y", "n"])?; - if answer == "y" { - key_pairs.network_keypair = salt_password_prompt(std::io::stdin())?; +pub fn key_wizard<UI: UserInteractionTrait>( + user_interaction: &UI, + mut key_pairs: DuniterKeyPairs, +) -> Result<DuniterKeyPairs, UiError> { + let mut answer = + user_interaction.question_prompt("Modify your network keypair?", &["y", "n"])?; + if answer == 0 { + key_pairs.network_keypair = salt_password_prompt(user_interaction)?; } - answer = question_prompt("Modify your member keypair?", &["y", "n", "d"])?; - if answer == "y" { - key_pairs.member_keypair = Some(salt_password_prompt(std::io::stdin())?); - } else if answer == "d" { - println!("Deleting member keypair!"); + answer = user_interaction.question_prompt("Modify your member keypair?", &["y", "n", "d"])?; + if answer == 0 { + key_pairs.member_keypair = Some(salt_password_prompt(user_interaction)?); + } else if answer == 2 { + user_interaction.message("Deleting member keypair!"); clear_member_key(&mut key_pairs); } @@ -212,6 +190,7 @@ pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, Cli #[cfg(test)] mod tests { use super::*; + // use crate::ui::ui_trait::MockU; use unwrap::unwrap; @@ -223,8 +202,8 @@ mod tests { static SALT_TEST: &str = "testsalt"; static PASSWORD_TEST: &str = "testpassword"; - fn setup_user_password_input() -> MockUserPasswordInput { - let mut stdin_mock = MockUserPasswordInput::new(); + fn setup_user_password_input() -> MockUserInteractionTrait { + let mut stdin_mock = MockUserInteractionTrait::new(); stdin_mock .expect_get_password() .returning(|prompt| { @@ -233,10 +212,7 @@ mod tests { } else if prompt.starts_with("Password:") { Ok(PASSWORD_TEST.to_owned()) } else { - Err(std::io::Error::new( - std::io::ErrorKind::InvalidInput, - format!("should not be called with {}", prompt), - )) + Err(UiError::BadInput) } }) .times(2); @@ -270,7 +246,7 @@ mod tests { let key_pairs = setup_keys(false); let stdin_mock = setup_user_password_input(); let result_key_pairs = - inner_modify_member_keys(stdin_mock, key_pairs).expect("Fail to read new member keys"); + inner_modify_member_keys(&stdin_mock, key_pairs).expect("Fail to read new member keys"); // We expect network key not to change assert_eq!( result_key_pairs.network_keypair.public_key(), @@ -310,7 +286,7 @@ mod tests { fn test_modify_network_keys() { let key_pairs = setup_keys(false); let stdin_mock = setup_user_password_input(); - let result_key_pairs = inner_modify_network_keys(stdin_mock, key_pairs) + let result_key_pairs = inner_modify_network_keys(&stdin_mock, key_pairs) .expect("Fail to read new network keys"); // We expect network key to update assert_eq!( diff --git a/lib/core/conf/src/lib.rs b/lib/core/conf/src/lib.rs index 36a47b915b560efb1943935e342fe1a0f33d7bc1..66e964dfe36bc5c190954ed6c9b5dbdf9f45ae75 100644 --- a/lib/core/conf/src/lib.rs +++ b/lib/core/conf/src/lib.rs @@ -40,6 +40,7 @@ mod global_conf; pub mod keypairs; pub mod modules_conf; mod resources; +pub mod ui; mod v1; pub use crate::errors::DursConfError; diff --git a/lib/core/conf/src/ui/mod.rs b/lib/core/conf/src/ui/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..adc7bdcf0f0e1a70eb1e1315a10262ea00407b48 --- /dev/null +++ b/lib/core/conf/src/ui/mod.rs @@ -0,0 +1,19 @@ +//! Durs-core ui module. +pub mod ui_cli; +pub mod ui_trait; + +#[derive(Debug, Copy, Clone)] +/// Errors encountered by the user interaction +pub enum UiError { + /// Canceled + Canceled, + + /// Bad input + BadInput, +} + +impl From<std::io::Error> for UiError { + fn from(_e: std::io::Error) -> Self { + Self::BadInput + } +} diff --git a/lib/core/conf/src/ui/ui_cli.rs b/lib/core/conf/src/ui/ui_cli.rs new file mode 100644 index 0000000000000000000000000000000000000000..32e616392646cec1d5b2a84f9a5ae7d7ae494e62 --- /dev/null +++ b/lib/core/conf/src/ui/ui_cli.rs @@ -0,0 +1,37 @@ +//! Durs-core ui: cli trait implementation. +use crate::ui::ui_trait::*; +use crate::ui::*; + +use std::io; + +/// An empty struct for cli user interaction +#[derive(Debug, Clone, Copy)] +pub struct CLI {} + +impl UserInteractionTrait for CLI { + #[inline] + fn message(&self, message: &str) { + println!("{}", message); + } + + fn question_prompt<'a>(&self, question: &str, answers: &[&'a str]) -> Result<usize, UiError> { + let mut buf = String::new(); + + println!("{} ({}):", question, answers.join("/")); + let res = io::stdin().read_line(&mut buf); + + if res.is_ok() { + answers + .iter() + .position(|x| *x == buf.trim()) + .ok_or(UiError::Canceled) + } else { + Err(UiError::Canceled) + } + } + + #[inline] + fn get_password(&self, prompt: &str) -> Result<String, UiError> { + rpassword::prompt_password_stdout(prompt).map_err(Into::into) + } +} diff --git a/lib/core/conf/src/ui/ui_trait.rs b/lib/core/conf/src/ui/ui_trait.rs new file mode 100644 index 0000000000000000000000000000000000000000..b03334395053003995103ee6d16ceed5d7efed2c --- /dev/null +++ b/lib/core/conf/src/ui/ui_trait.rs @@ -0,0 +1,18 @@ +//! Durs-core ui: traits definition. +use crate::ui::*; + +#[cfg(test)] +use mockall::*; + +/// A trait for all user interaction somehow TODO rewrite +#[cfg_attr(test, automock)] +pub trait UserInteractionTrait { + /// Print a message to the user + fn message(&self, message: &str); + + /// Ask a question to the user + fn question_prompt<'a>(&self, question: &str, answers: &[&'a str]) -> Result<usize, UiError>; + + /// Get a password from the user + fn get_password(&self, prompt: &str) -> Result<String, UiError>; +} diff --git a/lib/core/core/src/commands/keys.rs b/lib/core/core/src/commands/keys.rs index a1c49ed7a61fab1b5df2b6749077786e9a922dfb..c91a56d2db87b90662545dcf2ad8149683931e82 100644 --- a/lib/core/core/src/commands/keys.rs +++ b/lib/core/core/src/commands/keys.rs @@ -20,6 +20,7 @@ use crate::errors::DursCoreError; use crate::DursCore; use clap::arg_enum; use durs_conf::keypairs::cli::*; +use durs_conf::ui::ui_cli::CLI; use durs_conf::DuRsConf; #[derive(StructOpt, Debug, Clone, Copy)] @@ -130,42 +131,44 @@ pub struct ShowOpt {} impl DursExecutableCoreCommand for KeysOpt { fn execute(self, durs_core: DursCore<DuRsConf>) -> Result<(), DursCoreError> { + let cli = CLI {}; let profile_path = durs_core.soft_meta_datas.profile_path; let keypairs_file = durs_core.options.keypairs_file; let keypairs = durs_core.keypairs; match self.subcommand { KeysSubCommand::Wizard(_) => { - let new_keypairs = key_wizard(keypairs)?; + let new_keypairs = key_wizard(&cli, keypairs)?; save_keypairs(profile_path, &keypairs_file, &new_keypairs) .map_err(DursCoreError::FailWriteKeypairsFile) .and_then(|_| { - show_keys(new_keypairs); + show_keys(&cli, new_keypairs); Ok(()) }) } KeysSubCommand::Modify(modify_opt) => match modify_opt.subcommand { ModifySubCommand::NetworkSaltPassword => { - let new_keypairs = modify_network_keys(keypairs)?; + let new_keypairs = modify_network_keys(&cli, keypairs)?; save_keypairs(profile_path, &keypairs_file, &new_keypairs) .map_err(DursCoreError::FailWriteKeypairsFile) .and_then(|_| { - show_network_keys(&new_keypairs); + show_network_keys(&cli, &new_keypairs); Ok(()) }) } ModifySubCommand::MemberSaltPassword => { - let new_keypairs = modify_member_keys(keypairs)?; + let new_keypairs = modify_member_keys(&cli, keypairs)?; save_keypairs(profile_path, &keypairs_file, &new_keypairs) .map_err(DursCoreError::FailWriteKeypairsFile) .and_then(|_| { - show_member_keys(&new_keypairs); + show_member_keys(&cli, &new_keypairs); Ok(()) }) } }, KeysSubCommand::Clear(clear_opt) => { let new_keypairs = clear_keys( + &cli, clear_opt.key.is_network(), clear_opt.key.is_member(), keypairs, @@ -173,12 +176,12 @@ impl DursExecutableCoreCommand for KeysOpt { save_keypairs(profile_path, &keypairs_file, &new_keypairs) .map_err(DursCoreError::FailWriteKeypairsFile) .and_then(|_| { - show_keys(new_keypairs); + show_keys(&cli, new_keypairs); Ok(()) }) } KeysSubCommand::Show(_) => { - show_keys(keypairs); + show_keys(&cli, keypairs); Ok(()) } } diff --git a/lib/core/core/src/errors.rs b/lib/core/core/src/errors.rs index dd9d85ab96beb228cf1d86c87b1bb11e38b40557..97faf814c707f51c19ab7b2bc19e4bec859c3032 100644 --- a/lib/core/core/src/errors.rs +++ b/lib/core/core/src/errors.rs @@ -17,7 +17,7 @@ use crate::logger::InitLoggerError; use dubp_currency_params::db::CurrencyParamsDbError; -use durs_conf::keypairs::cli::CliError; +use durs_conf::ui::UiError; use durs_module::{ModuleStaticName, PlugModuleError}; use failure::{Error, Fail}; @@ -67,7 +67,7 @@ pub enum DursCoreError { SyncWithoutSource, /// Error on keys sub-command #[fail(display = "Error on keys sub-command")] - WizardKeysError(CliError), + WizardKeysError(UiError), } impl From<InitLoggerError> for DursCoreError { @@ -76,8 +76,8 @@ impl From<InitLoggerError> for DursCoreError { } } -impl From<CliError> for DursCoreError { - fn from(e: CliError) -> Self { +impl From<UiError> for DursCoreError { + fn from(e: UiError) -> Self { DursCoreError::WizardKeysError(e) } }