1use std::collections::HashSet;
2
3use crate::core::row::Row;
4use serde_json::Value;
5
6use crate::dsl::{
7 eval::{
8 flatten::{coalesce_flat_row, flatten_row},
9 matchers::match_row_keys,
10 },
11 parse::{
12 key_spec::ExactMode,
13 path::{PathExpression, Selector, parse_path, requires_materialization},
14 },
15};
16
17pub fn resolve_values(row: &Row, token: &str, exact: ExactMode) -> Vec<Value> {
18 let trimmed = token.trim();
19 if trimmed.is_empty() {
20 return Vec::new();
21 }
22
23 if let Ok(path) = parse_path(trimmed) {
24 let nested = Value::Object(row.clone());
25 let direct = evaluate_path(&nested, &path);
29 if path.absolute || !direct.is_empty() {
30 return dedup_values(direct);
31 }
32 }
33
34 let flattened = flatten_row(row);
36 let matched = match_row_keys(&flattened, trimmed, exact);
37 let values = matched
38 .iter()
39 .filter_map(|key| flattened.get(*key).cloned())
40 .collect::<Vec<_>>();
41
42 dedup_values(values)
43}
44
45pub fn resolve_values_truthy(row: &Row, token: &str, exact: ExactMode) -> bool {
46 resolve_values(row, token, exact).iter().any(is_truthy)
47}
48
49pub fn resolve_first_value(row: &Row, token: &str, exact: ExactMode) -> Option<Value> {
50 let value = resolve_values(row, token, exact).into_iter().next()?;
51 match value {
52 Value::Array(values) => values.into_iter().next(),
53 scalar => Some(scalar),
54 }
55}
56
57pub fn evaluate_path(root: &Value, path: &PathExpression) -> Vec<Value> {
58 let mut current: Vec<Value> = vec![root.clone()];
59
60 for segment in &path.segments {
61 let mut next = Vec::new();
62
63 for node in current {
64 let mut values = Vec::new();
65 if let Some(name) = &segment.name {
66 if let Value::Object(map) = node
67 && let Some(value) = map.get(name)
68 {
69 values.push(value.clone());
70 }
71 } else {
72 values.push(node);
73 }
74
75 for selector in &segment.selectors {
76 values = apply_selector(values, selector);
77 if values.is_empty() {
78 break;
79 }
80 }
81
82 next.extend(values);
83 }
84
85 current = next;
86 if current.is_empty() {
87 break;
88 }
89 }
90
91 current
92}
93
94pub fn enumerate_path_values(root: &Value, path: &PathExpression) -> Vec<(String, Value)> {
95 if path.segments.is_empty() {
96 return Vec::new();
97 }
98
99 let mut out = Vec::new();
100 traverse_path(root, path, 0, String::new(), &mut out);
101 out
102}
103
104pub fn resolve_pairs(flat_row: &Row, token: &str) -> (Vec<(String, Value)>, bool) {
105 let trimmed = token.trim();
106 if trimmed.is_empty() {
107 return (Vec::new(), false);
108 }
109
110 let expr = parse_path(trimmed).ok();
111 if let Some(expr) = expr
112 && !expr.segments.is_empty()
113 {
114 let materialized = requires_materialization(&expr);
115 let nested = Value::Object(coalesce_flat_row(flat_row));
116 let pairs = enumerate_path_values(&nested, &expr);
117 if materialized || !pairs.is_empty() {
118 return (pairs, materialized);
119 }
120 }
121
122 let matched = match_row_keys(flat_row, trimmed, ExactMode::None);
123 if !matched.is_empty() {
124 let pairs = matched
125 .into_iter()
126 .filter_map(|key| {
127 flat_row
128 .get(key)
129 .cloned()
130 .map(|value| (key.to_string(), value))
131 })
132 .collect::<Vec<_>>();
133 return (pairs, false);
134 }
135
136 let pairs = flat_row
139 .iter()
140 .map(|(key, value)| (key.clone(), value.clone()))
141 .collect::<Vec<_>>();
142 (pairs, true)
143}
144
145fn apply_selector(values: Vec<Value>, selector: &Selector) -> Vec<Value> {
146 match selector {
147 Selector::Fanout => values
148 .into_iter()
149 .flat_map(|value| match value {
150 Value::Array(items) => items,
151 _ => Vec::new(),
152 })
153 .collect(),
154 Selector::Index(index) => values
155 .into_iter()
156 .filter_map(|value| match value {
157 Value::Array(items) => {
158 let len = items.len() as i64;
159 let idx = if *index < 0 { len + index } else { *index };
160 if idx >= 0 {
161 items.get(idx as usize).cloned()
162 } else {
163 None
164 }
165 }
166 _ => None,
167 })
168 .collect(),
169 Selector::Slice { start, stop, step } => values
170 .into_iter()
171 .flat_map(|value| match value {
172 Value::Array(items) => slice_indices(items.len() as i64, *start, *stop, *step)
173 .into_iter()
174 .filter_map(|index| items.get(index as usize).cloned())
175 .collect(),
176 _ => Vec::new(),
177 })
178 .collect(),
179 }
180}
181
182fn dedup_values(values: Vec<Value>) -> Vec<Value> {
183 let mut seen = HashSet::new();
184 let mut out = Vec::new();
185
186 for value in values {
187 let Ok(key) = serde_json::to_string(&value) else {
188 continue;
189 };
190 if seen.insert(key) {
191 out.push(value);
192 }
193 }
194
195 out
196}
197
198fn traverse_path(
199 root: &Value,
200 path: &PathExpression,
201 segment_index: usize,
202 flat_key: String,
203 out: &mut Vec<(String, Value)>,
204) {
205 if segment_index == path.segments.len() {
206 out.push((flat_key, root.clone()));
207 return;
208 }
209
210 let segment = &path.segments[segment_index];
211 let mut current: Vec<(Value, String)> = vec![(root.clone(), flat_key)];
212
213 if let Some(name) = &segment.name {
214 let mut next = Vec::new();
215 for (value, key) in current {
216 if let Value::Object(map) = value
217 && let Some(child) = map.get(name)
218 {
219 next.push((child.clone(), append_name(&key, name)));
220 }
221 }
222 current = next;
223 }
224
225 for selector in &segment.selectors {
226 current = apply_selector_pairs(current, selector);
227 if current.is_empty() {
228 return;
229 }
230 }
231
232 for (value, key) in current {
233 traverse_path(&value, path, segment_index + 1, key, out);
234 }
235}
236
237fn apply_selector_pairs(values: Vec<(Value, String)>, selector: &Selector) -> Vec<(Value, String)> {
238 let mut out = Vec::new();
239 for (value, key) in values {
240 let items = match value {
241 Value::Array(items) => items,
242 _ => Vec::new(),
243 };
244 let len = items.len() as i64;
245 match selector {
246 Selector::Fanout => {
247 for (index, item) in items.into_iter().enumerate() {
248 out.push((item, append_index(&key, index)));
249 }
250 }
251 Selector::Index(index) => {
252 let mut idx = *index;
253 if idx < 0 {
254 idx += len;
255 }
256 if idx >= 0
257 && let Some(item) = items.get(idx as usize)
258 {
259 out.push((item.clone(), append_index(&key, idx as usize)));
260 }
261 }
262 Selector::Slice { start, stop, step } => {
263 let indices = slice_indices(len, *start, *stop, *step);
264 for idx in indices {
265 if let Some(item) = items.get(idx as usize) {
266 out.push((item.clone(), append_index(&key, idx as usize)));
267 }
268 }
269 }
270 }
271 }
272 out
273}
274
275fn slice_indices(len: i64, start: Option<i64>, stop: Option<i64>, step: Option<i64>) -> Vec<i64> {
276 let step = step.unwrap_or(1);
277 if step == 0 {
278 return Vec::new();
279 }
280
281 let mut out = Vec::new();
282 if step > 0 {
283 let mut index = start.unwrap_or(0);
284 if index < 0 {
285 index += len;
286 }
287 index = index.clamp(0, len);
288
289 let mut stop_index = stop.unwrap_or(len);
290 if stop_index < 0 {
291 stop_index += len;
292 }
293 stop_index = stop_index.clamp(0, len);
294
295 while index < stop_index {
296 if index >= 0 {
297 out.push(index);
298 }
299 index += step;
300 }
301 } else {
302 let mut index = start.unwrap_or(len - 1);
303 if index < 0 {
304 index += len;
305 }
306 index = index.clamp(-1, len - 1);
307
308 let stop_index = match stop {
309 Some(stop_value) => {
310 let mut normalized = stop_value;
311 if normalized < 0 {
312 normalized += len;
313 }
314 normalized.clamp(-1, len - 1)
315 }
316 None => -1,
317 };
318
319 while index > stop_index {
320 if index >= 0 {
321 out.push(index);
322 }
323 index += step;
324 }
325 }
326
327 out
328}
329
330fn append_name(base: &str, name: &str) -> String {
331 if base.is_empty() {
332 name.to_string()
333 } else {
334 format!("{base}.{name}")
335 }
336}
337
338fn append_index(base: &str, index: usize) -> String {
339 if base.is_empty() {
340 format!("[{index}]")
341 } else {
342 format!("{base}[{index}]")
343 }
344}
345
346pub fn is_truthy(value: &Value) -> bool {
347 match value {
348 Value::Null => false,
349 Value::Bool(flag) => *flag,
350 Value::Number(number) => number.as_f64().is_some_and(|value| value != 0.0),
351 Value::String(text) => !text.is_empty(),
352 Value::Array(values) => !values.is_empty(),
353 Value::Object(map) => !map.is_empty(),
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use serde_json::json;
360
361 use crate::dsl::parse::{key_spec::ExactMode, path::parse_path};
362
363 use super::{
364 enumerate_path_values, evaluate_path, is_truthy, resolve_first_value, resolve_pairs,
365 resolve_values, slice_indices,
366 };
367
368 #[test]
369 fn resolve_values_prefers_direct_path_then_fuzzy_fallback() {
370 let row = json!({"metadata": {"asset": {"id": 42}}, "id": 7})
371 .as_object()
372 .cloned()
373 .expect("object");
374
375 let values = resolve_values(&row, "asset.id", ExactMode::None);
376 assert_eq!(values, vec![json!(42)]);
377
378 let values = resolve_values(&row, ".asset.id", ExactMode::None);
379 assert!(values.is_empty());
380 }
381
382 #[test]
383 fn evaluate_path_handles_fanout_and_slice() {
384 let root = json!({"items": [{"id": 1}, {"id": 2}, {"id": 3}]});
385
386 let path = parse_path("items[].id").expect("path should parse");
387 assert_eq!(
388 evaluate_path(&root, &path),
389 vec![json!(1), json!(2), json!(3)]
390 );
391
392 let path = parse_path("items[:2].id").expect("path should parse");
393 assert_eq!(evaluate_path(&root, &path), vec![json!(1), json!(2)]);
394
395 let path = parse_path("items[::-1].id").expect("path should parse");
396 assert_eq!(
397 evaluate_path(&root, &path),
398 vec![json!(3), json!(2), json!(1)]
399 );
400 }
401
402 #[test]
403 fn slice_indices_handles_forward_and_reverse_ranges_consistently() {
404 assert_eq!(slice_indices(5, Some(1), Some(4), Some(1)), vec![1, 2, 3]);
405 assert_eq!(slice_indices(5, None, None, Some(-1)), vec![4, 3, 2, 1, 0]);
406 assert_eq!(slice_indices(5, Some(-3), None, Some(1)), vec![2, 3, 4]);
407 assert_eq!(slice_indices(0, None, None, Some(-1)), Vec::<i64>::new());
408 assert_eq!(slice_indices(5, None, None, Some(0)), Vec::<i64>::new());
409 }
410
411 #[test]
412 fn truthy_rules_match_dsl_expectations() {
413 assert!(!is_truthy(&json!(null)));
414 assert!(!is_truthy(&json!("")));
415 assert!(!is_truthy(&json!([])));
416 assert!(is_truthy(&json!("x")));
417 assert!(is_truthy(&json!([1])));
418 }
419
420 #[test]
421 fn resolve_first_value_unwraps_arrays_and_dedups_results() {
422 let row = json!({
423 "items": [{"id": 7}, {"id": 7}],
424 "dup": [1, 1]
425 })
426 .as_object()
427 .cloned()
428 .expect("object");
429
430 assert_eq!(
431 resolve_first_value(&row, "items[].id", ExactMode::None),
432 Some(json!(7))
433 );
434 assert_eq!(
435 resolve_values(&row, "dup[]", ExactMode::None),
436 vec![json!(1)]
437 );
438 }
439
440 #[test]
441 fn resolve_pairs_handles_path_flat_fallback_and_full_materialization() {
442 let flat = json!({
443 "items[0].id": 1,
444 "items[1].id": 2,
445 "flat.value": "x"
446 })
447 .as_object()
448 .cloned()
449 .expect("object");
450
451 let (path_pairs, materialized) = resolve_pairs(&flat, "items[].id");
452 assert!(!materialized);
453 assert_eq!(
454 path_pairs,
455 vec![
456 ("items[0].id".to_string(), json!(1)),
457 ("items[1].id".to_string(), json!(2))
458 ]
459 );
460
461 let (materialized_pairs, materialized) = resolve_pairs(&flat, "items[-1].id");
462 assert!(materialized);
463 assert_eq!(
464 materialized_pairs,
465 vec![("items[1].id".to_string(), json!(2))]
466 );
467
468 let (flat_pairs, materialized) = resolve_pairs(&flat, "flat.value");
469 assert!(!materialized);
470 assert_eq!(flat_pairs, vec![("flat.value".to_string(), json!("x"))]);
471
472 let (fallback_pairs, materialized) = resolve_pairs(&flat, "missing");
473 assert!(materialized);
474 assert_eq!(fallback_pairs.len(), 3);
475 }
476
477 #[test]
478 fn enumerate_paths_and_selectors_cover_negative_indexes() {
479 let root = json!({"items": [{"id": 1}, {"id": 2}, {"id": 3}]});
480 let path = parse_path("items[-1].id").expect("path should parse");
481 assert_eq!(evaluate_path(&root, &path), vec![json!(3)]);
482
483 let enumerated = enumerate_path_values(
484 &root,
485 &parse_path("items[:2].id").expect("path should parse"),
486 );
487 assert_eq!(
488 enumerated,
489 vec![
490 ("items[0].id".to_string(), json!(1)),
491 ("items[1].id".to_string(), json!(2))
492 ]
493 );
494 }
495}