use crate::error::Error;
use std::io::{BufRead, Write};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
#[cfg(test)]
use mockall::predicate::*;
#[cfg_attr(test, mockall::automock)]
pub trait InputOutput {
fn print(&self, text: &str) -> Result<(), Error>;
fn println(&self, text: &str) -> Result<(), Error>;
fn flush(&self) -> Result<(), Error>;
fn read_line(&self) -> Result<String, Error>;
fn read_line_with_timeout(&self, timeout: Duration) -> Result<String, Error>;
}
pub struct RealInputOutput;
impl InputOutput for RealInputOutput {
fn print(&self, text: &str) -> Result<(), Error> {
print!("{text}");
Ok(())
}
fn println(&self, text: &str) -> Result<(), Error> {
println!("{text}");
Ok(())
}
fn flush(&self) -> Result<(), Error> {
std::io::stdout()
.flush()
.map_err(|e| Error::io_error(format!("Failed to flush stdout: {e}")))
}
fn read_line(&self) -> Result<String, Error> {
let stdin = std::io::stdin();
let mut line = String::new();
stdin
.lock()
.read_line(&mut line)
.map_err(|e| Error::io_error(format!("Failed to read from stdin: {e}")))?;
Ok(line)
}
fn read_line_with_timeout(&self, timeout: Duration) -> Result<String, Error> {
let (tx, rx) = mpsc::channel();
let read_thread = thread::spawn(move || {
let stdin = std::io::stdin();
let mut line = String::new();
let result = stdin.lock().read_line(&mut line);
match result {
Ok(_) => tx.send(Ok(line)).unwrap_or(()),
Err(e) => tx
.send(Err(Error::io_error(format!(
"Failed to read from stdin: {e}"
))))
.unwrap_or(()),
}
});
match rx.recv_timeout(timeout) {
Ok(result) => {
let _ = read_thread.join();
result
}
Err(mpsc::RecvTimeoutError::Timeout) => {
Err(Error::interactive_timeout())
}
Err(mpsc::RecvTimeoutError::Disconnected) => {
Err(Error::invalid_config("Input channel disconnected"))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mock_input_output() {
let mut mock = MockInputOutput::new();
mock.expect_print()
.with(eq("Hello"))
.times(1)
.returning(|_| Ok(()));
mock.expect_read_line()
.times(1)
.returning(|| Ok("test input\n".to_string()));
assert!(mock.print("Hello").is_ok());
assert_eq!(mock.read_line().unwrap(), "test input\n");
}
}