cobble/validator/
command_tree.rs1use serde::Deserialize;
2use std::collections::HashMap;
3use std::path::Path;
4
5#[derive(Debug, Clone, Deserialize)]
6pub struct CommandNode {
7 #[serde(rename = "type")]
8 pub node_type: String,
9 #[serde(default)]
10 pub children: HashMap<String, CommandNode>,
11 #[serde(default)]
12 pub executable: bool,
13 pub parser: Option<String>,
14 pub properties: Option<serde_json::Value>,
15 pub redirect: Option<Vec<String>>,
16}
17
18impl CommandNode {
19 pub fn from_file(path: &Path) -> Result<Self, String> {
20 let content = std::fs::read_to_string(path)
21 .map_err(|e| format!("Failed to read commands.json: {}", e))?;
22 Self::from_json_str(&content)
23 }
24
25 pub fn from_json_str(content: &str) -> Result<Self, String> {
26 let mut root: Self = serde_json::from_str(content)
27 .map_err(|e| format!("Failed to parse commands.json: {}", e))?;
28 root.fixup_root_redirects();
29 Ok(root)
30 }
31
32 fn fixup_root_redirects(&mut self) {
35 self.fixup_root_redirect(&["execute", "run"]);
36 self.fixup_root_redirect(&["return", "run"]);
37 }
38
39 fn fixup_root_redirect(&mut self, path: &[&str]) {
40 let mut node = self;
41 for segment in path {
42 let Some(child) = node.children.get_mut(*segment) else {
43 return;
44 };
45 node = child;
46 }
47
48 if node.redirect.is_none() && node.children.is_empty() {
49 node.redirect = Some(vec![]); }
51 }
52
53 pub fn resolve_redirect<'a>(
54 &'a self,
55 root: &'a CommandNode,
56 path: &[String],
57 ) -> Option<&'a CommandNode> {
58 if path.is_empty() {
59 return Some(root); }
61 let mut node = root;
62 for segment in path {
63 node = node.children.get(segment)?;
64 }
65 Some(node)
66 }
67}