osp_cli/dsl/stages/
values.rs1use crate::core::row::Row;
2use anyhow::Result;
3use serde_json::{Map, Value};
4
5use super::common::parse_terms;
6
7#[derive(Debug, Clone, Default)]
8pub(crate) struct ValuesPlan {
9 keys: Vec<String>,
10}
11
12impl ValuesPlan {
13 pub(crate) fn extract_row(&self, row: &Row) -> Vec<Row> {
14 let mut out = Vec::new();
15
16 if self.keys.is_empty() {
17 for value in row.values() {
18 emit_value_rows(&mut out, value);
19 }
20 return out;
21 }
22
23 for key in &self.keys {
24 if let Some(value) = row.get(key) {
25 emit_value_rows(&mut out, value);
26 }
27 }
28
29 out
30 }
31}
32
33pub(crate) fn compile(spec: &str) -> ValuesPlan {
34 ValuesPlan {
35 keys: parse_terms(spec),
36 }
37}
38
39pub fn apply(rows: Vec<Row>, spec: &str) -> Result<Vec<Row>> {
40 let plan = compile(spec);
41 let mut out: Vec<Row> = Vec::new();
42
43 for row in rows {
44 out.extend(plan.extract_row(&row));
45 }
46
47 Ok(out)
48}
49
50fn emit_value_rows(out: &mut Vec<Row>, value: &Value) {
51 match value {
52 Value::Array(values) => {
53 for item in values {
54 let mut row = Map::new();
55 row.insert("value".to_string(), item.clone());
56 out.push(row);
57 }
58 }
59 _ => {
60 let mut row = Map::new();
61 row.insert("value".to_string(), value.clone());
62 out.push(row);
63 }
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use serde_json::json;
70
71 use super::apply;
72
73 #[test]
74 fn explodes_array_values() {
75 let rows = vec![
76 json!({"members": ["a", "b"]})
77 .as_object()
78 .cloned()
79 .expect("object"),
80 ];
81
82 let output = apply(rows, "members").expect("values should work");
83 assert_eq!(output.len(), 2);
84 }
85
86 #[test]
87 fn emits_requested_scalar_values_and_ignores_missing_keys() {
88 let rows = vec![
89 json!({"uid": "oistes", "mail": "oistes@example.org"})
90 .as_object()
91 .cloned()
92 .expect("object"),
93 ];
94
95 let output = apply(rows, "uid missing").expect("values should work");
96 assert_eq!(output.len(), 1);
97 assert_eq!(
98 output[0].get("value").and_then(|value| value.as_str()),
99 Some("oistes")
100 );
101 }
102
103 #[test]
104 fn empty_spec_emits_all_scalar_and_array_values_in_order() {
105 let rows = vec![
106 json!({"uid": "oistes", "members": ["a", "b"], "active": true})
107 .as_object()
108 .cloned()
109 .expect("object"),
110 ];
111
112 let output = apply(rows, "").expect("empty values stage should enumerate all fields");
113 let mut values = output
114 .iter()
115 .map(|row| {
116 row.get("value")
117 .cloned()
118 .unwrap_or(serde_json::Value::Null)
119 .to_string()
120 })
121 .collect::<Vec<_>>();
122 values.sort();
123
124 assert_eq!(values, vec!["\"a\"", "\"b\"", "\"oistes\"", "true"]);
125 }
126}