use serde_json::{Map, Value};
use super::consts::{SchemaKeys, SectionKeys, tree_prefixes};
use super::format;
use super::sections::{Section, TreeNode, TreeSection};
fn flatten_node(node: &TreeNode, line_prefix: &str, continuation: &str) -> Vec<String> {
let mut out = Vec::new();
if let Some(ref val) = node.value {
if node.children.is_empty() {
out.push(format::prefixed_label_with_value(
line_prefix,
&node.label,
val,
));
return out;
}
out.push(format::prefixed_label_with_value(
line_prefix,
&node.label,
val,
));
} else {
out.push(format::prefixed_label(line_prefix, &node.label));
}
let n = node.children.len();
for (i, child) in node.children.iter().enumerate() {
let (branch, cont) = tree_prefixes(continuation, i == n.saturating_sub(1));
if !child.branch
&& child.children.is_empty()
&& let Some(ref v) = child.value
{
out.push(format!(
"{} {}: {}",
branch,
format::format_key(&child.label),
v
));
} else {
out.extend(flatten_node(child, &branch, &cont));
}
}
out
}
fn schema_node_tree(value: &Value, label: &str) -> TreeNode {
let label = label.to_string();
let Value::Object(map) = value else {
return TreeNode {
label,
value: Some(format::value_to_string(
value,
format::DEFAULT_MAX_ARRAY_INLINE,
)),
children: Vec::new(),
branch: false,
};
};
if map.is_empty() {
return TreeNode {
label,
value: None,
children: Vec::new(),
branch: false,
};
}
if SchemaKeys::has_children_or_attributes(map) {
let mut children = Vec::new();
if let Some(attrs) = map.get(SchemaKeys::ATTRIBUTES).and_then(Value::as_object)
&& !attrs.is_empty()
{
for (k, v) in attrs {
children.push(TreeNode {
label: k.clone(),
value: Some(format::value_to_string(v, format::DEFAULT_MAX_ARRAY_INLINE)),
children: Vec::new(),
branch: false,
});
}
}
if let Some(children_map) = map
.get(SchemaKeys::CHILDREN)
.and_then(Value::as_object)
.filter(|c| !c.is_empty())
{
children.extend(walk_schema_child_nodes(children_map));
}
let branch = !children.is_empty();
return TreeNode {
label,
value: None,
children,
branch,
};
}
let children = walk_schema_child_nodes(map);
let branch = !children.is_empty();
TreeNode {
label,
value: None,
children,
branch,
}
}
fn walk_schema_child_nodes(map: &Map<String, Value>) -> Vec<TreeNode> {
map.iter()
.map(|(name, val)| schema_node_tree(val, name))
.collect()
}
fn schema_value_to_tree(value: &Value) -> Vec<TreeNode> {
match value {
Value::Object(map) if !map.is_empty() => map
.iter()
.map(|(name, node_val)| schema_node_tree(node_val, name))
.collect(),
Value::Array(arr) if !arr.is_empty() => arr
.iter()
.map(|v| {
let label = v
.as_object()
.and_then(|o| {
o.get("name")
.or_else(|| o.get("type"))
.or_else(|| o.get("id"))
.and_then(Value::as_str)
})
.unwrap_or("…");
schema_node_tree(v, label)
})
.collect(),
_ => vec![schema_node_tree(value, "…")],
}
}
#[must_use]
pub fn tree_roots_to_lines(roots: &[TreeNode]) -> Vec<String> {
let mut lines = Vec::new();
for (idx, root) in roots.iter().enumerate() {
if idx > 0 {
lines.push(String::new());
}
lines.extend(flatten_node(root, "", ""));
}
if lines.is_empty() {
lines.push("—".to_string());
}
lines
}
pub fn push_schema_section(sections: &mut Vec<Section>, value: &Value) {
let mut roots = schema_value_to_tree(value);
if roots.is_empty() && !value.is_null() {
roots.push(TreeNode {
label: "—".into(),
value: None,
children: Vec::new(),
branch: false,
});
}
sections.push(Section::Tree(TreeSection {
title: format::format_key(SectionKeys::SCHEMA),
roots,
}));
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn schema_tree_flattens_like_prefixes() {
let v = json!({
"root": {
"attributes": { "id": "1" },
"children": {
"child": {}
}
}
});
let roots = schema_value_to_tree(&v);
let lines = tree_roots_to_lines(&roots);
assert!(lines.iter().any(|l| l.contains("root")));
assert!(lines.iter().any(|l| l.contains("Id") && l.contains('1')));
assert!(lines.iter().any(|l| l.contains("child")));
}
}