cli_engine/output/
fields.rs1use std::collections::BTreeMap;
2
3use serde_json::{Map, Value};
4
5#[derive(Clone, Debug, Default, Eq, PartialEq)]
7pub struct FieldTree {
8 children: BTreeMap<String, Option<Box<FieldTree>>>,
9}
10
11impl FieldTree {
12 pub(crate) fn top_level_names(&self) -> impl Iterator<Item = &str> {
14 self.children.keys().map(String::as_str)
15 }
16}
17
18#[must_use]
20pub fn parse_fields(fields: &str) -> FieldTree {
21 let mut root = FieldTree::default();
22 for part in fields
23 .split(',')
24 .map(str::trim)
25 .filter(|part| !part.is_empty())
26 {
27 insert_path(&mut root, part);
28 }
29 root
30}
31
32#[must_use]
34pub fn filter_fields(data: &Value, fields: &str) -> Value {
35 let fields = fields.trim();
36 if fields.is_empty() || fields == "all" || fields == "*" {
37 return data.clone();
38 }
39 project_fields(data, &parse_fields(fields))
40}
41
42pub(crate) fn project_fields(data: &Value, allowed: &FieldTree) -> Value {
47 match data {
48 Value::Array(items) => {
49 if items
50 .iter()
51 .any(|item| !matches!(item, Value::Object(_) | Value::Null))
52 {
53 return data.clone();
54 }
55 Value::Array(
56 items
57 .iter()
58 .map(|item| match item {
59 Value::Object(map) => Value::Object(filter_map(map, allowed)),
60 other => other.clone(),
61 })
62 .collect(),
63 )
64 }
65 Value::Object(map) => Value::Object(filter_map(map, allowed)),
66 other => other.clone(),
67 }
68}
69
70fn insert_path(tree: &mut FieldTree, path: &str) {
71 let Some((top, rest)) = path.split_once('.') else {
72 tree.children.insert(path.to_owned(), None);
73 return;
74 };
75 if matches!(tree.children.get(top), Some(None)) {
76 return;
77 }
78 let subtree = tree
79 .children
80 .entry(top.to_owned())
81 .or_insert_with(|| Some(Box::<FieldTree>::default()));
82 if let Some(subtree) = subtree {
83 insert_path(subtree, rest);
84 }
85}
86
87fn filter_map(map: &Map<String, Value>, allowed: &FieldTree) -> Map<String, Value> {
88 let mut out = Map::new();
89 for (key, subtree) in &allowed.children {
90 let Some(value) = map.get(key) else {
91 continue;
92 };
93 let filtered = match subtree {
94 None => value.clone(),
95 Some(subtree) => filter_nested(value, subtree),
96 };
97 out.insert(key.clone(), filtered);
98 }
99 out
100}
101
102fn filter_nested(value: &Value, subtree: &FieldTree) -> Value {
103 match value {
104 Value::Object(map) => Value::Object(filter_map(map, subtree)),
105 Value::Array(items) => Value::Array(
106 items
107 .iter()
108 .map(|item| match item {
109 Value::Object(map) => Value::Object(filter_map(map, subtree)),
110 other => other.clone(),
111 })
112 .collect(),
113 ),
114 other => other.clone(),
115 }
116}