Skip to main content

eure_document/
path.rs

1use core::fmt::Display;
2
3use crate::{prelude_internal::*, value::PartialObjectKey};
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    /// Partial-map key that cannot be losslessly represented as ObjectKey
29    PartialValue(PartialObjectKey),
30    /// Tuple element index (0-255)
31    TupleIndex(u8),
32    /// Array element access
33    ArrayIndex(Option<usize>),
34    /// A hole key in a PartialMap: `!` or `!label`
35    HoleKey(Option<Identifier>),
36}
37
38impl PathSegment {
39    pub fn from_partial_object_key(key: PartialObjectKey) -> Self {
40        match key {
41            PartialObjectKey::Number(n) => Self::Value(ObjectKey::Number(n)),
42            PartialObjectKey::String(s) => Self::Value(ObjectKey::String(s)),
43            PartialObjectKey::Hole(label) => Self::HoleKey(label),
44            PartialObjectKey::Tuple(items) => Self::PartialValue(PartialObjectKey::Tuple(items)),
45        }
46    }
47}
48
49impl Display for EurePath {
50    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
51        if self.0.is_empty() {
52            return write!(f, "(root)");
53        }
54        for (i, segment) in self.0.iter().enumerate() {
55            let is_first = i == 0;
56            match segment {
57                PathSegment::Ident(id) => {
58                    if !is_first {
59                        write!(f, ".")?;
60                    }
61                    write!(f, "{}", id)?;
62                }
63                PathSegment::Extension(id) => {
64                    if !is_first {
65                        write!(f, ".")?;
66                    }
67                    write!(f, "${}", id)?;
68                }
69                PathSegment::Value(key) => {
70                    if !is_first {
71                        write!(f, ".")?;
72                    }
73                    write!(f, "{}", key)?;
74                }
75                PathSegment::PartialValue(key) => {
76                    if !is_first {
77                        write!(f, ".")?;
78                    }
79                    write!(f, "{}", key)?;
80                }
81                PathSegment::TupleIndex(index) => {
82                    if !is_first {
83                        write!(f, ".")?;
84                    }
85                    write!(f, "#{}", index)?;
86                }
87                PathSegment::ArrayIndex(Some(index)) => write!(f, "[{}]", index)?,
88                PathSegment::ArrayIndex(None) => write!(f, "[]")?,
89                PathSegment::HoleKey(None) => {
90                    if !is_first {
91                        write!(f, ".")?;
92                    }
93                    write!(f, "!")?;
94                }
95                PathSegment::HoleKey(Some(label)) => {
96                    if !is_first {
97                        write!(f, ".")?;
98                    }
99                    write!(f, "!{}", label)?;
100                }
101            }
102        }
103        Ok(())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use alloc::format;
110
111    use super::*;
112    use crate::value::{ObjectKey, PartialObjectKey, Tuple};
113
114    #[test]
115    fn test_display_empty_path() {
116        let path = EurePath::root();
117        assert_eq!(format!("{}", path), "(root)");
118    }
119
120    #[test]
121    fn test_display_single_ident() {
122        let path = EurePath(vec![PathSegment::Ident(Identifier::new_unchecked("name"))]);
123        assert_eq!(format!("{}", path), "name");
124    }
125
126    #[test]
127    fn test_display_nested_idents() {
128        let path = EurePath(vec![
129            PathSegment::Ident(Identifier::new_unchecked("a")),
130            PathSegment::Ident(Identifier::new_unchecked("b")),
131            PathSegment::Ident(Identifier::new_unchecked("c")),
132        ]);
133        assert_eq!(format!("{}", path), "a.b.c");
134    }
135
136    #[test]
137    fn test_display_extension() {
138        let path = EurePath(vec![PathSegment::Extension(Identifier::new_unchecked(
139            "variant",
140        ))]);
141        assert_eq!(format!("{}", path), "$variant");
142    }
143
144    #[test]
145    fn test_display_array_index() {
146        let path = EurePath(vec![
147            PathSegment::Ident(Identifier::new_unchecked("items")),
148            PathSegment::ArrayIndex(Some(0)),
149        ]);
150        assert_eq!(format!("{}", path), "items[0]");
151    }
152
153    #[test]
154    fn test_display_array_index_none() {
155        let path = EurePath(vec![
156            PathSegment::Ident(Identifier::new_unchecked("items")),
157            PathSegment::ArrayIndex(None),
158        ]);
159        assert_eq!(format!("{}", path), "items[]");
160    }
161
162    #[test]
163    fn test_display_tuple_index() {
164        let path = EurePath(vec![
165            PathSegment::Ident(Identifier::new_unchecked("point")),
166            PathSegment::TupleIndex(1),
167        ]);
168        assert_eq!(format!("{}", path), "point.#1");
169    }
170
171    #[test]
172    fn test_display_string_key() {
173        let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
174            "hello".to_string(),
175        ))]);
176        assert_eq!(format!("{}", path), "\"hello\"");
177    }
178
179    #[test]
180    fn test_display_string_key_with_spaces() {
181        let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
182            "hello world".to_string(),
183        ))]);
184        assert_eq!(format!("{}", path), "\"hello world\"");
185    }
186
187    #[test]
188    fn test_display_string_key_with_quotes() {
189        let path = EurePath(vec![PathSegment::Value(ObjectKey::String(
190            "say \"hi\"".to_string(),
191        ))]);
192        assert_eq!(format!("{}", path), "\"say \\\"hi\\\"\"");
193    }
194
195    #[test]
196    fn test_display_number_key() {
197        let path = EurePath(vec![PathSegment::Value(ObjectKey::Number(42.into()))]);
198        assert_eq!(format!("{}", path), "42");
199    }
200
201    #[test]
202    fn test_display_bool_key() {
203        // Boolean identifiers in key position become string keys
204        let path = EurePath(vec![PathSegment::Value(ObjectKey::String("true".into()))]);
205        assert_eq!(format!("{}", path), "\"true\"");
206    }
207
208    #[test]
209    fn test_display_complex_path() {
210        let path = EurePath(vec![
211            PathSegment::Ident(Identifier::new_unchecked("config")),
212            PathSegment::Extension(Identifier::new_unchecked("eure")),
213            PathSegment::Ident(Identifier::new_unchecked("items")),
214            PathSegment::ArrayIndex(Some(0)),
215            PathSegment::Value(ObjectKey::String("key with space".to_string())),
216        ]);
217        assert_eq!(
218            format!("{}", path),
219            "config.$eure.items[0].\"key with space\""
220        );
221    }
222
223    #[test]
224    fn test_display_partial_tuple_key() {
225        let path = EurePath(vec![PathSegment::PartialValue(PartialObjectKey::Tuple(
226            Tuple(vec![
227                PartialObjectKey::Number(1.into()),
228                PartialObjectKey::Hole(None),
229            ]),
230        ))]);
231        assert_eq!(format!("{}", path), "(1, !)");
232    }
233}