Skip to main content

surrealdb_expr/val/value/
walk.rs

1use crate::expr::idiom::Idiom;
2use crate::expr::part::{Next, Part};
3use crate::val::Value;
4
5impl Value {
6	pub fn walk(&self, path: &[Part]) -> Vec<(Idiom, Self)> {
7		self._walk(path, Idiom::default())
8	}
9	fn _walk(&self, path: &[Part], prev: Idiom) -> Vec<(Idiom, Self)> {
10		match path.first() {
11			// Get the current path part
12			Some(p) => match self {
13				// Current path part is an object
14				Value::Object(v) => match p {
15					Part::Field(f) => match v.get(f as &str) {
16						Some(v) => v._walk(path.next(), prev.push(p.clone())),
17						None => Value::None._walk(path.next(), prev.push(p.clone())),
18					},
19					Part::All => v
20						.iter()
21						.flat_map(|(field, v)| {
22							v._walk(path.next(), prev.clone().push(Part::Field(field.clone())))
23						})
24						.collect::<Vec<_>>(),
25					x => {
26						if let Some(idx) = x.as_old_index() {
27							match v.get(&idx.to_string()) {
28								Some(v) => v._walk(path.next(), prev.push(p.clone())),
29								None => Value::None._walk(path.next(), prev.push(p.clone())),
30							}
31						} else {
32							vec![]
33						}
34					}
35				},
36				// Current path part is an array
37				Value::Array(v) => match p {
38					Part::First => match v.first() {
39						Some(v) => v._walk(path.next(), prev.push(p.clone())),
40						None => vec![],
41					},
42					Part::Last => match v.last() {
43						Some(v) => v._walk(path.next(), prev.push(p.clone())),
44						None => vec![],
45					},
46					x => {
47						if let Some(idx) = x.as_old_index() {
48							match v.get(idx) {
49								Some(v) => v._walk(path.next(), prev.push(p.clone())),
50								None => vec![],
51							}
52						} else {
53							v.iter()
54								.enumerate()
55								.flat_map(|(i, v)| {
56									v._walk(
57										path.next(),
58										prev.clone().push(Part::index_int(i as i64)),
59									)
60								})
61								.collect::<Vec<_>>()
62						}
63					}
64				},
65				// Current path part is a set
66				Value::Set(v) => match p {
67					Part::First => match v.first() {
68						Some(v) => v._walk(path.next(), prev.push(p.clone())),
69						None => vec![],
70					},
71					Part::Last => match v.last() {
72						Some(v) => v._walk(path.next(), prev.push(p.clone())),
73						None => vec![],
74					},
75					x => {
76						if let Some(idx) = x.as_old_index() {
77							match v.nth(idx) {
78								Some(v) => v._walk(path.next(), prev.push(p.clone())),
79								None => vec![],
80							}
81						} else {
82							v.iter()
83								.enumerate()
84								.flat_map(|(i, v)| {
85									v._walk(
86										path.next(),
87										prev.clone().push(Part::index_int(i as i64)),
88									)
89								})
90								.collect::<Vec<_>>()
91						}
92					}
93				},
94				// Ignore everything else
95				_ => match p {
96					Part::Field(_) => Value::None._walk(path.next(), prev.push(p.clone())),
97					x => {
98						if x.as_old_index().is_some() {
99							Value::None._walk(path.next(), prev.push(p.clone()))
100						} else {
101							vec![]
102						}
103					}
104				},
105			},
106			// No more parts so get the value
107			None => vec![(prev, self.clone())],
108		}
109	}
110}
111
112#[cfg(test)]
113mod tests {
114
115	use super::*;
116	use crate::expr::Idiom;
117	use crate::syn;
118
119	macro_rules! parse_val {
120		($input:expr) => {
121			crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
122		};
123	}
124
125	#[test]
126	fn walk_blank() {
127		let idi: Idiom = Default::default();
128		let val = parse_val!("{ test: { other: null, something: 123 } }");
129		let res: Vec<(Idiom, Value)> =
130			vec![(Idiom::default(), parse_val!("{ test: { other: null, something: 123 } }"))];
131		assert_eq!(res, val.walk(&idi));
132	}
133
134	#[test]
135	fn walk_basic() {
136		let idi: Idiom = syn::idiom("test.something").unwrap().into();
137		let val = parse_val!("{ test: { other: null, something: 123 } }");
138		let res: Vec<(Idiom, Value)> =
139			vec![(syn::idiom("test.something").unwrap().into(), Value::from(123))];
140		assert_eq!(res, val.walk(&idi));
141	}
142
143	#[test]
144	fn walk_empty() {
145		let idi: Idiom = syn::idiom("test.missing").unwrap().into();
146		let val = parse_val!("{ test: { other: null, something: 123 } }");
147		let res: Vec<(Idiom, Value)> =
148			vec![(syn::idiom("test.missing").unwrap().into(), Value::None)];
149		assert_eq!(res, val.walk(&idi));
150	}
151
152	#[test]
153	fn walk_empty_object() {
154		let idi: Idiom = syn::idiom("none.something.age").unwrap().into();
155		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
156		let res: Vec<(Idiom, Value)> =
157			vec![(syn::idiom("none.something.age").unwrap().into(), Value::None)];
158		assert_eq!(res, val.walk(&idi));
159	}
160
161	#[test]
162	fn walk_empty_array() {
163		let idi: Idiom = syn::idiom("none.something.*.age").unwrap().into();
164		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
165		let res: Vec<(Idiom, Value)> = vec![];
166		assert_eq!(res, val.walk(&idi));
167	}
168
169	#[test]
170	fn walk_empty_array_index() {
171		let idi: Idiom = syn::idiom("none.something[0].age").unwrap().into();
172		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
173		let res: Vec<(Idiom, Value)> =
174			vec![(syn::idiom("none.something[0].age").unwrap().into(), Value::None)];
175		assert_eq!(res, val.walk(&idi));
176	}
177
178	#[test]
179	fn walk_array() {
180		let idi: Idiom = syn::idiom("test.something").unwrap().into();
181		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
182		let res = vec![(
183			syn::idiom("test.something").unwrap().into(),
184			parse_val!("[{ age: 34 }, { age: 36 }]"),
185		)];
186		assert_eq!(res, val.walk(&idi));
187	}
188
189	#[test]
190	fn walk_array_field() {
191		let idi: Idiom = syn::idiom("test.something[*].age").unwrap().into();
192		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
193		let res: Vec<(Idiom, Value)> = vec![
194			(syn::idiom("test.something[0].age").unwrap().into(), Value::from(34)),
195			(syn::idiom("test.something[1].age").unwrap().into(), Value::from(36)),
196		];
197		assert_eq!(res, val.walk(&idi));
198	}
199
200	#[test]
201	fn walk_array_field_embedded() {
202		let idi: Idiom = syn::idiom("test.something[*].tags").unwrap().into();
203		let val = parse_val!(
204			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
205		);
206		let res: Vec<(Idiom, Value)> = vec![
207			(
208				syn::idiom("test.something[0].tags").unwrap().into(),
209				parse_val!("['code', 'databases']"),
210			),
211			(
212				syn::idiom("test.something[1].tags").unwrap().into(),
213				parse_val!("['design', 'operations']"),
214			),
215		];
216		assert_eq!(res, val.walk(&idi));
217	}
218
219	#[test]
220	fn walk_array_field_embedded_index() {
221		let idi: Idiom = syn::idiom("test.something[*].tags[1]").unwrap().into();
222		let val = parse_val!(
223			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
224		);
225		let res: Vec<(Idiom, Value)> = vec![
226			(syn::idiom("test.something[0].tags[1]").unwrap().into(), Value::from("databases")),
227			(syn::idiom("test.something[1].tags[1]").unwrap().into(), Value::from("operations")),
228		];
229		assert_eq!(res, val.walk(&idi));
230	}
231
232	#[test]
233	fn walk_set_field_all() {
234		let idi: Idiom = syn::idiom("test.*").unwrap().into();
235		let val = parse_val!("{ test: { ' hello ', } }");
236		let res: Vec<(Idiom, Value)> =
237			vec![(syn::idiom("test[0]").unwrap().into(), Value::from(" hello "))];
238		assert_eq!(res, val.walk(&idi));
239	}
240
241	#[test]
242	fn walk_array_field_embedded_index_all() {
243		let idi: Idiom = syn::idiom("test.something[*].tags[*]").unwrap().into();
244		let val = parse_val!(
245			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
246		);
247		let res: Vec<(Idiom, Value)> = vec![
248			(syn::idiom("test.something[0].tags[0]").unwrap().into(), Value::from("code")),
249			(syn::idiom("test.something[0].tags[1]").unwrap().into(), Value::from("databases")),
250			(syn::idiom("test.something[1].tags[0]").unwrap().into(), Value::from("design")),
251			(syn::idiom("test.something[1].tags[1]").unwrap().into(), Value::from("operations")),
252		];
253		assert_eq!(res, val.walk(&idi));
254	}
255}