Skip to main content

cobble/validator/
command_tree.rs

1use 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    /// The JSON export from Minecraft omits some command redirects to root.
33    /// Add them back so nested commands validate the same way Brigadier does.
34    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![]); // empty vec = redirect to root
50        }
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); // redirect to root
60        }
61        let mut node = root;
62        for segment in path {
63            node = node.children.get(segment)?;
64        }
65        Some(node)
66    }
67}