datalogic_rs/value/
access.rs

1//! Value access utilities for path-based value retrieval.
2//!
3//! This module provides utilities for accessing values in nested data structures
4//! using path expressions.
5
6use super::data_value::DataValue;
7use crate::arena::DataArena;
8
9/// A segment in a path expression.
10#[derive(Debug, Clone, PartialEq)]
11pub enum PathSegment<'a> {
12    /// A key in an object.
13    Key(&'a str),
14
15    /// An index in an array.
16    Index(usize),
17}
18
19impl<'a> PathSegment<'a> {
20    /// Creates a new key segment.
21    pub fn key(arena: &'a DataArena, key: &str) -> Self {
22        PathSegment::Key(arena.intern_str(key))
23    }
24
25    /// Creates a new index segment.
26    pub fn index(index: usize) -> Self {
27        PathSegment::Index(index)
28    }
29
30    /// Parses a path segment from a string.
31    pub fn parse(arena: &'a DataArena, segment: &str) -> Self {
32        if let Ok(index) = segment.parse::<usize>() {
33            PathSegment::Index(index)
34        } else {
35            PathSegment::Key(arena.intern_str(segment))
36        }
37    }
38}
39
40/// A trait for accessing values using path expressions.
41pub trait ValueAccess<'a> {
42    /// Gets a value using a path expression.
43    fn get_path(&self, path: &[PathSegment<'a>]) -> Option<&DataValue<'a>>;
44
45    /// Gets a value using a dot-separated path string.
46    fn get_path_str(&self, arena: &'a DataArena, path: &str) -> Option<&DataValue<'a>>;
47}
48
49impl<'a> ValueAccess<'a> for DataValue<'a> {
50    fn get_path(&self, path: &[PathSegment<'a>]) -> Option<&DataValue<'a>> {
51        if path.is_empty() {
52            return Some(self);
53        }
54
55        let (segment, rest) = path.split_first().unwrap();
56
57        match segment {
58            PathSegment::Key(key) => match self {
59                DataValue::Object(entries) => {
60                    for (k, v) in *entries {
61                        if *k == *key {
62                            if rest.is_empty() {
63                                return Some(v);
64                            } else {
65                                return v.get_path(rest);
66                            }
67                        }
68                    }
69                    None
70                }
71                _ => None,
72            },
73            PathSegment::Index(index) => match self {
74                DataValue::Array(elements) => {
75                    if let Some(value) = elements.get(*index) {
76                        if rest.is_empty() {
77                            Some(value)
78                        } else {
79                            value.get_path(rest)
80                        }
81                    } else {
82                        None
83                    }
84                }
85                _ => None,
86            },
87        }
88    }
89
90    fn get_path_str(&self, arena: &'a DataArena, path: &str) -> Option<&DataValue<'a>> {
91        if path.is_empty() {
92            return Some(self);
93        }
94
95        // Use the parse_path function to get arena-allocated path segments
96        let segments = parse_path(arena, path);
97        self.get_path(segments)
98    }
99}
100
101/// Parses a path string into a vector of path segments.
102pub fn parse_path<'a>(arena: &'a DataArena, path: &str) -> &'a [PathSegment<'a>] {
103    // Fast path for empty path
104    if path.is_empty() {
105        return &[];
106    }
107
108    // Fast path for single segment (no dots)
109    if !path.contains('.') {
110        let segment = PathSegment::parse(arena, path);
111        return arena.vec_into_slice(vec![segment]);
112    }
113
114    // Calculate the number of segments
115    let segment_count = path.chars().filter(|&c| c == '.').count() + 1;
116
117    // Pre-allocate a buffer for segments
118    let mut segments = Vec::with_capacity(segment_count);
119
120    // Fill the buffer
121    for segment in path.split('.') {
122        segments.push(PathSegment::parse(arena, segment));
123    }
124
125    // Return a slice from the arena
126    arena.vec_into_slice(segments)
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    use crate::arena::DataArena;
133
134    #[test]
135    fn test_path_segment_parsing() {
136        let arena = DataArena::new();
137
138        let key = PathSegment::parse(&arena, "name");
139        let index = PathSegment::parse(&arena, "42");
140
141        assert_eq!(key, PathSegment::Key(arena.intern_str("name")));
142        assert_eq!(index, PathSegment::Index(42));
143    }
144
145    #[test]
146    fn test_value_access() {
147        let arena = DataArena::new();
148
149        // Create a nested object
150        let user = DataValue::object(
151            &arena,
152            &[
153                (arena.intern_str("name"), DataValue::string(&arena, "John")),
154                (arena.intern_str("age"), DataValue::integer(30)),
155                (
156                    arena.intern_str("address"),
157                    DataValue::object(
158                        &arena,
159                        &[
160                            (
161                                arena.intern_str("city"),
162                                DataValue::string(&arena, "New York"),
163                            ),
164                            (arena.intern_str("zip"), DataValue::string(&arena, "10001")),
165                        ],
166                    ),
167                ),
168                (
169                    arena.intern_str("scores"),
170                    DataValue::array(
171                        &arena,
172                        &[
173                            DataValue::integer(85),
174                            DataValue::integer(90),
175                            DataValue::integer(95),
176                        ],
177                    ),
178                ),
179            ],
180        );
181
182        // Test path access
183        assert_eq!(
184            user.get_path_str(&arena, "name").unwrap().as_str(),
185            Some("John")
186        );
187        assert_eq!(user.get_path_str(&arena, "age").unwrap().as_i64(), Some(30));
188        assert_eq!(
189            user.get_path_str(&arena, "address.city").unwrap().as_str(),
190            Some("New York")
191        );
192        assert_eq!(
193            user.get_path_str(&arena, "scores.1").unwrap().as_i64(),
194            Some(90)
195        );
196
197        // Test with explicit path segments
198        let path = vec![
199            PathSegment::key(&arena, "address"),
200            PathSegment::key(&arena, "zip"),
201        ];
202        assert_eq!(user.get_path(&path).unwrap().as_str(), Some("10001"));
203
204        // Test non-existent paths
205        assert_eq!(user.get_path_str(&arena, "email"), None);
206        assert_eq!(user.get_path_str(&arena, "address.country"), None);
207        assert_eq!(user.get_path_str(&arena, "scores.5"), None);
208    }
209}