1#![forbid(unsafe_code)]
12
13use super::ConversationHandler;
14use crate::error::ErrorCode;
15use std::ffi::{CStr, CString};
16use std::iter::FusedIterator;
17use std::vec;
18
19#[derive(Debug, Clone)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub enum LogEntry {
23 Info(CString),
24 Error(CString),
25}
26
27#[derive(Debug, Clone)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct Conversation {
47 pub username: String,
49 pub password: String,
51 pub log: vec::Vec<LogEntry>,
53}
54
55impl Conversation {
56 #[must_use]
62 pub const fn new() -> Self {
63 Self {
64 username: String::new(),
65 password: String::new(),
66 log: vec::Vec::new(),
67 }
68 }
69
70 #[must_use]
72 pub fn with_credentials(username: impl Into<String>, password: impl Into<String>) -> Self {
73 Self {
74 username: username.into(),
75 password: password.into(),
76 log: vec::Vec::new(),
77 }
78 }
79
80 pub fn clear_log(&mut self) {
82 self.log.clear();
83 }
84
85 pub fn errors(&self) -> impl Iterator<Item = &CString> + FusedIterator {
87 self.log.iter().filter_map(|x| match x {
88 LogEntry::Info(_) => None,
89 LogEntry::Error(msg) => Some(msg),
90 })
91 }
92
93 pub fn infos(&self) -> impl Iterator<Item = &CString> + FusedIterator {
95 self.log.iter().filter_map(|x| match x {
96 LogEntry::Info(msg) => Some(msg),
97 LogEntry::Error(_) => None,
98 })
99 }
100}
101
102impl Default for Conversation {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108impl ConversationHandler for Conversation {
109 fn init(&mut self, default_user: Option<impl AsRef<str>>) {
110 if let Some(user) = default_user {
111 if self.username.is_empty() {
112 self.username = user.as_ref().to_string();
113 }
114 }
115 }
116
117 fn prompt_echo_on(&mut self, _msg: &CStr) -> Result<CString, ErrorCode> {
118 CString::new(self.username.clone()).map_err(|_| ErrorCode::CONV_ERR)
119 }
120
121 fn prompt_echo_off(&mut self, _msg: &CStr) -> Result<CString, ErrorCode> {
122 CString::new(self.password.clone()).map_err(|_| ErrorCode::CONV_ERR)
123 }
124
125 fn text_info(&mut self, msg: &CStr) {
126 self.log.push(LogEntry::Info(msg.to_owned()));
127 }
128
129 fn error_msg(&mut self, msg: &CStr) {
130 self.log.push(LogEntry::Error(msg.to_owned()));
131 }
132
133 fn radio_prompt(&mut self, _msg: &CStr) -> Result<bool, ErrorCode> {
134 Ok(false)
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 #[test]
143 fn test() {
144 let text = CString::new("test").unwrap();
145 let mut c = Conversation::default();
146 let _ = c.clone();
147 assert!(c.prompt_echo_on(&text).is_ok());
148 assert!(c.prompt_echo_off(&text).is_ok());
149 assert!(c.radio_prompt(&text).ok() == Some(false));
150 assert!(c.binary_prompt(0, &[]).is_err());
151 c.text_info(&text);
152 c.error_msg(&text);
153 assert_eq!(c.log.len(), 2);
154 let v: std::vec::Vec<&CString> = c.errors().collect();
155 assert_eq!(v.len(), 1);
156 let v: std::vec::Vec<&CString> = c.infos().collect();
157 assert_eq!(v.len(), 1);
158 assert!(format!("{:?}", &c).contains("test"));
159 }
160}