#![forbid(unsafe_code)]
use super::ConversationHandler;
use crate::error::ErrorCode;
use std::ffi::{CStr, CString};
use std::iter::FusedIterator;
use std::vec;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LogEntry {
Info(CString),
Error(CString),
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Conversation {
pub username: String,
pub password: String,
pub log: vec::Vec<LogEntry>,
}
impl Conversation {
#[must_use]
pub const fn new() -> Self {
Self {
username: String::new(),
password: String::new(),
log: vec::Vec::new(),
}
}
#[must_use]
pub fn with_credentials(username: impl Into<String>, password: impl Into<String>) -> Self {
Self {
username: username.into(),
password: password.into(),
log: vec::Vec::new(),
}
}
pub fn clear_log(&mut self) {
self.log.clear();
}
pub fn errors(&self) -> impl Iterator<Item = &CString> + FusedIterator {
self.log.iter().filter_map(|x| match x {
LogEntry::Info(_) => None,
LogEntry::Error(msg) => Some(msg),
})
}
pub fn infos(&self) -> impl Iterator<Item = &CString> + FusedIterator {
self.log.iter().filter_map(|x| match x {
LogEntry::Info(msg) => Some(msg),
LogEntry::Error(_) => None,
})
}
}
impl Default for Conversation {
fn default() -> Self {
Self::new()
}
}
impl ConversationHandler for Conversation {
fn init(&mut self, default_user: Option<&str>) {
if let Some(user) = default_user {
if self.username.is_empty() {
self.username = user.to_string();
}
}
}
fn prompt_echo_on(&mut self, _msg: &CStr) -> Result<CString, ErrorCode> {
CString::new(self.username.clone()).map_err(|_| ErrorCode::CONV_ERR)
}
fn prompt_echo_off(&mut self, _msg: &CStr) -> Result<CString, ErrorCode> {
CString::new(self.password.clone()).map_err(|_| ErrorCode::CONV_ERR)
}
fn text_info(&mut self, msg: &CStr) {
self.log.push(LogEntry::Info(msg.to_owned()));
}
fn error_msg(&mut self, msg: &CStr) {
self.log.push(LogEntry::Error(msg.to_owned()));
}
fn radio_prompt(&mut self, _msg: &CStr) -> Result<bool, ErrorCode> {
Ok(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let text = CString::new("test").unwrap();
let mut c = Conversation::default();
let _ = c.clone();
assert!(c.prompt_echo_on(&text).is_ok());
assert!(c.prompt_echo_off(&text).is_ok());
assert!(c.radio_prompt(&text).ok() == Some(false));
assert!(c.binary_prompt(0, &[]).is_err());
c.text_info(&text);
c.error_msg(&text);
assert_eq!(c.log.len(), 2);
let v: std::vec::Vec<&CString> = c.errors().collect();
assert_eq!(v.len(), 1);
let v: std::vec::Vec<&CString> = c.infos().collect();
assert_eq!(v.len(), 1);
assert!(format!("{:?}", &c).contains("test"));
}
#[test]
fn test_boxed() {
let text = CString::new("test").unwrap();
let mut c = Box::<Conversation>::default();
assert!(c.prompt_echo_on(&text).is_ok());
assert!(c.prompt_echo_off(&text).is_ok());
assert!(c.radio_prompt(&text).ok() == Some(false));
assert!(c.binary_prompt(0, &[]).is_err());
c.text_info(&text);
c.error_msg(&text);
assert_eq!(c.log.len(), 2);
let v: std::vec::Vec<&CString> = c.errors().collect();
assert_eq!(v.len(), 1);
let v: std::vec::Vec<&CString> = c.infos().collect();
assert_eq!(v.len(), 1);
assert!(format!("{:?}", &c).contains("test"));
}
}