use std::{fmt::Display, process};
use chrono::NaiveDate;
use inquire::{
validator::Validation, Confirm, DateSelect, InquireError, Password, PasswordDisplayMode,
Select, Text,
};
use twilly::TwilioConfig;
pub fn request_credentials() -> TwilioConfig {
let account_sid_prompt = Text::new("Please provide an account SID:")
.with_placeholder("AC...")
.with_validator(|val: &str| match val.starts_with("AC") {
true => Ok(Validation::Valid),
false => Ok(Validation::Invalid("Account SID must start with AC".into())),
})
.with_validator(|val: &str| match val.len() {
34 => Ok(Validation::Valid),
_ => Ok(Validation::Invalid(
"Your SID should be 34 characters in length".into(),
)),
});
let account_sid = prompt_user(account_sid_prompt).unwrap_or(String::from(""));
let auth_token_prompt = Password::new("Provide the auth token (input hidden):")
.with_validator(|val: &str| match val.len() {
32 => Ok(Validation::Valid),
_ => Ok(Validation::Invalid(
"Your SID should be 32 characters in length".into(),
)),
})
.with_display_mode(PasswordDisplayMode::Masked)
.with_display_toggle_enabled()
.without_confirmation()
.with_help_message("Input is masked. Use Ctrl + R to toggle visibility.");
let auth_token = prompt_user(auth_token_prompt).unwrap_or(String::from(""));
TwilioConfig::build(account_sid, auth_token)
}
pub trait InquireControl<T> {
fn prompt_user(&self) -> Result<T, InquireError>;
}
impl InquireControl<String> for Text<'_> {
fn prompt_user(&self) -> Result<String, InquireError> {
self.clone().prompt()
}
}
impl InquireControl<String> for Password<'_> {
fn prompt_user(&self) -> Result<String, InquireError> {
self.clone().prompt()
}
}
impl InquireControl<bool> for Confirm<'_> {
fn prompt_user(&self) -> Result<bool, InquireError> {
self.clone().prompt()
}
}
impl InquireControl<NaiveDate> for DateSelect<'_> {
fn prompt_user(&self) -> Result<NaiveDate, InquireError> {
self.clone().prompt()
}
}
fn handle_inquire_error<T>(error: InquireError) -> Option<T> {
match error {
inquire::InquireError::OperationCanceled => None,
inquire::InquireError::OperationInterrupted => {
eprintln!("Operation interrupted. Closing program.");
process::exit(130);
}
inquire::InquireError::IO(err) => {
panic!("Unhandled IO Error: {}", err);
}
inquire::InquireError::NotTTY => {
panic!("Unable to handle non-TTY input device.");
}
inquire::InquireError::InvalidConfiguration(err) => {
panic!(
"Invalid configuration for select, multi_select, or date_select: {}",
err
);
}
inquire::InquireError::Custom(err) => {
panic!(
"Custom user error caught at root. This probably shouldn't have happened :/ {}",
err
);
}
}
}
pub fn prompt_user<T>(control: impl InquireControl<T>) -> Option<T> {
match control.prompt_user() {
Ok(result) => Some(result),
Err(error) => handle_inquire_error(error),
}
}
pub fn prompt_user_selection<T: Display>(control: Select<'_, T>) -> Option<T> {
match control.prompt() {
Ok(result) => Some(result),
Err(error) => handle_inquire_error(error),
}
}
pub enum FilterChoice {
Any,
Other(String),
}
pub fn get_filter_choice_from_user(
mut filter_options: Vec<String>,
message: &str,
) -> Option<FilterChoice> {
filter_options.insert(0, String::from("Any"));
let filter_choice_prompt = Select::new(message, filter_options);
let filter_choice_opt = prompt_user_selection(filter_choice_prompt);
if filter_choice_opt.is_some() {
let filter_choice = filter_choice_opt.unwrap();
if filter_choice.as_str() == "Any" {
Some(FilterChoice::Any)
} else {
Some(FilterChoice::Other(filter_choice))
}
} else {
None
}
}
pub enum ActionChoice {
Back,
Exit,
Other(String),
}
pub fn get_action_choice_from_user(
mut action_options: Vec<String>,
message: &str,
) -> Option<ActionChoice> {
let mut back_and_exit_options = vec![String::from("Back"), String::from("Exit")];
action_options.append(&mut back_and_exit_options);
let action_choice_prompt = Select::new(message, action_options);
let action_choice_opt = prompt_user_selection(action_choice_prompt);
match action_choice_opt {
Some(action_choice) => match action_choice.as_str() {
"Back" => Some(ActionChoice::Back),
"Exit" => Some(ActionChoice::Exit),
_ => Some(ActionChoice::Other(action_choice)),
},
None => None,
}
}