1use std::collections::HashMap;
6use serde_json::Value;
7
8pub type ModuleResult = Result<Value, ModuleError>;
10
11#[derive(Debug, Clone)]
13pub struct ModuleError {
14 pub code: String,
15 pub message: String,
16}
17
18impl std::fmt::Display for ModuleError {
19 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
20 write!(f, "[{}] {}", self.code, self.message)
21 }
22}
23
24impl std::error::Error for ModuleError {}
25
26pub trait RyditModule: Send + Sync {
28 fn name(&self) -> &'static str;
30
31 fn version(&self) -> &'static str;
33
34 fn register(&self) -> HashMap<&'static str, &'static str>;
37
38 fn execute(&self, command: &str, params: Value) -> ModuleResult;
44}
45
46#[derive(Default)]
48pub struct ModuleRegistry {
49 modules: HashMap<String, Box<dyn RyditModule>>,
50}
51
52impl ModuleRegistry {
53 pub fn new() -> Self {
55 Self {
56 modules: HashMap::new(),
57 }
58 }
59
60 pub fn register<M: RyditModule + 'static>(&mut self, module: M) {
62 let name = module.name().to_string();
63 self.modules.insert(name, Box::new(module));
64 }
65
66 pub fn get(&self, name: &str) -> Option<&dyn RyditModule> {
68 self.modules.get(name).map(|b| b.as_ref())
69 }
70
71 pub fn list(&self) -> Vec<&str> {
73 self.modules.keys().map(|s| s.as_str()).collect()
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 struct TestModule;
83
84 impl RyditModule for TestModule {
85 fn name(&self) -> &'static str {
86 "test"
87 }
88
89 fn version(&self) -> &'static str {
90 "1.0.0"
91 }
92
93 fn register(&self) -> HashMap<&'static str, &'static str> {
94 let mut cmds = HashMap::new();
95 cmds.insert("ping", "Test ping command");
96 cmds.insert("echo", "Test echo command");
97 cmds
98 }
99
100 fn execute(&self, command: &str, params: Value) -> ModuleResult {
101 match command {
102 "ping" => Ok(Value::String("pong".to_string())),
103 "echo" => Ok(params),
104 _ => Err(ModuleError {
105 code: "UNKNOWN_COMMAND".to_string(),
106 message: format!("Unknown command: {}", command),
107 }),
108 }
109 }
110 }
111
112 #[test]
113 fn test_module_registry() {
114 let mut registry = ModuleRegistry::new();
115 registry.register(TestModule);
116
117 assert_eq!(registry.list().len(), 1);
118 assert!(registry.get("test").is_some());
119 assert!(registry.get("unknown").is_none());
120 }
121
122 #[test]
123 fn test_module_execute_ping() {
124 let mut registry = ModuleRegistry::new();
125 registry.register(TestModule);
126
127 let module = registry.get("test").unwrap();
128 let result = module.execute("ping", Value::Null).unwrap();
129 assert_eq!(result, Value::String("pong".to_string()));
130 }
131
132 #[test]
133 fn test_module_execute_echo() {
134 let mut registry = ModuleRegistry::new();
135 registry.register(TestModule);
136
137 let module = registry.get("test").unwrap();
138 let input = Value::String("hello".to_string());
139 let result = module.execute("echo", input.clone()).unwrap();
140 assert_eq!(result, input);
141 }
142
143 #[test]
144 fn test_module_error() {
145 let mut registry = ModuleRegistry::new();
146 registry.register(TestModule);
147
148 let module = registry.get("test").unwrap();
149 let result = module.execute("unknown", Value::Null);
150 assert!(result.is_err());
151
152 let err = result.unwrap_err();
153 assert_eq!(err.code, "UNKNOWN_COMMAND");
154 }
155}