ssh_cli/ssh/
session_io.rs1#![forbid(unsafe_code)]
4#[must_use]
20pub fn truncate_utf8(content: &str, max_chars: usize) -> (String, bool) {
21 if max_chars == 0 {
22 return (String::new(), !content.is_empty());
23 }
24 if content.len() <= max_chars {
26 return (content.to_string(), false);
27 }
28 match content.char_indices().nth(max_chars) {
29 None => (content.to_string(), false),
30 Some((idx, _)) => (content[..idx].to_string(), true),
31 }
32}
33
34#[must_use]
40pub(crate) fn take_utf8_capped(bytes: Vec<u8>, max_chars: usize) -> (String, bool) {
41 let s = match String::from_utf8(bytes) {
42 Ok(s) => s,
43 Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(),
44 };
45 if max_chars == 0 {
46 return (String::new(), !s.is_empty());
47 }
48 if s.len() <= max_chars {
50 return (s, false);
51 }
52 match s.char_indices().nth(max_chars) {
53 None => (s, false),
54 Some((idx, _)) => {
55 let mut owned = s;
56 owned.truncate(idx);
57 (owned, true)
58 }
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn truncate_utf8_no_truncate_if_fits() {
68 let (s, t) = truncate_utf8("ola mundo", 100);
69 assert_eq!(s, "ola mundo");
70 assert!(!t);
71 }
72
73 #[test]
74 fn truncate_utf8_truncates_large_ascii() {
75 let entrada: String = "a".repeat(200);
76 let (s, t) = truncate_utf8(&entrada, 50);
77 assert_eq!(s.chars().count(), 50);
78 assert!(t);
79 }
80
81 #[test]
82 fn truncate_utf8_preserves_accented_graphemes() {
83 let entrada: String = "á".repeat(30);
85 let (s, t) = truncate_utf8(&entrada, 10);
86 assert_eq!(s.chars().count(), 10);
87 assert_eq!(s.len(), 20);
89 assert!(t);
90 assert!(s.chars().all(|c| c == 'á'));
92 }
93
94 #[test]
95 fn truncate_utf8_emojis_does_not_break() {
96 let entrada = "🚀🔒🛡🔑✨🎉💎⚡🌟🔥🎨";
97 let (s, t) = truncate_utf8(entrada, 5);
98 assert_eq!(s.chars().count(), 5);
99 assert!(t);
100 }
101
102 #[test]
103 fn truncate_utf8_zero_returns_empty() {
104 let (s, t) = truncate_utf8("abc", 0);
105 assert_eq!(s, "");
106 assert!(t);
107 }
108
109 #[test]
110 fn take_utf8_capped_reuses_valid_utf8_without_truncate() {
111 let bytes = b"hello agent".to_vec();
112 let (s, t) = take_utf8_capped(bytes, 100);
113 assert_eq!(s, "hello agent");
114 assert!(!t);
115 }
116
117 #[test]
118 fn take_utf8_capped_truncates_and_replaces_invalid() {
119 let mut bytes = b"abcdef".to_vec();
120 let (s, t) = take_utf8_capped(bytes.clone(), 3);
121 assert_eq!(s, "abc");
122 assert!(t);
123 bytes = vec![0xFF, b'a', b'b'];
125 let (s, t) = take_utf8_capped(bytes, 10);
126 assert!(!s.is_empty());
127 assert!(!t);
128 assert!(s.contains('a'));
129 }
130}