Skip to main content

gluex_ccdb/
data.rs

1use crate::{
2    models::{ColumnMeta, ColumnType},
3    CCDBError, CCDBResult,
4};
5use itertools::izip;
6use memchr::memchr;
7use std::{collections::HashMap, sync::Arc};
8
9/// Column-oriented storage for a single CCDB field.
10#[derive(Debug, Clone)]
11pub enum Column {
12    /// Signed 32-bit integer values.
13    Int(Vec<i32>),
14    /// Unsigned 32-bit integer values.
15    UInt(Vec<u32>),
16    /// Signed 64-bit integer values.
17    Long(Vec<i64>),
18    /// Unsigned 64-bit integer values.
19    ULong(Vec<u64>),
20    /// Floating-point values.
21    Double(Vec<f64>),
22    /// Boolean values.
23    Bool(Vec<bool>),
24    /// UTF-8 string values.
25    String(Vec<String>),
26}
27
28impl Column {
29    /// Number of rows in this column.
30    #[must_use]
31    pub fn len(&self) -> usize {
32        match self {
33            Self::Int(v) => v.len(),
34            Self::UInt(v) => v.len(),
35            Self::Long(v) => v.len(),
36            Self::ULong(v) => v.len(),
37            Self::Double(v) => v.len(),
38            Self::Bool(v) => v.len(),
39            Self::String(v) => v.len(),
40        }
41    }
42
43    /// Check if the column is empty.
44    #[must_use]
45    pub fn is_empty(&self) -> bool {
46        match self {
47            Self::Int(v) => v.is_empty(),
48            Self::UInt(v) => v.is_empty(),
49            Self::Long(v) => v.is_empty(),
50            Self::ULong(v) => v.is_empty(),
51            Self::Double(v) => v.is_empty(),
52            Self::Bool(v) => v.is_empty(),
53            Self::String(v) => v.is_empty(),
54        }
55    }
56
57    /// Returns the value at the requested row.
58    #[must_use]
59    pub fn row(&self, row: usize) -> Value<'_> {
60        match self {
61            Self::Int(v) => Value::Int(&v[row]),
62            Self::UInt(v) => Value::UInt(&v[row]),
63            Self::Long(v) => Value::Long(&v[row]),
64            Self::ULong(v) => Value::ULong(&v[row]),
65            Self::Double(v) => Value::Double(&v[row]),
66            Self::Bool(v) => Value::Bool(&v[row]),
67            Self::String(v) => Value::String(&v[row]),
68        }
69    }
70
71    /// Returns a clone of the underlying [`i32`] data, if the type matches.
72    #[must_use]
73    pub fn int(&self) -> Option<Vec<i32>> {
74        match self {
75            Self::Int(v) => Some(v.clone()),
76            _ => None,
77        }
78    }
79    /// Returns a clone of the underlying [`u32`] data, if the type matches.
80    #[must_use]
81    pub fn uint(&self) -> Option<Vec<u32>> {
82        match self {
83            Self::UInt(v) => Some(v.clone()),
84            _ => None,
85        }
86    }
87    /// Returns a clone of the underlying [`i64`] data, if the type matches.
88    #[must_use]
89    pub fn long(&self) -> Option<Vec<i64>> {
90        match self {
91            Self::Long(v) => Some(v.clone()),
92            _ => None,
93        }
94    }
95    /// Returns a clone of the underlying [`u64`] data, if the type matches.
96    #[must_use]
97    pub fn ulong(&self) -> Option<Vec<u64>> {
98        match self {
99            Self::ULong(v) => Some(v.clone()),
100            _ => None,
101        }
102    }
103    /// Returns a clone of the underlying [`f64`] data, if the type matches.
104    #[must_use]
105    pub fn double(&self) -> Option<Vec<f64>> {
106        match self {
107            Self::Double(v) => Some(v.clone()),
108            _ => None,
109        }
110    }
111    /// Returns a clone of the underlying [`bool`] data, if the type matches.
112    #[must_use]
113    pub fn bool(&self) -> Option<Vec<bool>> {
114        match self {
115            Self::Bool(v) => Some(v.clone()),
116            _ => None,
117        }
118    }
119    /// Returns a clone of the underlying [`String`] data, if the type matches.
120    #[must_use]
121    pub fn string(&self) -> Option<Vec<String>> {
122        match self {
123            Self::String(v) => Some(v.clone()),
124            _ => None,
125        }
126    }
127}
128
129/// Borrowed view into a single cell of CCDB data.
130#[derive(Debug, Copy, Clone)]
131pub enum Value<'a> {
132    /// Signed 32-bit integer cell.
133    Int(&'a i32),
134    /// Unsigned 32-bit integer cell.
135    UInt(&'a u32),
136    /// Signed 64-bit integer cell.
137    Long(&'a i64),
138    /// Unsigned 64-bit integer cell.
139    ULong(&'a u64),
140    /// Floating-point cell.
141    Double(&'a f64),
142    /// Boolean cell.
143    Bool(&'a bool),
144    /// UTF-8 string cell.
145    String(&'a str),
146}
147impl<'a> Value<'a> {
148    /// Converts to [`i32`] if this is an integer cell.
149    #[must_use]
150    pub fn as_int(self) -> Option<i32> {
151        if let Value::Int(v) = self {
152            Some(*v)
153        } else {
154            None
155        }
156    }
157
158    /// Converts to [`u32`] if this is an unsigned integer cell.
159    #[must_use]
160    pub fn as_uint(self) -> Option<u32> {
161        if let Value::UInt(v) = self {
162            Some(*v)
163        } else {
164            None
165        }
166    }
167
168    /// Converts to [`i64`] if this is a 64-bit integer cell.
169    #[must_use]
170    pub fn as_long(self) -> Option<i64> {
171        if let Value::Long(v) = self {
172            Some(*v)
173        } else {
174            None
175        }
176    }
177
178    /// Converts to [`u64`] if this is an unsigned 64-bit integer cell.
179    #[must_use]
180    pub fn as_ulong(self) -> Option<u64> {
181        if let Value::ULong(v) = self {
182            Some(*v)
183        } else {
184            None
185        }
186    }
187
188    /// Converts to [`f64`] if this is a floating-point cell.
189    #[must_use]
190    pub fn as_double(self) -> Option<f64> {
191        if let Value::Double(v) = self {
192            Some(*v)
193        } else {
194            None
195        }
196    }
197
198    /// Converts to [`bool`] if this is a boolean cell.
199    #[must_use]
200    pub fn as_bool(self) -> Option<bool> {
201        if let Value::Bool(v) = self {
202            Some(*v)
203        } else {
204            None
205        }
206    }
207
208    /// Converts to [`&str`] if this is a string cell.
209    #[must_use]
210    pub fn as_str(self) -> Option<&'a str> {
211        if let Value::String(v) = self {
212            Some(v)
213        } else {
214            None
215        }
216    }
217}
218
219/// Borrowed view over a single row of a [`Data`] table.
220#[derive(Debug, Clone, Copy)]
221pub struct RowView<'a> {
222    row: usize,
223    columns: &'a [Column],
224    column_names: &'a [String],
225    column_indices: &'a HashMap<String, usize>,
226    column_types: &'a [ColumnType],
227}
228impl<'a> RowView<'a> {
229    /// Returns a typed cell by positional column index.
230    #[must_use]
231    pub fn value(&self, column: usize) -> Option<Value<'a>> {
232        self.columns.get(column).map(|col| col.row(self.row))
233    }
234
235    /// Returns a typed cell by column name.
236    #[must_use]
237    pub fn named_value(&self, name: &str) -> Option<Value<'a>> {
238        self.column_indices
239            .get(name)
240            .and_then(|&idx| self.value(idx))
241    }
242
243    /// Returns a positional column as [`i32`] if present and typed accordingly.
244    #[must_use]
245    pub fn int(&self, column: usize) -> Option<i32> {
246        self.value(column)?.as_int()
247    }
248    /// Returns a positional column as [`u32`] if present and typed accordingly.
249    #[must_use]
250    pub fn uint(&self, column: usize) -> Option<u32> {
251        self.value(column)?.as_uint()
252    }
253    /// Returns a positional column as [`i64`] if present and typed accordingly.
254    #[must_use]
255    pub fn long(&self, column: usize) -> Option<i64> {
256        self.value(column)?.as_long()
257    }
258    /// Returns a positional column as [`u64`] if present and typed accordingly.
259    #[must_use]
260    pub fn ulong(&self, column: usize) -> Option<u64> {
261        self.value(column)?.as_ulong()
262    }
263    /// Returns a positional column as [`f64`] if present and typed accordingly.
264    #[must_use]
265    pub fn double(&self, column: usize) -> Option<f64> {
266        self.value(column)?.as_double()
267    }
268    /// Returns a positional column as [`&str`] if present and typed accordingly.
269    #[must_use]
270    pub fn string(&self, column: usize) -> Option<&'a str> {
271        self.value(column)?.as_str()
272    }
273    /// Returns a positional column as [`bool`] if present and typed accordingly.
274    #[must_use]
275    pub fn bool(&self, column: usize) -> Option<bool> {
276        self.value(column)?.as_bool()
277    }
278
279    /// Returns a named column as [`i32`] if present and typed accordingly.
280    #[must_use]
281    pub fn named_int(&self, name: &str) -> Option<i32> {
282        self.named_value(name)?.as_int()
283    }
284    /// Returns a named column as [`u32`] if present and typed accordingly.
285    #[must_use]
286    pub fn named_uint(&self, name: &str) -> Option<u32> {
287        self.named_value(name)?.as_uint()
288    }
289    /// Returns a named column as [`i64`] if present and typed accordingly.
290    #[must_use]
291    pub fn named_long(&self, name: &str) -> Option<i64> {
292        self.named_value(name)?.as_long()
293    }
294    /// Returns a named column as [`u64`] if present and typed accordingly.
295    #[must_use]
296    pub fn named_ulong(&self, name: &str) -> Option<u64> {
297        self.named_value(name)?.as_ulong()
298    }
299    /// Returns a named column as [`f64`] if present and typed accordingly.
300    #[must_use]
301    pub fn named_double(&self, name: &str) -> Option<f64> {
302        self.named_value(name)?.as_double()
303    }
304    /// Returns a named column as [`&str`] if present and typed accordingly.
305    #[must_use]
306    pub fn named_string(&self, name: &str) -> Option<&'a str> {
307        self.named_value(name)?.as_str()
308    }
309    /// Returns a named column as [`bool`] if present and typed accordingly.
310    #[must_use]
311    pub fn named_bool(&self, name: &str) -> Option<bool> {
312        self.named_value(name)?.as_bool()
313    }
314
315    /// Iterates over `(name, type, value)` tuples for the current row.
316    pub fn iter_columns(&self) -> impl Iterator<Item = (&'a str, ColumnType, Value<'a>)> + '_ {
317        izip!(
318            self.column_names.iter(),
319            self.column_types.iter(),
320            self.columns.iter()
321        )
322        .map(move |(name, column_type, col)| (name.as_str(), *column_type, col.row(self.row)))
323    }
324
325    /// Checks whether the row contains a column with the given name.
326    #[must_use]
327    pub fn contains(&self, name: &str) -> bool {
328        self.column_indices.contains_key(name)
329    }
330
331    /// Number of columns in this row.
332    #[must_use]
333    pub fn n_columns(&self) -> usize {
334        self.columns.len()
335    }
336
337    /// Column types in this row in positional order.
338    #[must_use]
339    pub fn column_types(&self) -> &'a [ColumnType] {
340        self.column_types
341    }
342}
343
344/// Description of a column in a CCDB table.
345#[derive(Debug, Clone)]
346pub struct ColumnDef {
347    /// Zero-based column position.
348    pub index: usize,
349    /// Column name as stored in CCDB metadata.
350    pub name: String,
351    /// Logical column type for this field.
352    pub column_type: ColumnType,
353}
354
355/// Immutable layout information for a table's columns.
356#[derive(Debug, Clone)]
357pub struct ColumnLayout {
358    columns: Vec<ColumnMeta>,
359    column_names: Vec<String>,
360    column_indices: HashMap<String, usize>,
361    column_types: Vec<ColumnType>,
362}
363
364impl ColumnLayout {
365    /// Builds a layout from ordered column metadata.
366    #[must_use]
367    pub fn new(mut columns: Vec<ColumnMeta>) -> Self {
368        columns.sort_unstable_by_key(|c| c.order);
369        let column_names: Vec<String> = columns
370            .iter()
371            .enumerate()
372            .map(|(i, c)| {
373                if c.name.is_empty() {
374                    i.to_string()
375                } else {
376                    c.name.clone()
377                }
378            })
379            .collect();
380        let column_types: Vec<ColumnType> = columns.iter().map(|c| c.column_type).collect();
381        let column_indices: HashMap<String, usize> = column_names
382            .iter()
383            .enumerate()
384            .map(|(idx, name)| (name.clone(), idx))
385            .collect();
386        Self {
387            columns,
388            column_names,
389            column_indices,
390            column_types,
391        }
392    }
393
394    /// Sorted column metadata.
395    #[must_use]
396    pub fn columns(&self) -> &[ColumnMeta] {
397        &self.columns
398    }
399
400    /// Column names in positional order.
401    #[must_use]
402    pub fn column_names(&self) -> &[String] {
403        &self.column_names
404    }
405
406    /// Column name to positional index lookup table.
407    #[must_use]
408    pub fn column_indices(&self) -> &HashMap<String, usize> {
409        &self.column_indices
410    }
411
412    /// Column types in positional order.
413    #[must_use]
414    pub fn column_types(&self) -> &[ColumnType] {
415        &self.column_types
416    }
417
418    /// Number of columns described by this layout.
419    #[must_use]
420    pub fn column_count(&self) -> usize {
421        self.columns.len()
422    }
423}
424
425/// Column-major table returned from CCDB fetch operations.
426#[derive(Debug, Clone)]
427pub struct Data {
428    n_rows: usize,
429    layout: Arc<ColumnLayout>,
430    columns: Vec<Column>,
431}
432
433impl Data {
434    /// Builds a [`Data`] table from a raw vault string and column metadata.
435    ///
436    /// # Errors
437    ///
438    /// This method will return an error if the parsed number of columns does not equal the
439    /// expected number from the database or if any of the column contents cannot be parsed into
440    /// their respective data types.
441    pub fn from_vault(vault: &str, layout: Arc<ColumnLayout>, n_rows: usize) -> CCDBResult<Self> {
442        let n_columns = layout.column_count();
443        let expected_cells = n_rows * n_columns;
444        let column_types = layout.column_types();
445        let mut column_vecs: Vec<Column> = column_types
446            .iter()
447            .map(|t| match t {
448                ColumnType::Int => Column::Int(Vec::with_capacity(n_rows)),
449                ColumnType::UInt => Column::UInt(Vec::with_capacity(n_rows)),
450                ColumnType::Long => Column::Long(Vec::with_capacity(n_rows)),
451                ColumnType::ULong => Column::ULong(Vec::with_capacity(n_rows)),
452                ColumnType::Double => Column::Double(Vec::with_capacity(n_rows)),
453                ColumnType::String => Column::String(Vec::with_capacity(n_rows)),
454                ColumnType::Bool => Column::Bool(Vec::with_capacity(n_rows)),
455            })
456            .collect();
457        let mut raw_iter = VaultFieldIter::new(vault);
458        for idx in 0..expected_cells {
459            let Some(raw) = raw_iter.next() else {
460                return Err(CCDBError::ColumnCountMismatch {
461                    expected: expected_cells,
462                    found: idx,
463                });
464            };
465            let row = idx / n_columns;
466            let col = idx % n_columns;
467            let column_type = column_types[col];
468
469            match (&mut column_vecs[col], column_type) {
470                (Column::Int(vec), ColumnType::Int) => {
471                    vec.push(raw.parse().map_err(|_| CCDBError::ParseError {
472                        column: col,
473                        row,
474                        column_type,
475                        text: raw.to_string(),
476                    })?);
477                }
478                (Column::UInt(vec), ColumnType::UInt) => {
479                    vec.push(raw.parse().map_err(|_| CCDBError::ParseError {
480                        column: col,
481                        row,
482                        column_type,
483                        text: raw.to_string(),
484                    })?);
485                }
486                (Column::Long(vec), ColumnType::Long) => {
487                    vec.push(raw.parse().map_err(|_| CCDBError::ParseError {
488                        column: col,
489                        row,
490                        column_type,
491                        text: raw.to_string(),
492                    })?);
493                }
494                (Column::ULong(vec), ColumnType::ULong) => {
495                    vec.push(raw.parse().map_err(|_| CCDBError::ParseError {
496                        column: col,
497                        row,
498                        column_type,
499                        text: raw.to_string(),
500                    })?);
501                }
502                (Column::Double(vec), ColumnType::Double) => {
503                    vec.push(raw.parse().map_err(|_| CCDBError::ParseError {
504                        column: col,
505                        row,
506                        column_type,
507                        text: raw.to_string(),
508                    })?);
509                }
510                (Column::String(vec), ColumnType::String) => {
511                    let decoded = raw.replace("&delimeter", "|");
512                    vec.push(decoded);
513                }
514                (Column::Bool(vec), ColumnType::Bool) => {
515                    vec.push(parse_bool(raw));
516                }
517                _ => unreachable!("column type mismatch"),
518            }
519        }
520        if raw_iter.next().is_some() {
521            let found = expected_cells + 1 + raw_iter.count();
522            return Err(CCDBError::ColumnCountMismatch {
523                expected: expected_cells,
524                found,
525            });
526        }
527        Ok(Data {
528            n_rows,
529            layout,
530            columns: column_vecs,
531        })
532    }
533
534    /// Number of rows in the dataset.
535    #[must_use]
536    pub fn n_rows(&self) -> usize {
537        self.n_rows
538    }
539    /// Number of columns in the dataset.
540    #[must_use]
541    pub fn n_columns(&self) -> usize {
542        self.layout.column_count()
543    }
544    /// Column names in positional order.
545    #[must_use]
546    pub fn column_names(&self) -> &[String] {
547        self.layout.column_names()
548    }
549
550    /// Column types in positional order.
551    #[must_use]
552    pub fn column_types(&self) -> &[ColumnType] {
553        self.layout.column_types()
554    }
555
556    /// Returns a borrowed column by positional index.
557    #[must_use]
558    pub fn column(&self, idx: usize) -> Option<&Column> {
559        self.columns.get(idx)
560    }
561
562    /// Returns a borrowed column by name.
563    #[must_use]
564    pub fn named_column(&self, name: &str) -> Option<&Column> {
565        self.layout
566            .column_indices()
567            .get(name)
568            .and_then(|idx| self.columns.get(*idx))
569    }
570
571    /// Returns a cloned column by positional index.
572    #[must_use]
573    pub fn column_clone(&self, idx: usize) -> Option<Column> {
574        self.columns.get(idx).cloned()
575    }
576
577    /// Returns a cloned column by name.
578    #[must_use]
579    pub fn named_column_clone(&self, name: &str) -> Option<Column> {
580        self.layout
581            .column_indices()
582            .get(name)
583            .and_then(|idx| self.columns.get(*idx))
584            .cloned()
585    }
586
587    /// Returns a single cell value by column and row index.
588    #[must_use]
589    pub fn value(&self, column: usize, row: usize) -> Option<Value<'_>> {
590        if row >= self.n_rows || column >= self.layout.column_count() {
591            return None;
592        }
593        match self.columns.get(column)? {
594            Column::Int(v) => Some(Value::Int(&v[row])),
595            Column::UInt(v) => Some(Value::UInt(&v[row])),
596            Column::Long(v) => Some(Value::Long(&v[row])),
597            Column::ULong(v) => Some(Value::ULong(&v[row])),
598            Column::Double(v) => Some(Value::Double(&v[row])),
599            Column::Bool(v) => Some(Value::Bool(&v[row])),
600            Column::String(v) => Some(Value::String(&v[row])),
601        }
602    }
603    /// Returns a named cell as [`i32`] if present and typed accordingly.
604    #[must_use]
605    pub fn named_int(&self, name: &str, row: usize) -> Option<i32> {
606        self.named_column(name)?.row(row).as_int()
607    }
608    /// Returns a named cell as [`u32`] if present and typed accordingly.
609    #[must_use]
610    pub fn named_uint(&self, name: &str, row: usize) -> Option<u32> {
611        self.named_column(name)?.row(row).as_uint()
612    }
613    /// Returns a named cell as [`i64`] if present and typed accordingly.
614    #[must_use]
615    pub fn named_long(&self, name: &str, row: usize) -> Option<i64> {
616        self.named_column(name)?.row(row).as_long()
617    }
618    /// Returns a named cell as [`u64`] if present and typed accordingly.
619    #[must_use]
620    pub fn named_ulong(&self, name: &str, row: usize) -> Option<u64> {
621        self.named_column(name)?.row(row).as_ulong()
622    }
623    /// Returns a named cell as [`f64`] if present and typed accordingly.
624    #[must_use]
625    pub fn named_double(&self, name: &str, row: usize) -> Option<f64> {
626        self.named_column(name)?.row(row).as_double()
627    }
628    /// Returns a named cell as [`&str`] if present and typed accordingly.
629    #[must_use]
630    pub fn named_string(&self, name: &str, row: usize) -> Option<&str> {
631        self.named_column(name)?.row(row).as_str()
632    }
633    /// Returns a named cell as [`bool`] if present and typed accordingly.
634    #[must_use]
635    pub fn named_bool(&self, name: &str, row: usize) -> Option<bool> {
636        self.named_column(name)?.row(row).as_bool()
637    }
638
639    /// Returns a positional cell as [`i32`] if present and typed accordingly.
640    #[must_use]
641    pub fn int(&self, column: usize, row: usize) -> Option<i32> {
642        self.value(column, row)?.as_int()
643    }
644    /// Returns a positional cell as [`u32`] if present and typed accordingly.
645    #[must_use]
646    pub fn uint(&self, column: usize, row: usize) -> Option<u32> {
647        self.value(column, row)?.as_uint()
648    }
649    /// Returns a positional cell as [`i64`] if present and typed accordingly.
650    #[must_use]
651    pub fn long(&self, column: usize, row: usize) -> Option<i64> {
652        self.value(column, row)?.as_long()
653    }
654    /// Returns a positional cell as [`u64`] if present and typed accordingly.
655    #[must_use]
656    pub fn ulong(&self, column: usize, row: usize) -> Option<u64> {
657        self.value(column, row)?.as_ulong()
658    }
659    /// Returns a positional cell as [`f64`] if present and typed accordingly.
660    #[must_use]
661    pub fn double(&self, column: usize, row: usize) -> Option<f64> {
662        self.value(column, row)?.as_double()
663    }
664    /// Returns a positional cell as [`&str`] if present and typed accordingly.
665    #[must_use]
666    pub fn string(&self, column: usize, row: usize) -> Option<&str> {
667        self.value(column, row)?.as_str()
668    }
669    /// Returns a positional cell as [`bool`] if present and typed accordingly.
670    #[must_use]
671    pub fn bool(&self, column: usize, row: usize) -> Option<bool> {
672        self.value(column, row)?.as_bool()
673    }
674
675    /// Returns a borrowed view of a single row, or an error if out of bounds.
676    ///
677    /// # Errors
678    ///
679    /// This method will return an error if `row` is out of bounds.
680    pub fn row(&self, row: usize) -> CCDBResult<RowView<'_>> {
681        if row >= self.n_rows {
682            return Err(CCDBError::RowOutOfBounds {
683                requested: row,
684                n_rows: self.n_rows,
685            });
686        }
687        let layout = self.layout.as_ref();
688        Ok(RowView {
689            row,
690            columns: &self.columns,
691            column_names: layout.column_names(),
692            column_indices: layout.column_indices(),
693            column_types: layout.column_types(),
694        })
695    }
696
697    /// Iterates over all rows in the dataset.
698    pub fn iter_rows(&self) -> impl Iterator<Item = RowView<'_>> {
699        let layout = self.layout.as_ref();
700        let columns = &self.columns;
701        let column_names = layout.column_names();
702        let column_indices = layout.column_indices();
703        let column_types = layout.column_types();
704        (0..self.n_rows).map(move |row| RowView {
705            row,
706            columns,
707            column_names,
708            column_indices,
709            column_types,
710        })
711    }
712
713    /// Iterates over `(name, type, column)` tuples for each column.
714    pub fn iter_columns(&self) -> impl Iterator<Item = (&String, &ColumnType, &Column)> {
715        izip!(
716            self.layout.column_names().iter(),
717            self.layout.column_types().iter(),
718            self.columns.iter()
719        )
720    }
721
722    /// True if a column with the given name exists.
723    #[must_use]
724    pub fn contains(&self, name: &str) -> bool {
725        self.layout.column_indices().contains_key(name)
726    }
727}
728
729struct VaultFieldIter<'a> {
730    input: &'a str,
731    cursor: usize,
732    finished: bool,
733}
734
735impl<'a> VaultFieldIter<'a> {
736    fn new(input: &'a str) -> Self {
737        Self {
738            input,
739            cursor: 0,
740            finished: false,
741        }
742    }
743}
744
745impl<'a> Iterator for VaultFieldIter<'a> {
746    type Item = &'a str;
747
748    fn next(&mut self) -> Option<Self::Item> {
749        if self.finished {
750            return None;
751        }
752        if self.cursor > self.input.len() {
753            self.finished = true;
754            return Some("");
755        }
756        if self.cursor == self.input.len() {
757            self.finished = true;
758            return Some("");
759        }
760        let bytes = self.input.as_bytes();
761        if let Some(pos) = memchr(b'|', &bytes[self.cursor..]) {
762            let start = self.cursor;
763            let end = start + pos;
764            self.cursor = end + 1;
765            Some(&self.input[start..end])
766        } else {
767            self.finished = true;
768            let start = self.cursor;
769            Some(&self.input[start..])
770        }
771    }
772}
773
774fn parse_bool(s: &str) -> bool {
775    if s == "true" {
776        return true;
777    }
778    if s == "false" {
779        return false;
780    }
781    s.parse::<i32>().unwrap_or(0) != 0
782}