use serde_json::Value;
use std::collections::HashMap;
pub type ModuleResult = Result<Value, ModuleError>;
#[derive(Debug, Clone)]
pub struct ModuleError {
pub code: String,
pub message: String,
}
impl std::fmt::Display for ModuleError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for ModuleError {}
#[derive(Debug, Clone, Default)]
pub struct ModuleMetadata {
pub name: &'static str,
pub version: &'static str,
pub authors: Vec<&'static str>,
pub description: &'static str,
pub license: &'static str,
pub dependencies: Vec<&'static str>,
}
impl ModuleMetadata {
pub fn new() -> Self {
Self::default()
}
pub fn with_name(mut self, name: &'static str) -> Self {
self.name = name;
self
}
pub fn with_version(mut self, version: &'static str) -> Self {
self.version = version;
self
}
pub fn with_authors(mut self, authors: Vec<&'static str>) -> Self {
self.authors = authors;
self
}
pub fn with_description(mut self, description: &'static str) -> Self {
self.description = description;
self
}
pub fn with_license(mut self, license: &'static str) -> Self {
self.license = license;
self
}
pub fn with_dependencies(mut self, deps: Vec<&'static str>) -> Self {
self.dependencies = deps;
self
}
}
pub trait RyditModule: Send + Sync {
fn name(&self) -> &'static str;
fn version(&self) -> &'static str;
fn register(&self) -> HashMap<&'static str, &'static str>;
fn execute(&self, command: &str, params: Value) -> ModuleResult;
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata {
name: self.name(),
version: self.version(),
authors: vec![],
description: "",
license: "MIT",
dependencies: vec![],
}
}
fn on_reload(&mut self) {}
fn on_unload(&mut self) {}
}
impl RyditModule for Box<dyn RyditModule> {
fn name(&self) -> &'static str {
self.as_ref().name()
}
fn version(&self) -> &'static str {
self.as_ref().version()
}
fn register(&self) -> HashMap<&'static str, &'static str> {
self.as_ref().register()
}
fn execute(&self, command: &str, params: Value) -> ModuleResult {
self.as_ref().execute(command, params)
}
fn metadata(&self) -> ModuleMetadata {
self.as_ref().metadata()
}
fn on_reload(&mut self) {
self.as_mut().on_reload()
}
fn on_unload(&mut self) {
self.as_mut().on_unload()
}
}
#[derive(Default)]
pub struct ModuleRegistry {
modules: HashMap<String, Box<dyn RyditModule>>,
}
impl ModuleRegistry {
pub fn new() -> Self {
Self {
modules: HashMap::new(),
}
}
pub fn register<M: RyditModule + 'static>(&mut self, module: M) {
let name = module.name().to_string();
self.modules.insert(name, Box::new(module));
}
pub fn get(&self, name: &str) -> Option<&dyn RyditModule> {
self.modules.get(name).map(|b| b.as_ref())
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut Box<dyn RyditModule>> {
self.modules.get_mut(name)
}
pub fn list(&self) -> Vec<&str> {
self.modules.keys().map(|s| s.as_str()).collect()
}
pub fn list_with_metadata(&self) -> Vec<(&str, ModuleMetadata)> {
self.modules
.values()
.map(|m| (m.name(), m.metadata()))
.collect()
}
pub fn reload(&mut self, name: &str) {
if let Some(module) = self.modules.get_mut(name) {
module.on_reload();
}
}
pub fn unload(&mut self, name: &str) {
if let Some(mut module) = self.modules.remove(name) {
module.on_unload();
}
}
pub fn contains(&self, name: &str) -> bool {
self.modules.contains_key(name)
}
pub fn len(&self) -> usize {
self.modules.len()
}
pub fn is_empty(&self) -> bool {
self.modules.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestModule;
impl RyditModule for TestModule {
fn name(&self) -> &'static str {
"test"
}
fn version(&self) -> &'static str {
"1.0.0"
}
fn register(&self) -> HashMap<&'static str, &'static str> {
let mut cmds = HashMap::new();
cmds.insert("ping", "Test ping command");
cmds.insert("echo", "Test echo command");
cmds
}
fn execute(&self, command: &str, params: Value) -> ModuleResult {
match command {
"ping" => Ok(Value::String("pong".to_string())),
"echo" => Ok(params),
_ => Err(ModuleError {
code: "UNKNOWN_COMMAND".to_string(),
message: format!("Unknown command: {}", command),
}),
}
}
fn metadata(&self) -> ModuleMetadata {
ModuleMetadata::new()
.with_name("test")
.with_version("1.0.0")
.with_description("Módulo de prueba para tests")
.with_license("MIT")
}
}
#[test]
fn test_module_registry() {
let mut registry = ModuleRegistry::new();
registry.register(TestModule);
assert_eq!(registry.list().len(), 1);
assert!(registry.get("test").is_some());
assert!(registry.get("unknown").is_none());
}
#[test]
fn test_module_execute_ping() {
let mut registry = ModuleRegistry::new();
registry.register(TestModule);
let module = registry.get("test").unwrap();
let result = module.execute("ping", Value::Null).unwrap();
assert_eq!(result, Value::String("pong".to_string()));
}
#[test]
fn test_module_execute_echo() {
let mut registry = ModuleRegistry::new();
registry.register(TestModule);
let module = registry.get("test").unwrap();
let input = Value::String("hello".to_string());
let result = module.execute("echo", input.clone()).unwrap();
assert_eq!(result, input);
}
#[test]
fn test_module_error() {
let mut registry = ModuleRegistry::new();
registry.register(TestModule);
let module = registry.get("test").unwrap();
let result = module.execute("unknown", Value::Null);
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.code, "UNKNOWN_COMMAND");
}
#[test]
fn test_module_metadata() {
let mut registry = ModuleRegistry::new();
registry.register(TestModule);
let module = registry.get("test").unwrap();
let metadata = module.metadata();
assert_eq!(metadata.name, "test");
assert_eq!(metadata.version, "1.0.0");
assert_eq!(metadata.description, "Módulo de prueba para tests");
assert_eq!(metadata.license, "MIT");
}
#[test]
fn test_module_registry_with_metadata() {
let mut registry = ModuleRegistry::new();
registry.register(TestModule);
let list = registry.list_with_metadata();
assert_eq!(list.len(), 1);
let (name, metadata) = &list[0];
assert_eq!(*name, "test");
assert_eq!(metadata.name, "test");
}
#[test]
fn test_module_reload() {
let mut registry = ModuleRegistry::new();
registry.register(TestModule);
registry.reload("test");
assert!(registry.contains("test"));
registry.reload("nonexistent");
assert!(!registry.contains("nonexistent"));
}
#[test]
fn test_module_unload() {
let mut registry = ModuleRegistry::new();
registry.register(TestModule);
assert!(registry.contains("test"));
registry.unload("test");
assert!(!registry.contains("test"));
registry.unload("nonexistent");
assert!(!registry.contains("nonexistent"));
}
#[test]
fn test_module_registry_len() {
let mut registry = ModuleRegistry::new();
assert!(registry.is_empty());
registry.register(TestModule);
assert_eq!(registry.len(), 1);
assert!(!registry.is_empty());
registry.unload("test");
assert!(registry.is_empty());
}
}