cloudfront_logs/
shared.rs1use crate::{COMMENT_U8, TABS, TAB_U8};
2
3#[inline]
26pub fn validate_line(line: &str) -> Result<(), &'static str> {
27 let bytes = line.as_bytes();
28 if bytes.is_empty() {
29 return Err("Invalid log line (empty)");
30 }
31 if bytes[0] == COMMENT_U8 {
32 return Err("Invalid log line (comment)");
33 }
34 if memchr::memchr_iter(TAB_U8, bytes).count() != TABS {
35 return Err("Invalid log line (field count)");
36 }
37 Ok(())
38}
39
40#[inline]
41pub(crate) fn split(line: &str) -> MemchrTabSplitter<'_> {
42 MemchrTabSplitter::new(line)
43}
44
45#[derive(Debug, Clone)]
46pub(crate) struct MemchrTabSplitter<'a> {
47 pub(crate) data: &'a str,
48 pub(crate) prev: usize,
49 pub(crate) end: usize,
50 pub(crate) iter: memchr::Memchr<'a>,
51}
52
53impl<'a> MemchrTabSplitter<'a> {
54 pub(crate) fn new(data: &'a str) -> Self {
55 let prev = 0;
56 let end = data.len();
57 let iter = memchr::memchr_iter(TAB_U8, data.as_bytes());
58 Self {
59 data,
60 prev,
61 end,
62 iter,
63 }
64 }
65}
66
67impl<'a> Iterator for MemchrTabSplitter<'a> {
68 type Item = &'a str;
69
70 fn next(&mut self) -> Option<Self::Item> {
71 let current_tab = self.iter.next();
72 if let Some(tab_idx) = current_tab {
73 assert!(tab_idx > 0, "Found tab stop at index 0 (invalid log line)");
74
75 let from = self.prev;
76 let to = tab_idx;
77 self.prev = to + 1;
78 Some(&self.data[from..to])
79 } else {
80 if self.prev < self.end {
82 let from = self.prev;
83 self.prev = self.end;
84 Some(&self.data[from..])
85 } else {
86 None
87 }
88 }
89 }
90}
91
92pub(crate) fn parse_as_option<T: std::str::FromStr>(s: &str) -> Result<Option<T>, T::Err> {
96 if s == "-" {
97 Ok(None)
98 } else {
99 s.parse().map(|v| Some(v))
100 }
101}
102
103pub(crate) fn as_optional_t<T: std::str::FromStr>(s: &str) -> Option<Result<T, T::Err>> {
106 if s == "-" {
107 None
108 } else {
109 Some(s.parse())
110 }
111}
112
113pub(crate) trait ToOptionalString {
116 fn to_optional_string(&self) -> Option<String>;
117}
118
119impl ToOptionalString for &str {
120 fn to_optional_string(&self) -> Option<String> {
121 if self == &"-" {
122 None
123 } else {
124 Some((*self).to_string())
125 }
126 }
127}
128
129#[cfg(feature = "parquet")]
132pub(crate) trait AsOptionalStr {
133 fn as_optional_str(&self) -> Option<&str>;
134}
135
136#[cfg(feature = "parquet")]
137impl AsOptionalStr for str {
138 fn as_optional_str(&self) -> Option<&str> {
139 if self == "-" {
140 None
141 } else {
142 Some(self)
143 }
144 }
145}