freeswitch_log_parser/
attached.rs1#[derive(Debug, Default, Clone, PartialEq, Eq)]
23pub struct AttachedLines {
24 buf: String,
25 offsets: Vec<u32>,
26}
27
28impl AttachedLines {
29 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn len(&self) -> usize {
36 self.offsets.len()
37 }
38
39 pub fn is_empty(&self) -> bool {
41 self.offsets.is_empty()
42 }
43
44 pub fn push(&mut self, line: &str) {
51 let start = u32::try_from(self.buf.len()).expect("attached buffer exceeded 4 GiB");
52 self.offsets.push(start);
53 self.buf.push_str(line);
54 self.buf.push('\n');
55 }
56
57 pub fn get(&self, i: usize) -> Option<&str> {
59 let start = *self.offsets.get(i)? as usize;
60 let end = self
61 .offsets
62 .get(i + 1)
63 .map(|&o| o as usize - 1)
64 .unwrap_or_else(|| self.buf.len() - 1);
65 Some(&self.buf[start..end])
66 }
67
68 pub fn iter(&self) -> AttachedLinesIter<'_> {
70 AttachedLinesIter {
71 lines: self,
72 pos: 0,
73 }
74 }
75}
76
77impl<'a> IntoIterator for &'a AttachedLines {
78 type Item = &'a str;
79 type IntoIter = AttachedLinesIter<'a>;
80
81 fn into_iter(self) -> Self::IntoIter {
82 self.iter()
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct AttachedLinesIter<'a> {
89 lines: &'a AttachedLines,
90 pos: usize,
91}
92
93impl<'a> Iterator for AttachedLinesIter<'a> {
94 type Item = &'a str;
95
96 fn next(&mut self) -> Option<&'a str> {
97 let line = self.lines.get(self.pos)?;
98 self.pos += 1;
99 Some(line)
100 }
101
102 fn size_hint(&self) -> (usize, Option<usize>) {
103 let remaining = self.lines.len() - self.pos;
104 (remaining, Some(remaining))
105 }
106}
107
108impl ExactSizeIterator for AttachedLinesIter<'_> {}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn empty_default() {
116 let a = AttachedLines::new();
117 assert_eq!(a.len(), 0);
118 assert!(a.is_empty());
119 assert!(a.get(0).is_none());
120 assert_eq!(a.iter().count(), 0);
121 }
122
123 #[test]
124 fn push_and_iterate_preserves_order_and_content() {
125 let mut a = AttachedLines::new();
126 a.push("first");
127 a.push("");
128 a.push("third line");
129 assert_eq!(a.len(), 3);
130 assert!(!a.is_empty());
131 assert_eq!(a.get(0), Some("first"));
132 assert_eq!(a.get(1), Some(""));
133 assert_eq!(a.get(2), Some("third line"));
134 assert!(a.get(3).is_none());
135 let collected: Vec<&str> = a.iter().collect();
136 assert_eq!(collected, vec!["first", "", "third line"]);
137 }
138
139 #[test]
140 fn intoiter_for_ref_works_in_for_loop() {
141 let mut a = AttachedLines::new();
142 a.push("a");
143 a.push("b");
144 let mut out = Vec::new();
145 for line in &a {
146 out.push(line.to_string());
147 }
148 assert_eq!(out, vec!["a".to_string(), "b".to_string()]);
149 }
150
151 #[test]
152 fn lines_with_embedded_separators_round_trip() {
153 let mut a = AttachedLines::new();
157 a.push("has\nnewline");
158 a.push("plain");
159 assert_eq!(a.get(0), Some("has\nnewline"));
160 assert_eq!(a.get(1), Some("plain"));
161 }
162
163 #[test]
164 fn allocation_pattern_is_logarithmic_not_per_line() {
165 let mut a = AttachedLines::new();
168 for i in 0..200 {
169 a.push(&format!(
170 "variable_some_long_name_{i}: [a typical value here]"
171 ));
172 }
173 assert_eq!(a.len(), 200);
174 for (i, line) in a.iter().enumerate() {
176 assert!(line.starts_with(&format!("variable_some_long_name_{i}")));
177 }
178 }
179}