eure_document/
path.rs

1use core::fmt::Display;
2
3use crate::prelude_internal::*;
4
5#[derive(Debug, Clone, PartialEq, Eq, Hash, Plural)]
6pub struct EurePath(pub Vec<PathSegment>);
7
8impl EurePath {
9    /// Create an empty path representing the document root
10    pub fn root() -> Self {
11        EurePath(Vec::new())
12    }
13
14    /// Check if this is the root path
15    pub fn is_root(&self) -> bool {
16        self.0.is_empty()
17    }
18}
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
21pub enum PathSegment {
22    /// Regular identifiers like id, description
23    Ident(Identifier),
24    /// Extension namespace fields starting with $ like $eure, $variant
25    Extension(Identifier),
26    /// Arbitrary value used as key
27    Value(ObjectKey),
28    /// Tuple element index (0-255)
29    TupleIndex(u8),
30    /// Array element access
31    ArrayIndex(Option<usize>),
32}
33
34impl Display for EurePath {
35    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36        if self.0.is_empty() {
37            return write!(f, "(root)");
38        }
39        for (i, segment) in self.0.iter().enumerate() {
40            let is_first = i == 0;
41            match segment {
42                PathSegment::Ident(id) => {
43                    if !is_first {
44                        write!(f, ".")?;
45                    }
46                    write!(f, "{}", id)?;
47                }
48                PathSegment::Extension(id) => {
49                    if !is_first {
50                        write!(f, ".")?;
51                    }
52                    write!(f, "${}", id)?;
53                }
54                PathSegment::Value(key) => {
55                    if !is_first {
56                        write!(f, ".")?;
57                    }
58                    write!(f, "{}", key)?;
59                }
60                PathSegment::TupleIndex(index) => {
61                    if !is_first {
62                        write!(f, ".")?;
63                    }
64                    write!(f, "#{}", index)?;
65                }
66                PathSegment::ArrayIndex(Some(index)) => write!(f, "[{}]", index)?,
67                PathSegment::ArrayIndex(None) => write!(f, "[]")?,
68            }
69        }
70        Ok(())
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use alloc::format;
77
78    use super::*;
79    use crate::value::ObjectKey;
80
81    #[test]
82    fn test_display_empty_path() {
83        let path = EurePath::root();
84        assert_eq!(format!("{}", path), "(root)");
85    }
86
87    #[test]
88    fn test_display_single_ident() {
89        let path = EurePath(vec![PathSegment::Ident(Identifier::new_unchecked("name"))]);
90        assert_eq!(format!("{}", path), "name");
91    }
92
93    #[test]
94    fn test_display_nested_idents() {
95        let path = EurePath(vec![
96            PathSegment::Ident(Identifier::new_unchecked("a")),
97            PathSegment::Ident(Identifier::new_unchecked("b")),
98            PathSegment::Ident(Identifier::new_unchecked("c")),
99        ]);
100        assert_eq!(format!("{}", path), "a.b.c");
101    }
102
103    #[test]
104    fn test_display_extension() {
105        let path = EurePath(vec![PathSegment::Extension(Identifier::new_unchecked(
106            "variant",
107        ))]);
108        assert_eq!(format!("{}", path), "$variant");
109    }
110
111    #[test]
112    fn test_display_array_index() {
113        let path = EurePath(vec![
114            PathSegment::Ident(Identifier::new_unchecked("items")),
115            PathSegment::ArrayIndex(Some(0)),
116        ]);
117        assert_eq!(format!("{}", path), "items[0]");
118    }
119
120    #[test]
121    fn test_display_array_index_none() {
122        let path = EurePath(vec![
123            PathSegment::Ident(Identifier::new_unchecked("items")),
124            PathSegment::ArrayIndex(None),
125        ]);
126        assert_eq!(format!("{}", path), "items[]");
127    }
128
129    #[test]
130    fn test_display_tuple_index() {
131        let path = EurePath(vec![
132            PathSegment::Ident(Identifier::new_unchecked("point")),
133            PathSegment::TupleIndex(1),
134        ]);
135        assert_eq!(format!("{}", path), "point.#1");
136    }
137
138    #[test]
139    fn test_display_string_key() {
140        let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
141            "hello".to_string(),
142        ))]);
143        assert_eq!(format!("{}", path), "\"hello\"");
144    }
145
146    #[test]
147    fn test_display_string_key_with_spaces() {
148        let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
149            "hello world".to_string(),
150        ))]);
151        assert_eq!(format!("{}", path), "\"hello world\"");
152    }
153
154    #[test]
155    fn test_display_string_key_with_quotes() {
156        let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
157            "say \"hi\"".to_string(),
158        ))]);
159        assert_eq!(format!("{}", path), "\"say \\\"hi\\\"\"");
160    }
161
162    #[test]
163    fn test_display_number_key() {
164        let path = EurePath(vec![PathSegment::Value(ObjectKey::Number(42.into()))]);
165        assert_eq!(format!("{}", path), "42");
166    }
167
168    #[test]
169    fn test_display_bool_key() {
170        // Boolean identifiers in key position become string keys
171        let path = EurePath(vec![PathSegment::Value(ObjectKey::String("true".into()))]);
172        assert_eq!(format!("{}", path), "\"true\"");
173    }
174
175    #[test]
176    fn test_display_complex_path() {
177        let path = EurePath(vec![
178            PathSegment::Ident(Identifier::new_unchecked("config")),
179            PathSegment::Extension(Identifier::new_unchecked("eure")),
180            PathSegment::Ident(Identifier::new_unchecked("items")),
181            PathSegment::ArrayIndex(Some(0)),
182            PathSegment::Value(ObjectKey::String("key with space".to_string())),
183        ]);
184        assert_eq!(
185            format!("{}", path),
186            "config.$eure.items[0].\"key with space\""
187        );
188    }
189}