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
11#[must_use]
13pub fn parse_fields(fields: &str) -> FieldTree {
14 let mut root = FieldTree::default();
15 for part in fields
16 .split(',')
17 .map(str::trim)
18 .filter(|part| !part.is_empty())
19 {
20 insert_path(&mut root, part);
21 }
22 root
23}
24
25#[must_use]
27pub fn filter_fields(data: &Value, fields: &str) -> Value {
28 let fields = fields.trim();
29 if fields.is_empty() || fields == "all" || fields == "*" {
30 return data.clone();
31 }
32 let allowed = parse_fields(fields);
33 match data {
34 Value::Array(items) => {
35 if items
36 .iter()
37 .any(|item| !matches!(item, Value::Object(_) | Value::Null))
38 {
39 return data.clone();
40 }
41 Value::Array(
42 items
43 .iter()
44 .map(|item| match item {
45 Value::Object(map) => Value::Object(filter_map(map, &allowed)),
46 other => other.clone(),
47 })
48 .collect(),
49 )
50 }
51 Value::Object(map) => Value::Object(filter_map(map, &allowed)),
52 other => other.clone(),
53 }
54}
55
56fn insert_path(tree: &mut FieldTree, path: &str) {
57 let Some((top, rest)) = path.split_once('.') else {
58 tree.children.insert(path.to_owned(), None);
59 return;
60 };
61 if matches!(tree.children.get(top), Some(None)) {
62 return;
63 }
64 let subtree = tree
65 .children
66 .entry(top.to_owned())
67 .or_insert_with(|| Some(Box::<FieldTree>::default()));
68 if let Some(subtree) = subtree {
69 insert_path(subtree, rest);
70 }
71}
72
73fn filter_map(map: &Map<String, Value>, allowed: &FieldTree) -> Map<String, Value> {
74 let mut out = Map::new();
75 for (key, subtree) in &allowed.children {
76 let Some(value) = map.get(key) else {
77 continue;
78 };
79 let filtered = match subtree {
80 None => value.clone(),
81 Some(subtree) => filter_nested(value, subtree),
82 };
83 out.insert(key.clone(), filtered);
84 }
85 out
86}
87
88fn filter_nested(value: &Value, subtree: &FieldTree) -> Value {
89 match value {
90 Value::Object(map) => Value::Object(filter_map(map, subtree)),
91 Value::Array(items) => Value::Array(
92 items
93 .iter()
94 .map(|item| match item {
95 Value::Object(map) => Value::Object(filter_map(map, subtree)),
96 other => other.clone(),
97 })
98 .collect(),
99 ),
100 other => other.clone(),
101 }
102}