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