freeswitch_log_parser/
attached.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub struct AttachedOverflow;
19
20impl std::fmt::Display for AttachedOverflow {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 f.write_str("attached lines exceed the 4 GiB an entry can address")
23 }
24}
25
26impl std::error::Error for AttachedOverflow {}
27
28#[derive(Debug, Default, Clone, PartialEq, Eq)]
35pub struct AttachedLines {
36 buf: String,
37 offsets: Vec<u32>,
38}
39
40impl AttachedLines {
41 pub fn new() -> Self {
43 Self::default()
44 }
45
46 pub fn len(&self) -> usize {
48 self.offsets.len()
49 }
50
51 pub fn is_empty(&self) -> bool {
53 self.offsets.is_empty()
54 }
55
56 pub fn push(&mut self, line: &str) -> Result<(), AttachedOverflow> {
64 let start = u32::try_from(self.buf.len()).map_err(|_| AttachedOverflow)?;
65 self.offsets.push(start);
66 self.buf.push_str(line);
67 self.buf.push('\n');
68 Ok(())
69 }
70
71 pub fn get(&self, i: usize) -> Option<&str> {
73 let start = *self.offsets.get(i)? as usize;
74 let end = self
75 .offsets
76 .get(i + 1)
77 .map(|&o| o as usize - 1)
78 .unwrap_or_else(|| self.buf.len() - 1);
79 Some(&self.buf[start..end])
80 }
81
82 pub fn iter(&self) -> AttachedLinesIter<'_> {
84 AttachedLinesIter {
85 lines: self,
86 pos: 0,
87 }
88 }
89}
90
91impl<'a> IntoIterator for &'a AttachedLines {
92 type Item = &'a str;
93 type IntoIter = AttachedLinesIter<'a>;
94
95 fn into_iter(self) -> Self::IntoIter {
96 self.iter()
97 }
98}
99
100#[derive(Debug, Clone)]
102pub struct AttachedLinesIter<'a> {
103 lines: &'a AttachedLines,
104 pos: usize,
105}
106
107impl<'a> Iterator for AttachedLinesIter<'a> {
108 type Item = &'a str;
109
110 fn next(&mut self) -> Option<&'a str> {
111 let line = self.lines.get(self.pos)?;
112 self.pos += 1;
113 Some(line)
114 }
115
116 fn size_hint(&self) -> (usize, Option<usize>) {
117 let remaining = self.lines.len() - self.pos;
118 (remaining, Some(remaining))
119 }
120}
121
122impl ExactSizeIterator for AttachedLinesIter<'_> {}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn empty_default() {
130 let a = AttachedLines::new();
131 assert_eq!(a.len(), 0);
132 assert!(a.is_empty());
133 assert!(a.get(0).is_none());
134 assert_eq!(a.iter().count(), 0);
135 }
136
137 #[test]
138 fn push_and_iterate_preserves_order_and_content() {
139 let mut a = AttachedLines::new();
140 a.push("first").expect("fits");
141 a.push("").expect("fits");
142 a.push("third line").expect("fits");
143 assert_eq!(a.len(), 3);
144 assert!(!a.is_empty());
145 assert_eq!(a.get(0), Some("first"));
146 assert_eq!(a.get(1), Some(""));
147 assert_eq!(a.get(2), Some("third line"));
148 assert!(a.get(3).is_none());
149 let collected: Vec<&str> = a.iter().collect();
150 assert_eq!(collected, vec!["first", "", "third line"]);
151 }
152
153 #[test]
154 fn intoiter_for_ref_works_in_for_loop() {
155 let mut a = AttachedLines::new();
156 a.push("a").expect("fits");
157 a.push("b").expect("fits");
158 let mut out = Vec::new();
159 for line in &a {
160 out.push(line.to_string());
161 }
162 assert_eq!(out, vec!["a".to_string(), "b".to_string()]);
163 }
164
165 #[test]
166 fn lines_with_embedded_separators_round_trip() {
167 let mut a = AttachedLines::new();
171 a.push("has\nnewline").expect("fits");
172 a.push("plain").expect("fits");
173 assert_eq!(a.get(0), Some("has\nnewline"));
174 assert_eq!(a.get(1), Some("plain"));
175 }
176
177 #[test]
178 fn allocation_pattern_is_logarithmic_not_per_line() {
179 let mut a = AttachedLines::new();
182 for i in 0..200 {
183 a.push(&format!(
184 "variable_some_long_name_{i}: [a typical value here]"
185 ))
186 .expect("fits");
187 }
188 assert_eq!(a.len(), 200);
189 for (i, line) in a.iter().enumerate() {
191 assert!(line.starts_with(&format!("variable_some_long_name_{i}")));
192 }
193 }
194}