Skip to main content

ry_core/
lib.rs

1//! RyDit Core - Trait y Registro para módulos
2//!
3//! Proporciona la interfaz común que todos los módulos deben implementar.
4//!
5//! # Versión
6//! **v0.8.2** - Sistema Universal Ry (metadata + hot reload hooks)
7
8use serde_json::Value;
9use std::collections::HashMap;
10
11/// Resultado de operación de módulo
12pub type ModuleResult = Result<Value, ModuleError>;
13
14/// Error de módulo
15#[derive(Debug, Clone)]
16pub struct ModuleError {
17    pub code: String,
18    pub message: String,
19}
20
21impl std::fmt::Display for ModuleError {
22    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
23        write!(f, "[{}] {}", self.code, self.message)
24    }
25}
26
27impl std::error::Error for ModuleError {}
28
29/// Metadata de un módulo (v0.8.2+)
30///
31/// Información descriptiva para carga dinámica y sistema de plugins
32#[derive(Debug, Clone, Default)]
33pub struct ModuleMetadata {
34    /// Nombre del módulo
35    pub name: &'static str,
36    /// Versión del módulo
37    pub version: &'static str,
38    /// Autores del módulo
39    pub authors: Vec<&'static str>,
40    /// Descripción del módulo
41    pub description: &'static str,
42    /// Licencia (ej: "MIT", "Apache-2.0")
43    pub license: &'static str,
44    /// Dependencias de otros módulos
45    pub dependencies: Vec<&'static str>,
46}
47
48impl ModuleMetadata {
49    /// Crea una nueva metadata vacía
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Establece el nombre del módulo
55    pub fn with_name(mut self, name: &'static str) -> Self {
56        self.name = name;
57        self
58    }
59
60    /// Establece la versión del módulo
61    pub fn with_version(mut self, version: &'static str) -> Self {
62        self.version = version;
63        self
64    }
65
66    /// Establece los autores del módulo
67    pub fn with_authors(mut self, authors: Vec<&'static str>) -> Self {
68        self.authors = authors;
69        self
70    }
71
72    /// Establece la descripción del módulo
73    pub fn with_description(mut self, description: &'static str) -> Self {
74        self.description = description;
75        self
76    }
77
78    /// Establece la licencia del módulo
79    pub fn with_license(mut self, license: &'static str) -> Self {
80        self.license = license;
81        self
82    }
83
84    /// Establece las dependencias del módulo
85    pub fn with_dependencies(mut self, deps: Vec<&'static str>) -> Self {
86        self.dependencies = deps;
87        self
88    }
89}
90
91/// Trait que todos los módulos deben implementar
92///
93/// # Ejemplo
94/// ```rust
95/// use ry_core::{RyditModule, ModuleResult, ModuleMetadata};
96/// use serde_json::Value;
97/// use std::collections::HashMap;
98///
99/// struct MiModulo;
100///
101/// impl RyditModule for MiModulo {
102///     fn name(&self) -> &'static str { "mi_modulo" }
103///     fn version(&self) -> &'static str { "1.0.0" }
104///     fn register(&self) -> HashMap<&'static str, &'static str> {
105///         let mut cmds = HashMap::new();
106///         cmds.insert("saludar", "Saluda al usuario");
107///         cmds
108///     }
109///     fn execute(&self, command: &str, params: Value) -> ModuleResult {
110///         match command {
111///             "saludar" => Ok(Value::String("Hola!".to_string())),
112///             _ => Err(ry_core::ModuleError {
113///                 code: "UNKNOWN_COMMAND".to_string(),
114///                 message: format!("Comando desconocido: {}", command),
115///             }),
116///         }
117///     }
118/// }
119/// ```
120pub trait RyditModule: Send + Sync {
121    /// Nombre único del módulo
122    fn name(&self) -> &'static str;
123
124    /// Versión del módulo
125    fn version(&self) -> &'static str;
126
127    /// Registro de comandos disponibles
128    /// Retorna: HashMap<nombre_comando, descripción>
129    fn register(&self) -> HashMap<&'static str, &'static str>;
130
131    /// Ejecuta un comando con parámetros
132    ///
133    /// # Arguments
134    /// * `command` - Nombre del comando
135    /// * `params` - Parámetros JSON
136    fn execute(&self, command: &str, params: Value) -> ModuleResult;
137
138    /// Metadata del módulo (v0.8.2+)
139    ///
140    /// Proporciona información descriptiva para el sistema de plugins
141    /// y carga dinámica. Por defecto retorna metadata básica.
142    fn metadata(&self) -> ModuleMetadata {
143        ModuleMetadata {
144            name: self.name(),
145            version: self.version(),
146            authors: vec![],
147            description: "",
148            license: "MIT",
149            dependencies: vec![],
150        }
151    }
152
153    /// Hook llamado antes de recargar el módulo (hot reload)
154    ///
155    /// Permite limpiar recursos o guardar estado antes de una recarga.
156    /// Por defecto no hace nada.
157    fn on_reload(&mut self) {}
158
159    /// Hook llamado al descargar el módulo
160    ///
161    /// Permite limpiar recursos asignados.
162    /// Por defecto no hace nada.
163    fn on_unload(&mut self) {}
164}
165
166/// Permite registrar `Box<dyn RyditModule>` directamente
167/// Necesario para carga dinámica (libloading) donde el módulo
168/// ya viene como `Box<dyn RyditModule>`
169impl RyditModule for Box<dyn RyditModule> {
170    fn name(&self) -> &'static str {
171        self.as_ref().name()
172    }
173    fn version(&self) -> &'static str {
174        self.as_ref().version()
175    }
176    fn register(&self) -> HashMap<&'static str, &'static str> {
177        self.as_ref().register()
178    }
179    fn execute(&self, command: &str, params: Value) -> ModuleResult {
180        self.as_ref().execute(command, params)
181    }
182    fn metadata(&self) -> ModuleMetadata {
183        self.as_ref().metadata()
184    }
185    fn on_reload(&mut self) {
186        self.as_mut().on_reload()
187    }
188    fn on_unload(&mut self) {
189        self.as_mut().on_unload()
190    }
191}
192
193/// Registro de módulos disponibles (v0.8.2+)
194///
195/// Soporta carga dinámica, hot reload y metadata de módulos
196#[derive(Default)]
197pub struct ModuleRegistry {
198    modules: HashMap<String, Box<dyn RyditModule>>,
199}
200
201impl ModuleRegistry {
202    /// Crea un nuevo registro vacío
203    pub fn new() -> Self {
204        Self {
205            modules: HashMap::new(),
206        }
207    }
208
209    /// Registra un módulo
210    pub fn register<M: RyditModule + 'static>(&mut self, module: M) {
211        let name = module.name().to_string();
212        self.modules.insert(name, Box::new(module));
213    }
214
215    /// Obtiene un módulo por nombre
216    pub fn get(&self, name: &str) -> Option<&dyn RyditModule> {
217        self.modules.get(name).map(|b| b.as_ref())
218    }
219
220    /// Obtiene un módulo mutable por nombre (para hot reload)
221    pub fn get_mut(&mut self, name: &str) -> Option<&mut Box<dyn RyditModule>> {
222        self.modules.get_mut(name)
223    }
224
225    /// Lista todos los módulos registrados
226    pub fn list(&self) -> Vec<&str> {
227        self.modules.keys().map(|s| s.as_str()).collect()
228    }
229
230    /// Lista todos los módulos con su metadata (v0.8.2+)
231    pub fn list_with_metadata(&self) -> Vec<(&str, ModuleMetadata)> {
232        self.modules
233            .values()
234            .map(|m| (m.name(), m.metadata()))
235            .collect()
236    }
237
238    /// Recarga un módulo (hot reload) (v0.8.2+)
239    ///
240    /// Llama al hook `on_reload()` del módulo.
241    pub fn reload(&mut self, name: &str) {
242        if let Some(module) = self.modules.get_mut(name) {
243            module.on_reload();
244        }
245    }
246
247    /// Descarga un módulo (v0.8.2+)
248    ///
249    /// Llama al hook `on_unload()` y luego remueve el módulo.
250    pub fn unload(&mut self, name: &str) {
251        if let Some(mut module) = self.modules.remove(name) {
252            module.on_unload();
253        }
254    }
255
256    /// Verifica si un módulo está registrado
257    pub fn contains(&self, name: &str) -> bool {
258        self.modules.contains_key(name)
259    }
260
261    /// Obtiene el número de módulos registrados
262    pub fn len(&self) -> usize {
263        self.modules.len()
264    }
265
266    /// Verifica si el registro está vacío
267    pub fn is_empty(&self) -> bool {
268        self.modules.is_empty()
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    // Módulo de prueba para tests
277    struct TestModule;
278
279    impl RyditModule for TestModule {
280        fn name(&self) -> &'static str {
281            "test"
282        }
283
284        fn version(&self) -> &'static str {
285            "1.0.0"
286        }
287
288        fn register(&self) -> HashMap<&'static str, &'static str> {
289            let mut cmds = HashMap::new();
290            cmds.insert("ping", "Test ping command");
291            cmds.insert("echo", "Test echo command");
292            cmds
293        }
294
295        fn execute(&self, command: &str, params: Value) -> ModuleResult {
296            match command {
297                "ping" => Ok(Value::String("pong".to_string())),
298                "echo" => Ok(params),
299                _ => Err(ModuleError {
300                    code: "UNKNOWN_COMMAND".to_string(),
301                    message: format!("Unknown command: {}", command),
302                }),
303            }
304        }
305
306        fn metadata(&self) -> ModuleMetadata {
307            ModuleMetadata::new()
308                .with_name("test")
309                .with_version("1.0.0")
310                .with_description("Módulo de prueba para tests")
311                .with_license("MIT")
312        }
313    }
314
315    #[test]
316    fn test_module_registry() {
317        let mut registry = ModuleRegistry::new();
318        registry.register(TestModule);
319
320        assert_eq!(registry.list().len(), 1);
321        assert!(registry.get("test").is_some());
322        assert!(registry.get("unknown").is_none());
323    }
324
325    #[test]
326    fn test_module_execute_ping() {
327        let mut registry = ModuleRegistry::new();
328        registry.register(TestModule);
329
330        let module = registry.get("test").unwrap();
331        let result = module.execute("ping", Value::Null).unwrap();
332        assert_eq!(result, Value::String("pong".to_string()));
333    }
334
335    #[test]
336    fn test_module_execute_echo() {
337        let mut registry = ModuleRegistry::new();
338        registry.register(TestModule);
339
340        let module = registry.get("test").unwrap();
341        let input = Value::String("hello".to_string());
342        let result = module.execute("echo", input.clone()).unwrap();
343        assert_eq!(result, input);
344    }
345
346    #[test]
347    fn test_module_error() {
348        let mut registry = ModuleRegistry::new();
349        registry.register(TestModule);
350
351        let module = registry.get("test").unwrap();
352        let result = module.execute("unknown", Value::Null);
353        assert!(result.is_err());
354
355        let err = result.unwrap_err();
356        assert_eq!(err.code, "UNKNOWN_COMMAND");
357    }
358
359    #[test]
360    fn test_module_metadata() {
361        let mut registry = ModuleRegistry::new();
362        registry.register(TestModule);
363
364        let module = registry.get("test").unwrap();
365        let metadata = module.metadata();
366
367        assert_eq!(metadata.name, "test");
368        assert_eq!(metadata.version, "1.0.0");
369        assert_eq!(metadata.description, "Módulo de prueba para tests");
370        assert_eq!(metadata.license, "MIT");
371    }
372
373    #[test]
374    fn test_module_registry_with_metadata() {
375        let mut registry = ModuleRegistry::new();
376        registry.register(TestModule);
377
378        let list = registry.list_with_metadata();
379        assert_eq!(list.len(), 1);
380
381        let (name, metadata) = &list[0];
382        assert_eq!(*name, "test");
383        assert_eq!(metadata.name, "test");
384    }
385
386    #[test]
387    fn test_module_reload() {
388        let mut registry = ModuleRegistry::new();
389        registry.register(TestModule);
390
391        // El reload debería funcionar (on_reload por defecto no hace nada)
392        registry.reload("test");
393        assert!(registry.contains("test"));
394
395        // Reload en módulo inexistente no hace nada
396        registry.reload("nonexistent");
397        assert!(!registry.contains("nonexistent"));
398    }
399
400    #[test]
401    fn test_module_unload() {
402        let mut registry = ModuleRegistry::new();
403        registry.register(TestModule);
404
405        assert!(registry.contains("test"));
406
407        // Unload debería funcionar
408        registry.unload("test");
409        assert!(!registry.contains("test"));
410
411        // Unload de módulo inexistente no hace nada
412        registry.unload("nonexistent");
413        assert!(!registry.contains("nonexistent"));
414    }
415
416    #[test]
417    fn test_module_registry_len() {
418        let mut registry = ModuleRegistry::new();
419        assert!(registry.is_empty());
420
421        registry.register(TestModule);
422        assert_eq!(registry.len(), 1);
423        assert!(!registry.is_empty());
424
425        registry.unload("test");
426        assert!(registry.is_empty());
427    }
428}