1use std::borrow::Cow;
9use std::fmt;
10
11use crate::val::{Set, Value};
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum FieldPathPart {
16 Field(String),
18 Index(usize),
20 First,
22 Last,
24 Lookup(String),
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub struct FieldPath(pub Vec<FieldPathPart>);
55
56impl FieldPath {
57 pub fn field(name: impl Into<String>) -> Self {
59 FieldPath(vec![FieldPathPart::Field(name.into())])
60 }
61
62 pub fn is_empty(&self) -> bool {
64 self.0.is_empty()
65 }
66
67 pub fn len(&self) -> usize {
69 self.0.len()
70 }
71
72 pub fn extract<'a>(&self, value: &'a Value) -> Cow<'a, Value> {
81 let mut current = Cow::Borrowed(value);
82 for part in &self.0 {
83 current = match current {
84 Cow::Borrowed(v) => match step(v, part) {
85 Step::Borrowed(b) => Cow::Borrowed(b),
86 Step::Owned(o) => Cow::Owned(o),
87 },
88 Cow::Owned(v) => Cow::Owned(match step(&v, part) {
93 Step::Borrowed(b) => b.clone(),
94 Step::Owned(o) => o,
95 }),
96 };
97 }
98 current
99 }
100}
101
102enum Step<'v> {
105 Borrowed(&'v Value),
106 Owned(Value),
107}
108
109fn step<'v>(value: &'v Value, part: &FieldPathPart) -> Step<'v> {
112 match (value, part) {
113 (Value::Object(obj), FieldPathPart::Field(name) | FieldPathPart::Lookup(name)) => {
115 obj.get(name).map_or(Step::Owned(Value::None), Step::Borrowed)
116 }
117 (Value::Array(arr), FieldPathPart::Index(i)) => {
119 arr.get(*i).map_or(Step::Owned(Value::None), Step::Borrowed)
120 }
121 (Value::Set(set), FieldPathPart::Index(i)) => {
123 set.nth(*i).map_or(Step::Owned(Value::None), Step::Borrowed)
124 }
125 (Value::Array(arr), FieldPathPart::First) => {
127 arr.first().map_or(Step::Owned(Value::None), Step::Borrowed)
128 }
129 (Value::Set(set), FieldPathPart::First) => {
131 set.first().map_or(Step::Owned(Value::None), Step::Borrowed)
132 }
133 (Value::Array(arr), FieldPathPart::Last) => {
135 arr.last().map_or(Step::Owned(Value::None), Step::Borrowed)
136 }
137 (Value::Set(set), FieldPathPart::Last) => {
139 set.last().map_or(Step::Owned(Value::None), Step::Borrowed)
140 }
141 (Value::Array(arr), FieldPathPart::Field(name) | FieldPathPart::Lookup(name)) => {
143 Step::Owned(Value::Array(
144 arr.iter()
145 .map(|v| match v {
146 Value::Object(obj) => obj.get(name).cloned().unwrap_or(Value::None),
147 _ => Value::None,
148 })
149 .collect::<Vec<_>>()
150 .into(),
151 ))
152 }
153 (Value::Set(set), FieldPathPart::Field(name) | FieldPathPart::Lookup(name)) => {
155 Step::Owned(Value::Set(Set::from(
156 set.iter()
157 .map(|v| match v {
158 Value::Object(obj) => obj.get(name).cloned().unwrap_or(Value::None),
159 _ => Value::None,
160 })
161 .collect::<Vec<_>>(),
162 )))
163 }
164 _ => Step::Owned(Value::None),
166 }
167}
168
169impl fmt::Display for FieldPath {
170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171 for (i, part) in self.0.iter().enumerate() {
172 match part {
173 FieldPathPart::Field(name) if i == 0 => write!(f, "{}", name)?,
174 FieldPathPart::Field(name) => write!(f, ".{}", name)?,
175 FieldPathPart::Index(idx) => write!(f, "[{}]", idx)?,
176 FieldPathPart::First => write!(f, "[0]")?,
177 FieldPathPart::Last => write!(f, "[$]")?,
178 FieldPathPart::Lookup(key) if i == 0 => write!(f, "{}", key)?,
179 FieldPathPart::Lookup(key) => write!(f, ".{}", key)?,
180 }
181 }
182 Ok(())
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use std::collections::BTreeMap;
189
190 use super::*;
191 use crate::val::Object;
192
193 fn make_obj(pairs: Vec<(&str, Value)>) -> Object {
195 let map: BTreeMap<String, Value> =
196 pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect();
197 Object::from(map)
198 }
199
200 #[test]
201 fn test_field_path_simple() {
202 let path = FieldPath::field("name");
203 assert_eq!(path.to_string(), "name");
204 assert_eq!(path.len(), 1);
205 }
206
207 #[test]
208 fn test_field_path_extract_simple() {
209 let path = FieldPath::field("name");
210 let obj = make_obj(vec![("name", Value::from("Alice"))]);
211 let value = Value::Object(obj);
212
213 let result = path.extract(&value);
214 assert_eq!(*result, Value::from("Alice"));
215 assert!(matches!(result, Cow::Borrowed(_)));
217 }
218
219 #[test]
220 fn test_field_path_extract_nested() {
221 let path = FieldPath(vec![
223 FieldPathPart::Field("user".into()),
224 FieldPathPart::Field("address".into()),
225 FieldPathPart::Field("city".into()),
226 ]);
227
228 let city_obj = make_obj(vec![("city", Value::from("Austin"))]);
230 let address_obj = make_obj(vec![("address", Value::Object(city_obj))]);
231 let user_obj = make_obj(vec![("user", Value::Object(address_obj))]);
232 let value = Value::Object(user_obj);
233
234 let result = path.extract(&value);
235 assert_eq!(*result, Value::from("Austin"));
236 assert!(matches!(result, Cow::Borrowed(_)));
238 }
239
240 #[test]
241 fn test_field_path_extract_array_index() {
242 let path = FieldPath(vec![FieldPathPart::Field("items".into()), FieldPathPart::Index(0)]);
244
245 let items = Value::Array(vec![Value::from("first"), Value::from("second")].into());
246 let obj = make_obj(vec![("items", items)]);
247 let value = Value::Object(obj);
248
249 let result = path.extract(&value);
250 assert_eq!(*result, Value::from("first"));
251 assert!(matches!(result, Cow::Borrowed(_)));
252 }
253
254 #[test]
255 fn test_field_path_extract_array_last() {
256 let path = FieldPath(vec![FieldPathPart::Field("items".into()), FieldPathPart::Last]);
258
259 let items = Value::Array(vec![Value::from("first"), Value::from("second")].into());
260 let obj = make_obj(vec![("items", items)]);
261 let value = Value::Object(obj);
262
263 let result = path.extract(&value);
264 assert_eq!(*result, Value::from("second"));
265 assert!(matches!(result, Cow::Borrowed(_)));
266 }
267
268 #[test]
269 fn test_field_path_extract_missing() {
270 let path = FieldPath::field("missing");
271 let obj = make_obj(vec![("name", Value::from("Alice"))]);
272 let value = Value::Object(obj);
273
274 let result = path.extract(&value);
275 assert_eq!(*result, Value::None);
276 assert!(matches!(result, Cow::Owned(_)));
278 }
279
280 #[test]
281 fn test_field_path_extract_past_missing_stays_none() {
282 let path = FieldPath(vec![
285 FieldPathPart::Field("missing".into()),
286 FieldPathPart::Field("deeper".into()),
287 FieldPathPart::Index(3),
288 ]);
289 let obj = make_obj(vec![("name", Value::from("Alice"))]);
290 let value = Value::Object(obj);
291
292 let result = path.extract(&value);
293 assert_eq!(*result, Value::None);
294 assert!(matches!(result, Cow::Owned(_)));
295 }
296
297 #[test]
298 fn test_field_path_extract_field_on_array() {
299 let path = FieldPath(vec![
301 FieldPathPart::Field("users".into()),
302 FieldPathPart::Field("name".into()),
303 ]);
304
305 let user1 = Value::Object(make_obj(vec![("name", Value::from("Alice"))]));
306 let user2 = Value::Object(make_obj(vec![("name", Value::from("Bob"))]));
307 let users = Value::Array(vec![user1, user2].into());
308 let obj = make_obj(vec![("users", users)]);
309 let value = Value::Object(obj);
310
311 let result = path.extract(&value);
312 assert!(matches!(result, Cow::Owned(_)));
314 if let Value::Array(arr) = result.into_owned() {
315 assert_eq!(arr.len(), 2);
316 assert_eq!(arr[0], Value::from("Alice"));
317 assert_eq!(arr[1], Value::from("Bob"));
318 } else {
319 panic!("Expected array result");
320 }
321 }
322
323 #[test]
324 fn test_field_path_extract_through_owned_intermediate() {
325 let path = FieldPath(vec![
328 FieldPathPart::Field("users".into()),
329 FieldPathPart::Field("name".into()),
330 FieldPathPart::Index(0),
331 ]);
332
333 let user1 = Value::Object(make_obj(vec![("name", Value::from("Alice"))]));
334 let user2 = Value::Object(make_obj(vec![("name", Value::from("Bob"))]));
335 let users = Value::Array(vec![user1, user2].into());
336 let obj = make_obj(vec![("users", users)]);
337 let value = Value::Object(obj);
338
339 let result = path.extract(&value);
340 assert_eq!(*result, Value::from("Alice"));
341 assert!(matches!(result, Cow::Owned(_)));
342 }
343
344 #[test]
345 fn test_field_path_display() {
346 let path = FieldPath(vec![
347 FieldPathPart::Field("user".into()),
348 FieldPathPart::Field("address".into()),
349 FieldPathPart::Index(0),
350 FieldPathPart::Field("city".into()),
351 ]);
352 assert_eq!(path.to_string(), "user.address[0].city");
353 }
354}