Skip to main content

base_web_content/
field_path.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use std::fmt;
5
6/// The path to a field: the declared names and array indices that lead to it.
7///
8/// It holds only names the declaration gives and indices the parser counted, never submitted text, so it is safe to log.
9#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
10pub struct FieldPath {
11    segments: Vec<FieldPathSegment>,
12}
13
14/// One step of a [`FieldPath`].
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum FieldPathSegment {
17    /// A declared field, by the name a form submits.
18    Name(&'static str),
19    /// An element of an array, counting from zero in the order the elements arrived.
20    Index(usize),
21}
22
23impl FieldPath {
24    /// The segments, from the top level.
25    pub fn segments(&self) -> &[FieldPathSegment] {
26        &self.segments
27    }
28
29    pub(crate) fn push(&mut self, segment: FieldPathSegment) {
30        self.segments.push(segment);
31    }
32
33    pub(crate) fn pop(&mut self) {
34        self.segments.pop();
35    }
36
37    pub(crate) fn extend(&mut self, segments: impl IntoIterator<Item = FieldPathSegment>) {
38        self.segments.extend(segments);
39    }
40}
41
42/// Writes the path as a form names it, with indices in brackets, such as `user[address][city]` or `items[1][count]`.
43impl fmt::Display for FieldPath {
44    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
45        let mut text = String::new();
46        for (position, segment) in self.segments.iter().enumerate() {
47            match (position, segment) {
48                (0, FieldPathSegment::Name(name)) => text.push_str(name),
49                (_, FieldPathSegment::Name(name)) => text.push_str(&format!("[{name}]")),
50                (_, FieldPathSegment::Index(index)) => text.push_str(&format!("[{index}]")),
51            }
52        }
53        formatter.write_str(&text)
54    }
55}