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