Skip to main content

bamboo_server/handlers/command/
types.rs

1use serde::{Deserialize, Serialize};
2
3/// Command type enumeration for categorizing different command sources.
4#[derive(Debug, Serialize, Deserialize, Clone)]
5#[serde(rename_all = "lowercase")]
6pub enum CommandType {
7    /// Markdown commands and stored prompt presets.
8    Prompt,
9    /// Workflow commands from markdown files.
10    Workflow,
11    /// Skill commands defined in the skill system.
12    Skill,
13    /// MCP (Model Context Protocol) tool commands.
14    Mcp,
15}
16
17/// Represents a unified command item from workflows, skills, and MCP tools.
18#[derive(Debug, Serialize, Clone)]
19pub struct CommandItem {
20    pub id: String,
21    pub name: String,
22    pub display_name: String,
23    pub description: String,
24    #[serde(rename = "type")]
25    pub command_type: String,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub category: Option<String>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub tags: Option<Vec<String>>,
30    pub metadata: serde_json::Value,
31}
32
33#[derive(Debug, Default, Deserialize)]
34pub struct ListCommandsQuery {
35    #[serde(default)]
36    pub workspace_path: Option<String>,
37    #[serde(default)]
38    pub session_id: Option<String>,
39}
40
41#[derive(Debug, Default, Deserialize)]
42pub struct GetCommandQuery {
43    #[serde(default)]
44    pub workspace_path: Option<String>,
45    #[serde(default)]
46    pub session_id: Option<String>,
47    #[serde(default)]
48    pub arguments: Option<String>,
49}
50
51/// Response structure for listing all available commands.
52#[derive(Debug, Serialize)]
53pub struct CommandListResponse {
54    pub commands: Vec<CommandItem>,
55    pub total: usize,
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_command_type_serialization() {
64        let workflow = CommandType::Workflow;
65        assert_eq!(serde_json::to_string(&workflow).unwrap(), "\"workflow\"");
66
67        let prompt = CommandType::Prompt;
68        assert_eq!(serde_json::to_string(&prompt).unwrap(), "\"prompt\"");
69
70        let skill = CommandType::Skill;
71        assert_eq!(serde_json::to_string(&skill).unwrap(), "\"skill\"");
72
73        let mcp = CommandType::Mcp;
74        assert_eq!(serde_json::to_string(&mcp).unwrap(), "\"mcp\"");
75    }
76
77    #[test]
78    fn test_command_type_deserialization() {
79        let workflow: CommandType = serde_json::from_str("\"workflow\"").unwrap();
80        assert!(matches!(workflow, CommandType::Workflow));
81
82        let skill: CommandType = serde_json::from_str("\"skill\"").unwrap();
83        assert!(matches!(skill, CommandType::Skill));
84
85        let mcp: CommandType = serde_json::from_str("\"mcp\"").unwrap();
86        assert!(matches!(mcp, CommandType::Mcp));
87    }
88
89    #[test]
90    fn test_command_type_clone() {
91        let cmd = CommandType::Workflow;
92        let cloned = cmd.clone();
93        assert!(matches!(cloned, CommandType::Workflow));
94    }
95
96    #[test]
97    fn test_command_type_debug() {
98        let cmd = CommandType::Skill;
99        let debug_str = format!("{:?}", cmd);
100        assert!(debug_str.contains("Skill"));
101    }
102
103    #[test]
104    fn test_command_item_serialization() {
105        let item = CommandItem {
106            id: "cmd-1".to_string(),
107            name: "test_command".to_string(),
108            display_name: "Test Command".to_string(),
109            description: "A test".to_string(),
110            command_type: "workflow".to_string(),
111            category: Some("general".to_string()),
112            tags: Some(vec!["test".to_string()]),
113            metadata: serde_json::json!({"key": "value"}),
114        };
115
116        let json = serde_json::to_string(&item).unwrap();
117        assert!(json.contains("cmd-1"));
118        assert!(json.contains("test_command"));
119        assert!(json.contains("Test Command"));
120        assert!(json.contains("workflow"));
121        assert!(json.contains("category"));
122        assert!(json.contains("tags"));
123    }
124
125    #[test]
126    fn test_command_item_skip_none_fields() {
127        let item = CommandItem {
128            id: "cmd-2".to_string(),
129            name: "test".to_string(),
130            display_name: "Test".to_string(),
131            description: "Desc".to_string(),
132            command_type: "skill".to_string(),
133            category: None,
134            tags: None,
135            metadata: serde_json::json!(null),
136        };
137
138        let json = serde_json::to_string(&item).unwrap();
139        assert!(!json.contains("category"));
140        assert!(!json.contains("tags"));
141    }
142
143    #[test]
144    fn test_command_item_debug() {
145        let item = CommandItem {
146            id: "id".to_string(),
147            name: "name".to_string(),
148            display_name: "Display".to_string(),
149            description: "desc".to_string(),
150            command_type: "mcp".to_string(),
151            category: None,
152            tags: None,
153            metadata: serde_json::json!({}),
154        };
155
156        let debug_str = format!("{:?}", item);
157        assert!(debug_str.contains("CommandItem"));
158    }
159
160    #[test]
161    fn test_command_list_response_serialization() {
162        let response = CommandListResponse {
163            commands: vec![],
164            total: 0,
165        };
166
167        let json = serde_json::to_string(&response).unwrap();
168        assert!(json.contains("\"commands\":[]"));
169        assert!(json.contains("\"total\":0"));
170    }
171
172    #[test]
173    fn test_command_list_response_with_commands() {
174        let item = CommandItem {
175            id: "1".to_string(),
176            name: "cmd".to_string(),
177            display_name: "Command".to_string(),
178            description: "Test".to_string(),
179            command_type: "workflow".to_string(),
180            category: None,
181            tags: None,
182            metadata: serde_json::json!({}),
183        };
184
185        let response = CommandListResponse {
186            commands: vec![item],
187            total: 1,
188        };
189
190        let json = serde_json::to_string(&response).unwrap();
191        assert!(json.contains("\"total\":1"));
192    }
193
194    #[test]
195    fn test_command_list_response_debug() {
196        let response = CommandListResponse {
197            commands: vec![],
198            total: 0,
199        };
200
201        let debug_str = format!("{:?}", response);
202        assert!(debug_str.contains("CommandListResponse"));
203    }
204}