1use std::fmt;
27
28#[derive(Copy, Clone)]
30pub struct Params<'a> {
31 raw: &'a [u8],
32}
33
34impl<'a> Params<'a> {
35 pub const EMPTY: Self = Self { raw: &[] };
37
38 #[inline]
41 pub fn from_raw(raw: &'a [u8]) -> Self {
42 Self { raw }
43 }
44
45 #[inline]
47 pub fn raw(&self) -> &'a [u8] {
48 self.raw
49 }
50
51 #[inline]
53 pub fn is_empty(&self) -> bool {
54 self.raw.is_empty()
55 }
56
57 pub fn len(&self) -> usize {
60 if self.raw.is_empty() {
61 0
62 } else {
63 self.raw.iter().filter(|&&b| b == b';').count() + 1
64 }
65 }
66
67 pub fn iter(&self) -> ParamIter<'a> {
69 ParamIter {
70 inner: if self.raw.is_empty() {
71 None
72 } else {
73 Some(self.raw.split(is_semicolon))
74 },
75 }
76 }
77
78 pub fn group(&self, idx: usize) -> Option<Group<'a>> {
80 self.iter().nth(idx)
81 }
82
83 pub fn get(&self, idx: usize) -> Option<u32> {
86 self.group(idx).and_then(|g| g.first())
87 }
88
89 pub fn get_or(&self, idx: usize, default: u32) -> u32 {
92 self.get(idx).unwrap_or(default)
93 }
94
95 pub fn slice_from(&self, start: usize) -> Params<'a> {
99 let mut cur = self.raw;
100 let mut skipped = 0;
101 while skipped < start {
102 match cur.iter().position(|&b| b == b';') {
103 Some(p) => {
104 cur = &cur[p + 1..];
105 skipped += 1;
106 }
107 None => return Params::EMPTY,
108 }
109 }
110 Params { raw: cur }
111 }
112}
113
114impl<'a> fmt::Debug for Params<'a> {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 f.debug_list()
117 .entries(self.iter().map(|g| g.first()))
118 .finish()
119 }
120}
121
122impl<'a> IntoIterator for Params<'a> {
123 type Item = Group<'a>;
124 type IntoIter = ParamIter<'a>;
125 fn into_iter(self) -> Self::IntoIter {
126 self.iter()
127 }
128}
129
130#[derive(Copy, Clone)]
134pub struct Group<'a> {
135 raw: &'a [u8],
136}
137
138impl<'a> Group<'a> {
139 pub const EMPTY: Self = Self { raw: &[] };
141
142 #[inline]
144 pub fn from_raw(raw: &'a [u8]) -> Self {
145 Self { raw }
146 }
147
148 #[inline]
150 pub fn raw(&self) -> &'a [u8] {
151 self.raw
152 }
153
154 #[inline]
157 pub fn is_empty(&self) -> bool {
158 self.raw.is_empty()
159 }
160
161 pub fn len(&self) -> usize {
164 if self.raw.is_empty() {
165 1
166 } else {
167 self.raw.iter().filter(|&&b| b == b':').count() + 1
168 }
169 }
170
171 pub fn iter(&self) -> SubIter<'a> {
173 SubIter {
174 inner: self.raw.split(is_colon),
175 }
176 }
177
178 pub fn first(&self) -> Option<u32> {
180 self.iter().next().flatten()
181 }
182
183 pub fn nth(&self, i: usize) -> Option<u32> {
186 self.iter().nth(i).flatten()
187 }
188}
189
190impl<'a> fmt::Debug for Group<'a> {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.debug_list().entries(self.iter()).finish()
193 }
194}
195
196impl<'a> IntoIterator for Group<'a> {
197 type Item = Option<u32>;
198 type IntoIter = SubIter<'a>;
199 fn into_iter(self) -> Self::IntoIter {
200 self.iter()
201 }
202}
203
204pub struct ParamIter<'a> {
206 #[allow(clippy::type_complexity)]
209 inner: Option<std::slice::Split<'a, u8, fn(&u8) -> bool>>,
210}
211
212impl<'a> Iterator for ParamIter<'a> {
213 type Item = Group<'a>;
214 fn next(&mut self) -> Option<Group<'a>> {
215 self.inner.as_mut()?.next().map(|raw| Group { raw })
216 }
217}
218
219pub struct SubIter<'a> {
221 inner: std::slice::Split<'a, u8, fn(&u8) -> bool>,
222}
223
224impl<'a> Iterator for SubIter<'a> {
225 type Item = Option<u32>;
226 fn next(&mut self) -> Option<Option<u32>> {
227 self.inner.next().map(parse_u32)
228 }
229}
230
231fn is_semicolon(b: &u8) -> bool {
232 *b == b';'
233}
234
235fn is_colon(b: &u8) -> bool {
236 *b == b':'
237}
238
239fn parse_u32(bytes: &[u8]) -> Option<u32> {
240 if bytes.is_empty() {
241 return None;
242 }
243 let mut acc: u32 = 0;
244 for &b in bytes {
245 if !b.is_ascii_digit() {
246 return None;
247 }
248 acc = acc.checked_mul(10)?.checked_add((b - b'0') as u32)?;
249 }
250 Some(acc)
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 fn flat(p: Params<'_>) -> Vec<Option<u32>> {
258 p.iter().map(|g| g.first()).collect()
259 }
260
261 fn nested(p: Params<'_>) -> Vec<Vec<Option<u32>>> {
262 p.iter().map(|g| g.iter().collect()).collect()
263 }
264
265 #[test]
266 fn flat_distinguishes_absent_from_explicit_zero() {
267 assert_eq!(flat(Params::from_raw(b"")), Vec::<Option<u32>>::new());
268 assert_eq!(flat(Params::from_raw(b";")), vec![None, None]);
269 assert_eq!(
270 flat(Params::from_raw(b"0;;3")),
271 vec![Some(0), None, Some(3)]
272 );
273 assert_eq!(flat(Params::from_raw(b";5")), vec![None, Some(5)]);
274 assert_eq!(
275 flat(Params::from_raw(b"27;5;128512")),
276 vec![Some(27), Some(5), Some(128512)]
277 );
278 }
279
280 #[test]
281 fn flat_takes_first_colon_subparam() {
282 assert_eq!(flat(Params::from_raw(b"4:3;1")), vec![Some(4), Some(1)]);
283 }
284
285 #[test]
286 fn nested_preserves_colon_groups() {
287 assert_eq!(
288 nested(Params::from_raw(b"")),
289 Vec::<Vec<Option<u32>>>::new()
290 );
291 assert_eq!(
292 nested(Params::from_raw(b"38:2::255:100:50")),
293 vec![vec![
294 Some(38),
295 Some(2),
296 None,
297 Some(255),
298 Some(100),
299 Some(50),
300 ]]
301 );
302 assert_eq!(
303 nested(Params::from_raw(b"1;31;48:5:200")),
304 vec![
305 vec![Some(1)],
306 vec![Some(31)],
307 vec![Some(48), Some(5), Some(200)],
308 ]
309 );
310 }
311
312 #[test]
313 fn nested_empty_slots() {
314 assert_eq!(
315 nested(Params::from_raw(b";1;")),
316 vec![vec![None], vec![Some(1)], vec![None]]
317 );
318 assert_eq!(nested(Params::from_raw(b"4:")), vec![vec![Some(4), None]]);
319 }
320
321 #[test]
322 fn get_or_returns_default_only_for_absent() {
323 let p = Params::from_raw(b"5;;0;7");
325 assert_eq!(p.get_or(0, 1), 5);
326 assert_eq!(p.get_or(1, 1), 1);
327 assert_eq!(p.get_or(2, 1), 0);
328 assert_eq!(p.get_or(3, 1), 7);
329 assert_eq!(p.get_or(4, 1), 1);
330 assert_eq!(Params::EMPTY.get_or(0, 42), 42);
331 }
332
333 #[test]
334 fn len_counts_groups() {
335 assert_eq!(Params::from_raw(b"").len(), 0);
336 assert_eq!(Params::from_raw(b"1").len(), 1);
337 assert_eq!(Params::from_raw(b";").len(), 2);
338 assert_eq!(Params::from_raw(b"1;2;3").len(), 3);
339 assert_eq!(Params::from_raw(b"1;").len(), 2);
340 }
341
342 #[test]
343 fn slice_from_skips_groups() {
344 let p = Params::from_raw(b"1;2;3;4");
345 assert_eq!(
346 flat(p.slice_from(0)),
347 vec![Some(1), Some(2), Some(3), Some(4)]
348 );
349 assert_eq!(flat(p.slice_from(2)), vec![Some(3), Some(4)]);
350 assert_eq!(flat(p.slice_from(4)), Vec::<Option<u32>>::new());
351 assert_eq!(flat(p.slice_from(99)), Vec::<Option<u32>>::new());
352 }
353
354 #[test]
355 fn debug_format_matches_flat_list() {
356 let p = Params::from_raw(b"1;2");
357 assert_eq!(format!("{:?}", p), "[Some(1), Some(2)]");
358 }
359}