Skip to main content

send_sms/
input.rs

1use crate::constants::{MAX_MESSAGE_LENGTH, MESSAGE_PREVIEW_LENGTH};
2use freemobile_api::FreeMobileError;
3use inquire::Text;
4use std::fs;
5use std::io::{self, Read};
6use std::path::Path;
7
8pub struct InputHandler;
9
10impl InputHandler {
11    pub async fn get_message_from_file<P: AsRef<Path>>(path: P) -> Result<String, FreeMobileError> {
12        let content = fs::read_to_string(path).map_err(FreeMobileError::IoError)?;
13
14        if content.trim().is_empty() {
15            return Err(FreeMobileError::EmptyMessage);
16        }
17
18        Ok(content.trim().to_string())
19    }
20
21    pub async fn get_message_from_stdin() -> Result<String, FreeMobileError> {
22        let mut buffer = String::new();
23        io::stdin()
24            .read_to_string(&mut buffer)
25            .map_err(FreeMobileError::IoError)?;
26
27        if buffer.trim().is_empty() {
28            return Err(FreeMobileError::EmptyMessage);
29        }
30
31        Ok(buffer.trim().to_string())
32    }
33
34    pub async fn get_message_interactive() -> Result<String, FreeMobileError> {
35        println!("Enter your message (press Enter to send, Ctrl+C to cancel):");
36
37        let message = Text::new("")
38            .with_placeholder("Type your message here...")
39            .prompt()
40            .map_err(|e| {
41                FreeMobileError::ConfigError(format!("Interactive input failed: {}", e))
42            })?;
43
44        if message.trim().is_empty() {
45            return Err(FreeMobileError::EmptyMessage);
46        }
47
48        Ok(message.trim().to_string())
49    }
50
51    pub fn validate_message(message: &str) -> Result<(), FreeMobileError> {
52        if message.trim().is_empty() {
53            return Err(FreeMobileError::EmptyMessage);
54        }
55
56        // Check for potential issues
57        if message.len() > MAX_MESSAGE_LENGTH {
58            return Err(FreeMobileError::InvalidMessage(format!(
59                "Message too long (maximum {} characters)",
60                MAX_MESSAGE_LENGTH
61            )));
62        }
63
64        Ok(())
65    }
66
67    pub fn preview_message(message: &str, verbose: bool) {
68        if !verbose {
69            return;
70        }
71
72        println!("📄 Message preview:");
73        println!("Length: {} characters", message.len());
74
75        if message.len() > MESSAGE_PREVIEW_LENGTH {
76            use unicode_segmentation::UnicodeSegmentation;
77            let truncated: String = message
78                .graphemes(true)
79                .take(MESSAGE_PREVIEW_LENGTH)
80                .collect();
81            println!(
82                "Content (first {} graphemes): {}",
83                MESSAGE_PREVIEW_LENGTH, truncated
84            );
85            println!("... (truncated for preview)");
86        } else {
87            println!("Content: {}", message);
88        }
89        println!();
90    }
91
92    pub fn has_stdin_input() -> bool {
93        use is_terminal::IsTerminal;
94        !io::stdin().is_terminal()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use std::io::Write;
102    use tempfile::NamedTempFile;
103
104    #[tokio::test]
105    async fn test_read_from_file() {
106        let mut temp_file = NamedTempFile::new().unwrap();
107        writeln!(temp_file, "Test message from file").unwrap();
108
109        let message = InputHandler::get_message_from_file(temp_file.path())
110            .await
111            .unwrap();
112        assert_eq!(message, "Test message from file");
113    }
114
115    #[tokio::test]
116    async fn test_read_from_empty_file() {
117        let temp_file = NamedTempFile::new().unwrap();
118
119        let result = InputHandler::get_message_from_file(temp_file.path()).await;
120        assert!(result.is_err());
121        assert!(matches!(result.unwrap_err(), FreeMobileError::EmptyMessage));
122    }
123
124    #[test]
125    fn test_validate_message() {
126        assert!(InputHandler::validate_message("Valid message").is_ok());
127        assert!(InputHandler::validate_message("").is_err());
128        assert!(InputHandler::validate_message("   ").is_err());
129
130        // Test maximum length boundary
131        let valid_message = "a".repeat(MAX_MESSAGE_LENGTH);
132        assert!(InputHandler::validate_message(&valid_message).is_ok());
133
134        let too_long_message = "a".repeat(MAX_MESSAGE_LENGTH + 1);
135        assert!(InputHandler::validate_message(&too_long_message).is_err());
136    }
137
138    #[test]
139    fn test_message_preview() {
140        // Test with verbose = false (should not print anything)
141        InputHandler::preview_message("Test message", false);
142
143        // Test with verbose = true (would print to stdout in real usage)
144        InputHandler::preview_message("Test message", true);
145
146        let long_message = "a".repeat(150);
147        InputHandler::preview_message(&long_message, true);
148    }
149}