Skip to main content

tirea_state/
path.rs

1//! JSON path representation for navigating document structure.
2//!
3//! Paths are sequences of segments that describe a location in a JSON document.
4//! Each segment is either a key (for objects) or an index (for arrays).
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// A single segment in a JSON path.
10#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11#[serde(untagged)]
12pub enum Seg {
13    /// Object key access: `{"key": value}`
14    Key(String),
15    /// Array index access: `[index]`
16    Index(usize),
17}
18
19impl Seg {
20    /// Create a key segment.
21    #[inline]
22    pub fn key(k: impl Into<String>) -> Self {
23        Seg::Key(k.into())
24    }
25
26    /// Create an index segment.
27    #[inline]
28    pub fn index(i: usize) -> Self {
29        Seg::Index(i)
30    }
31
32    /// Returns true if this is a key segment.
33    #[inline]
34    pub fn is_key(&self) -> bool {
35        matches!(self, Seg::Key(_))
36    }
37
38    /// Returns true if this is an index segment.
39    #[inline]
40    pub fn is_index(&self) -> bool {
41        matches!(self, Seg::Index(_))
42    }
43
44    /// Get the key if this is a key segment.
45    #[inline]
46    pub fn as_key(&self) -> Option<&str> {
47        match self {
48            Seg::Key(k) => Some(k),
49            Seg::Index(_) => None,
50        }
51    }
52
53    /// Get the index if this is an index segment.
54    #[inline]
55    pub fn as_index(&self) -> Option<usize> {
56        match self {
57            Seg::Key(_) => None,
58            Seg::Index(i) => Some(*i),
59        }
60    }
61}
62
63impl fmt::Display for Seg {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Seg::Key(k) => write!(f, ".{}", k),
67            Seg::Index(i) => write!(f, "[{}]", i),
68        }
69    }
70}
71
72impl From<String> for Seg {
73    fn from(s: String) -> Self {
74        Seg::Key(s)
75    }
76}
77
78impl From<&str> for Seg {
79    fn from(s: &str) -> Self {
80        Seg::Key(s.to_owned())
81    }
82}
83
84impl From<usize> for Seg {
85    fn from(i: usize) -> Self {
86        Seg::Index(i)
87    }
88}
89
90/// A complete path into a JSON structure.
91///
92/// Paths are immutable sequences of segments. Use builder methods to construct
93/// paths incrementally.
94///
95/// # Examples
96///
97/// ```
98/// use tirea_state::Path;
99///
100/// let path = Path::root().key("users").index(0).key("name");
101/// assert_eq!(path.len(), 3);
102/// ```
103#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
104pub struct Path(Vec<Seg>);
105
106impl Path {
107    /// Create an empty path (root).
108    #[inline]
109    pub fn new() -> Self {
110        Self(Vec::new())
111    }
112
113    /// Create an empty path (alias for `new`).
114    #[inline]
115    pub fn root() -> Self {
116        Self::new()
117    }
118
119    /// Create a path from a vector of segments.
120    #[inline]
121    pub fn from_segments(segments: Vec<Seg>) -> Self {
122        Self(segments)
123    }
124
125    /// Create a path with pre-allocated capacity.
126    #[inline]
127    pub fn with_capacity(capacity: usize) -> Self {
128        Self(Vec::with_capacity(capacity))
129    }
130
131    /// Append a key segment and return self (builder pattern).
132    #[inline]
133    pub fn key(mut self, k: impl Into<String>) -> Self {
134        self.0.push(Seg::Key(k.into()));
135        self
136    }
137
138    /// Append an index segment and return self (builder pattern).
139    #[inline]
140    pub fn index(mut self, i: usize) -> Self {
141        self.0.push(Seg::Index(i));
142        self
143    }
144
145    /// Push a segment onto the path (mutating).
146    #[inline]
147    pub fn push(&mut self, seg: Seg) {
148        self.0.push(seg);
149    }
150
151    /// Push a key segment onto the path (mutating).
152    #[inline]
153    pub fn push_key(&mut self, k: impl Into<String>) {
154        self.0.push(Seg::Key(k.into()));
155    }
156
157    /// Push an index segment onto the path (mutating).
158    #[inline]
159    pub fn push_index(&mut self, i: usize) {
160        self.0.push(Seg::Index(i));
161    }
162
163    /// Pop the last segment from the path.
164    #[inline]
165    pub fn pop(&mut self) -> Option<Seg> {
166        self.0.pop()
167    }
168
169    /// Get the segments of this path.
170    #[inline]
171    pub fn segments(&self) -> &[Seg] {
172        &self.0
173    }
174
175    /// Get a mutable reference to the segments.
176    #[inline]
177    pub fn segments_mut(&mut self) -> &mut Vec<Seg> {
178        &mut self.0
179    }
180
181    /// Check if this path is empty (root).
182    #[inline]
183    pub fn is_empty(&self) -> bool {
184        self.0.is_empty()
185    }
186
187    /// Get the number of segments in this path.
188    #[inline]
189    pub fn len(&self) -> usize {
190        self.0.len()
191    }
192
193    /// Get the first segment.
194    #[inline]
195    pub fn first(&self) -> Option<&Seg> {
196        self.0.first()
197    }
198
199    /// Get the last segment.
200    #[inline]
201    pub fn last(&self) -> Option<&Seg> {
202        self.0.last()
203    }
204
205    /// Join this path with another path.
206    #[inline]
207    pub fn join(&self, other: &Path) -> Path {
208        let mut result = self.clone();
209        result.0.extend(other.0.iter().cloned());
210        result
211    }
212
213    /// Append a segment and return a new path (non-mutating builder).
214    #[inline]
215    pub fn with_segment(&self, seg: Seg) -> Path {
216        let mut result = self.clone();
217        result.0.push(seg);
218        result
219    }
220
221    /// Check if this path is a prefix of another path.
222    ///
223    /// A path is a prefix of another if all of its segments match
224    /// the beginning of the other path's segments.
225    ///
226    /// # Examples
227    ///
228    /// ```
229    /// use tirea_state::path;
230    ///
231    /// let parent = path!("user");
232    /// let child = path!("user", "name");
233    ///
234    /// assert!(parent.is_prefix_of(&child));
235    /// assert!(!child.is_prefix_of(&parent));
236    /// assert!(parent.is_prefix_of(&parent)); // A path is a prefix of itself
237    /// ```
238    #[inline]
239    pub fn is_prefix_of(&self, other: &Path) -> bool {
240        if self.len() > other.len() {
241            return false;
242        }
243        self.0.iter().zip(other.0.iter()).all(|(a, b)| a == b)
244    }
245
246    /// Extend this path with segments from another path.
247    #[inline]
248    pub fn extend(&mut self, other: &Path) {
249        self.0.extend(other.0.iter().cloned());
250    }
251
252    /// Get the parent path (path without the last segment).
253    #[inline]
254    pub fn parent(&self) -> Option<Path> {
255        if self.0.is_empty() {
256            None
257        } else {
258            let mut p = self.clone();
259            p.pop();
260            Some(p)
261        }
262    }
263
264    /// Check if this path starts with another path.
265    #[inline]
266    pub fn starts_with(&self, prefix: &Path) -> bool {
267        self.0.starts_with(&prefix.0)
268    }
269
270    /// Get a slice of segments from start to end.
271    #[inline]
272    pub fn slice(&self, start: usize, end: usize) -> Path {
273        Path(self.0[start..end].to_vec())
274    }
275
276    /// Iterate over the segments.
277    #[inline]
278    pub fn iter(&self) -> impl Iterator<Item = &Seg> {
279        self.0.iter()
280    }
281}
282
283impl fmt::Display for Path {
284    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
285        if self.0.is_empty() {
286            write!(f, "$")
287        } else {
288            write!(f, "$")?;
289            for seg in &self.0 {
290                write!(f, "{}", seg)?;
291            }
292            Ok(())
293        }
294    }
295}
296
297impl FromIterator<Seg> for Path {
298    fn from_iter<I: IntoIterator<Item = Seg>>(iter: I) -> Self {
299        Path(iter.into_iter().collect())
300    }
301}
302
303impl IntoIterator for Path {
304    type Item = Seg;
305    type IntoIter = std::vec::IntoIter<Seg>;
306
307    fn into_iter(self) -> Self::IntoIter {
308        self.0.into_iter()
309    }
310}
311
312impl<'a> IntoIterator for &'a Path {
313    type Item = &'a Seg;
314    type IntoIter = std::slice::Iter<'a, Seg>;
315
316    fn into_iter(self) -> Self::IntoIter {
317        self.0.iter()
318    }
319}
320
321impl std::ops::Index<usize> for Path {
322    type Output = Seg;
323
324    fn index(&self, index: usize) -> &Self::Output {
325        &self.0[index]
326    }
327}
328
329/// Construct a `Path` from a sequence of segments.
330///
331/// # Examples
332///
333/// ```
334/// use tirea_state::path;
335///
336/// // String literals become Key segments
337/// let p = path!("users", "alice", "email");
338///
339/// // Numbers become Index segments
340/// let p = path!("items", 0, "name");
341///
342/// // Mixed types
343/// let p = path!("data", "list", 2, "value");
344/// ```
345#[macro_export]
346macro_rules! path {
347    () => {
348        $crate::Path::root()
349    };
350    ($($seg:expr),+ $(,)?) => {{
351        let mut p = $crate::Path::root();
352        $(
353            p.push($crate::path!(@seg $seg));
354        )+
355        p
356    }};
357    (@seg $seg:expr) => {
358        $crate::Seg::from($seg)
359    };
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    #[test]
367    fn test_path_construction() {
368        let path = Path::root().key("users").index(0).key("name");
369        assert_eq!(path.len(), 3);
370        assert_eq!(path[0], Seg::Key("users".into()));
371        assert_eq!(path[1], Seg::Index(0));
372        assert_eq!(path[2], Seg::Key("name".into()));
373    }
374
375    #[test]
376    fn test_path_display() {
377        let path = Path::root().key("users").index(0).key("name");
378        assert_eq!(format!("{}", path), "$.users[0].name");
379    }
380
381    #[test]
382    fn test_path_macro() {
383        let p = path!("users", 0, "name");
384        assert_eq!(p.len(), 3);
385        assert_eq!(p[0], Seg::Key("users".into()));
386        assert_eq!(p[1], Seg::Index(0));
387        assert_eq!(p[2], Seg::Key("name".into()));
388    }
389
390    #[test]
391    fn test_path_join() {
392        let base = Path::root().key("data");
393        let sub = Path::root().key("items").index(0);
394        let joined = base.join(&sub);
395        assert_eq!(joined.len(), 3);
396    }
397
398    #[test]
399    fn test_path_parent() {
400        let path = Path::root().key("a").key("b");
401        let parent = path.parent().unwrap();
402        assert_eq!(parent.len(), 1);
403        assert_eq!(parent[0], Seg::Key("a".into()));
404    }
405
406    #[test]
407    fn test_path_serde() {
408        let path = Path::root().key("users").index(0);
409        let json = serde_json::to_string(&path).unwrap();
410        let parsed: Path = serde_json::from_str(&json).unwrap();
411        assert_eq!(path, parsed);
412    }
413}