Skip to content
Snippets Groups Projects
Commit 9f9fd066 authored by dvermd's avatar dvermd
Browse files

wip: rework user interaction

parent 761e5cc5
No related branches found
No related tags found
No related merge requests found
...@@ -27,80 +27,63 @@ ...@@ -27,80 +27,63 @@
unused_qualifications unused_qualifications
)] )]
use crate::ui::ui_trait::*;
use crate::ui::UiError;
use crate::*; 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] #[inline]
/// Modify network keys command /// Modify network keys command
pub fn modify_network_keys(key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> { pub fn modify_network_keys<UI: UserInteractionTrait>(
inner_modify_network_keys(std::io::stdin(), key_pairs) user_interaction: &UI,
key_pairs: DuniterKeyPairs,
) -> Result<DuniterKeyPairs, UiError> {
inner_modify_network_keys(user_interaction, key_pairs)
} }
/// Private function to modify network keys /// Private function to modify network keys
fn inner_modify_network_keys<T: UserPasswordInput>( fn inner_modify_network_keys<UI: UserInteractionTrait>(
stdin: T, user_interaction: &UI,
mut key_pairs: DuniterKeyPairs, mut key_pairs: DuniterKeyPairs,
) -> Result<DuniterKeyPairs, CliError> { ) -> Result<DuniterKeyPairs, UiError> {
key_pairs.network_keypair = salt_password_prompt(stdin)?; key_pairs.network_keypair = salt_password_prompt(user_interaction)?;
Ok(key_pairs) Ok(key_pairs)
} }
#[inline] #[inline]
/// Modify member keys command /// Modify member keys command
pub fn modify_member_keys(key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> { pub fn modify_member_keys<UI: UserInteractionTrait>(
inner_modify_member_keys(std::io::stdin(), key_pairs) user_interaction: &UI,
key_pairs: DuniterKeyPairs,
) -> Result<DuniterKeyPairs, UiError> {
inner_modify_member_keys(user_interaction, key_pairs)
} }
/// Private function to modify network keys /// Private function to modify network keys
fn inner_modify_member_keys<T: UserPasswordInput>( fn inner_modify_member_keys<UI: UserInteractionTrait>(
stdin: T, user_interaction: &UI,
mut key_pairs: DuniterKeyPairs, mut key_pairs: DuniterKeyPairs,
) -> Result<DuniterKeyPairs, CliError> { ) -> Result<DuniterKeyPairs, UiError> {
key_pairs.member_keypair = Some(salt_password_prompt(stdin)?); key_pairs.member_keypair = Some(salt_password_prompt(user_interaction)?);
Ok(key_pairs) Ok(key_pairs)
} }
/// Ask user for confirmation and Clear keys command /// 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 network {
if let Ok("y") = question_prompt("Clear your network keypair?", &["y", "n"]) { if let Ok(0) = user_interaction.question_prompt("Clear your network keypair?", &["y", "n"])
println!("Generating a new network keypair!"); {
user_interaction.message("Generating a new network keypair!");
clear_network_key(&mut key_pairs); clear_network_key(&mut key_pairs);
} }
} }
if member { if member {
if let Ok("y") = question_prompt("Clear your member keypair?", &["y", "n"]) { if let Ok(0) = user_interaction.question_prompt("Clear your member keypair?", &["y", "n"]) {
println!("Deleting member keypair!"); user_interaction.message("Deleting member keypair!");
clear_member_key(&mut key_pairs); clear_member_key(&mut key_pairs);
} }
} }
...@@ -120,23 +103,29 @@ fn clear_member_key(key_pairs: &mut DuniterKeyPairs) { ...@@ -120,23 +103,29 @@ fn clear_member_key(key_pairs: &mut DuniterKeyPairs) {
} }
/// Show keys command /// Show keys command
pub fn show_keys(key_pairs: DuniterKeyPairs) { pub fn show_keys<UI: UserInteractionTrait>(user_interaction: &UI, key_pairs: DuniterKeyPairs) {
show_network_keys(&key_pairs); show_network_keys(user_interaction, &key_pairs);
show_member_keys(&key_pairs); show_member_keys(user_interaction, &key_pairs);
} }
#[inline] #[inline]
/// Show network keys /// Show network keys
pub fn show_network_keys(key_pairs: &DuniterKeyPairs) { pub fn show_network_keys<UI: UserInteractionTrait>(
println!("Network key: {}", key_pairs.network_keypair); user_interaction: &UI,
key_pairs: &DuniterKeyPairs,
) {
user_interaction.message(&format!("Network key: {}", key_pairs.network_keypair));
} }
#[inline] #[inline]
/// Show member keys /// 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 { match &key_pairs.member_keypair {
None => println!("No member key configured"), None => user_interaction.message("No member key configured"),
Some(key) => println!("Member key: {}", key), Some(key) => user_interaction.message(&format!("Member key: {}", key)),
} }
} }
...@@ -156,27 +145,12 @@ pub fn save_keypairs( ...@@ -156,27 +145,12 @@ pub fn save_keypairs(
super::write_keypairs_file(&conf_keys_path, &key_pairs) super::write_keypairs_file(&conf_keys_path, &key_pairs)
} }
fn question_prompt<'a>(question: &str, answers: &[&'a str]) -> Result<&'a str, CliError> { fn salt_password_prompt<UI: UserInteractionTrait>(
let mut buf = String::new(); user_interaction: &UI,
) -> Result<KeyPairEnum, UiError> {
println!("{} ({}):", question, answers.join("/")); let salt = user_interaction.get_password("Salt: ")?;
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: ")?;
if !salt.is_empty() { if !salt.is_empty() {
let password = stdin.get_password("Password: ")?; let password = user_interaction.get_password("Password: ")?;
if !password.is_empty() { if !password.is_empty() {
let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters(); let generator = ed25519::KeyPairFromSaltedPasswordGenerator::with_default_parameters();
let key_pairs = KeyPairEnum::Ed25519( let key_pairs = KeyPairEnum::Ed25519(
...@@ -184,25 +158,29 @@ fn salt_password_prompt<T: UserPasswordInput>(stdin: T) -> Result<KeyPairEnum, C ...@@ -184,25 +158,29 @@ fn salt_password_prompt<T: UserPasswordInput>(stdin: T) -> Result<KeyPairEnum, C
); );
Ok(key_pairs) Ok(key_pairs)
} else { } else {
Err(CliError::BadInput) Err(UiError::BadInput)
} }
} else { } else {
Err(CliError::BadInput) Err(UiError::BadInput)
} }
} }
/// The wizard key function /// The wizard key function
pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, CliError> { pub fn key_wizard<UI: UserInteractionTrait>(
let mut answer = question_prompt("Modify your network keypair?", &["y", "n"])?; user_interaction: &UI,
if answer == "y" { mut key_pairs: DuniterKeyPairs,
key_pairs.network_keypair = salt_password_prompt(std::io::stdin())?; ) -> Result<DuniterKeyPairs, UiError> {
} let mut answer =
user_interaction.question_prompt("Modify your network keypair?", &["y", "n"])?;
answer = question_prompt("Modify your member keypair?", &["y", "n", "d"])?; if answer == 0 {
if answer == "y" { key_pairs.network_keypair = salt_password_prompt(user_interaction)?;
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); clear_member_key(&mut key_pairs);
} }
...@@ -212,6 +190,7 @@ pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, Cli ...@@ -212,6 +190,7 @@ pub fn key_wizard(mut key_pairs: DuniterKeyPairs) -> Result<DuniterKeyPairs, Cli
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
// use crate::ui::ui_trait::MockU;
use unwrap::unwrap; use unwrap::unwrap;
...@@ -223,8 +202,8 @@ mod tests { ...@@ -223,8 +202,8 @@ mod tests {
static SALT_TEST: &str = "testsalt"; static SALT_TEST: &str = "testsalt";
static PASSWORD_TEST: &str = "testpassword"; static PASSWORD_TEST: &str = "testpassword";
fn setup_user_password_input() -> MockUserPasswordInput { fn setup_user_password_input() -> MockUserInteractionTrait {
let mut stdin_mock = MockUserPasswordInput::new(); let mut stdin_mock = MockUserInteractionTrait::new();
stdin_mock stdin_mock
.expect_get_password() .expect_get_password()
.returning(|prompt| { .returning(|prompt| {
...@@ -233,10 +212,7 @@ mod tests { ...@@ -233,10 +212,7 @@ mod tests {
} else if prompt.starts_with("Password:") { } else if prompt.starts_with("Password:") {
Ok(PASSWORD_TEST.to_owned()) Ok(PASSWORD_TEST.to_owned())
} else { } else {
Err(std::io::Error::new( Err(UiError::BadInput)
std::io::ErrorKind::InvalidInput,
format!("should not be called with {}", prompt),
))
} }
}) })
.times(2); .times(2);
...@@ -270,7 +246,7 @@ mod tests { ...@@ -270,7 +246,7 @@ mod tests {
let key_pairs = setup_keys(false); let key_pairs = setup_keys(false);
let stdin_mock = setup_user_password_input(); let stdin_mock = setup_user_password_input();
let result_key_pairs = 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 // We expect network key not to change
assert_eq!( assert_eq!(
result_key_pairs.network_keypair.public_key(), result_key_pairs.network_keypair.public_key(),
...@@ -310,7 +286,7 @@ mod tests { ...@@ -310,7 +286,7 @@ mod tests {
fn test_modify_network_keys() { fn test_modify_network_keys() {
let key_pairs = setup_keys(false); let key_pairs = setup_keys(false);
let stdin_mock = setup_user_password_input(); 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"); .expect("Fail to read new network keys");
// We expect network key to update // We expect network key to update
assert_eq!( assert_eq!(
......
...@@ -40,6 +40,7 @@ mod global_conf; ...@@ -40,6 +40,7 @@ mod global_conf;
pub mod keypairs; pub mod keypairs;
pub mod modules_conf; pub mod modules_conf;
mod resources; mod resources;
pub mod ui;
mod v1; mod v1;
pub use crate::errors::DursConfError; pub use crate::errors::DursConfError;
......
//! 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
}
}
//! 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)
}
}
//! 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>;
}
...@@ -20,6 +20,7 @@ use crate::errors::DursCoreError; ...@@ -20,6 +20,7 @@ use crate::errors::DursCoreError;
use crate::DursCore; use crate::DursCore;
use clap::arg_enum; use clap::arg_enum;
use durs_conf::keypairs::cli::*; use durs_conf::keypairs::cli::*;
use durs_conf::ui::ui_cli::CLI;
use durs_conf::DuRsConf; use durs_conf::DuRsConf;
#[derive(StructOpt, Debug, Clone, Copy)] #[derive(StructOpt, Debug, Clone, Copy)]
...@@ -130,42 +131,44 @@ pub struct ShowOpt {} ...@@ -130,42 +131,44 @@ pub struct ShowOpt {}
impl DursExecutableCoreCommand for KeysOpt { impl DursExecutableCoreCommand for KeysOpt {
fn execute(self, durs_core: DursCore<DuRsConf>) -> Result<(), DursCoreError> { fn execute(self, durs_core: DursCore<DuRsConf>) -> Result<(), DursCoreError> {
let cli = CLI {};
let profile_path = durs_core.soft_meta_datas.profile_path; let profile_path = durs_core.soft_meta_datas.profile_path;
let keypairs_file = durs_core.options.keypairs_file; let keypairs_file = durs_core.options.keypairs_file;
let keypairs = durs_core.keypairs; let keypairs = durs_core.keypairs;
match self.subcommand { match self.subcommand {
KeysSubCommand::Wizard(_) => { KeysSubCommand::Wizard(_) => {
let new_keypairs = key_wizard(keypairs)?; let new_keypairs = key_wizard(&cli, keypairs)?;
save_keypairs(profile_path, &keypairs_file, &new_keypairs) save_keypairs(profile_path, &keypairs_file, &new_keypairs)
.map_err(DursCoreError::FailWriteKeypairsFile) .map_err(DursCoreError::FailWriteKeypairsFile)
.and_then(|_| { .and_then(|_| {
show_keys(new_keypairs); show_keys(&cli, new_keypairs);
Ok(()) Ok(())
}) })
} }
KeysSubCommand::Modify(modify_opt) => match modify_opt.subcommand { KeysSubCommand::Modify(modify_opt) => match modify_opt.subcommand {
ModifySubCommand::NetworkSaltPassword => { 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) save_keypairs(profile_path, &keypairs_file, &new_keypairs)
.map_err(DursCoreError::FailWriteKeypairsFile) .map_err(DursCoreError::FailWriteKeypairsFile)
.and_then(|_| { .and_then(|_| {
show_network_keys(&new_keypairs); show_network_keys(&cli, &new_keypairs);
Ok(()) Ok(())
}) })
} }
ModifySubCommand::MemberSaltPassword => { 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) save_keypairs(profile_path, &keypairs_file, &new_keypairs)
.map_err(DursCoreError::FailWriteKeypairsFile) .map_err(DursCoreError::FailWriteKeypairsFile)
.and_then(|_| { .and_then(|_| {
show_member_keys(&new_keypairs); show_member_keys(&cli, &new_keypairs);
Ok(()) Ok(())
}) })
} }
}, },
KeysSubCommand::Clear(clear_opt) => { KeysSubCommand::Clear(clear_opt) => {
let new_keypairs = clear_keys( let new_keypairs = clear_keys(
&cli,
clear_opt.key.is_network(), clear_opt.key.is_network(),
clear_opt.key.is_member(), clear_opt.key.is_member(),
keypairs, keypairs,
...@@ -173,12 +176,12 @@ impl DursExecutableCoreCommand for KeysOpt { ...@@ -173,12 +176,12 @@ impl DursExecutableCoreCommand for KeysOpt {
save_keypairs(profile_path, &keypairs_file, &new_keypairs) save_keypairs(profile_path, &keypairs_file, &new_keypairs)
.map_err(DursCoreError::FailWriteKeypairsFile) .map_err(DursCoreError::FailWriteKeypairsFile)
.and_then(|_| { .and_then(|_| {
show_keys(new_keypairs); show_keys(&cli, new_keypairs);
Ok(()) Ok(())
}) })
} }
KeysSubCommand::Show(_) => { KeysSubCommand::Show(_) => {
show_keys(keypairs); show_keys(&cli, keypairs);
Ok(()) Ok(())
} }
} }
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
use crate::logger::InitLoggerError; use crate::logger::InitLoggerError;
use dubp_currency_params::db::CurrencyParamsDbError; use dubp_currency_params::db::CurrencyParamsDbError;
use durs_conf::keypairs::cli::CliError; use durs_conf::ui::UiError;
use durs_module::{ModuleStaticName, PlugModuleError}; use durs_module::{ModuleStaticName, PlugModuleError};
use failure::{Error, Fail}; use failure::{Error, Fail};
...@@ -67,7 +67,7 @@ pub enum DursCoreError { ...@@ -67,7 +67,7 @@ pub enum DursCoreError {
SyncWithoutSource, SyncWithoutSource,
/// Error on keys sub-command /// Error on keys sub-command
#[fail(display = "Error on keys sub-command")] #[fail(display = "Error on keys sub-command")]
WizardKeysError(CliError), WizardKeysError(UiError),
} }
impl From<InitLoggerError> for DursCoreError { impl From<InitLoggerError> for DursCoreError {
...@@ -76,8 +76,8 @@ impl From<InitLoggerError> for DursCoreError { ...@@ -76,8 +76,8 @@ impl From<InitLoggerError> for DursCoreError {
} }
} }
impl From<CliError> for DursCoreError { impl From<UiError> for DursCoreError {
fn from(e: CliError) -> Self { fn from(e: UiError) -> Self {
DursCoreError::WizardKeysError(e) DursCoreError::WizardKeysError(e)
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment