use std::any::Any;
pub trait Module: Send + Sync {
fn name(&self) -> &str;
fn routes(&self) -> Option<Vec<RouteEntry>> {
None
}
fn state(&self) -> Option<Box<dyn Any + Send + Sync>> {
None
}
}
#[derive(Debug, Clone)]
pub struct RouteEntry {
pub path: String,
pub method: String,
pub description: String,
}
pub struct ModuleBuilder {
modules: Vec<Box<dyn Module>>,
}
impl ModuleBuilder {
pub fn new() -> Self {
Self {
modules: Vec::new(),
}
}
pub fn register<M: Module + 'static>(mut self, module: M) -> Self {
self.modules.push(Box::new(module));
self
}
pub fn module_names(&self) -> Vec<&str> {
self.modules.iter().map(|m| m.name()).collect()
}
pub fn all_routes(&self) -> Vec<RouteEntry> {
self.modules
.iter()
.filter_map(|m| m.routes())
.flatten()
.collect()
}
pub fn module_count(&self) -> usize {
self.modules.len()
}
}
impl Default for ModuleBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct EmptyModule {
name: String,
}
impl EmptyModule {
pub fn new(name: impl Into<String>) -> Self {
Self { name: name.into() }
}
}
impl Module for EmptyModule {
fn name(&self) -> &str {
&self.name
}
}
pub struct RouteModule {
name: String,
routes: Vec<RouteEntry>,
}
impl RouteModule {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
routes: Vec::new(),
}
}
pub fn with_route(mut self, path: impl Into<String>, method: impl Into<String>, desc: impl Into<String>) -> Self {
self.routes.push(RouteEntry {
path: path.into(),
method: method.into(),
description: desc.into(),
});
self
}
}
impl Module for RouteModule {
fn name(&self) -> &str {
&self.name
}
fn routes(&self) -> Option<Vec<RouteEntry>> {
if self.routes.is_empty() {
None
} else {
Some(self.routes.clone())
}
}
}
pub struct StateModule {
name: String,
state: Option<Box<dyn Any + Send + Sync>>,
}
impl StateModule {
pub fn new(name: impl Into<String>, state: Box<dyn Any + Send + Sync>) -> Self {
Self {
name: name.into(),
state: Some(state),
}
}
}
impl Module for StateModule {
fn name(&self) -> &str {
&self.name
}
fn state(&self) -> Option<Box<dyn Any + Send + Sync>> {
self.state.as_ref().map(|s| {
Box::new(format!("state from {}", self.name)) as Box<dyn Any + Send + Sync>
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_module() {
let m = EmptyModule::new("test");
assert_eq!(m.name(), "test");
assert!(m.routes().is_none());
assert!(m.state().is_none());
}
#[test]
fn test_route_module() {
let m = RouteModule::new("api")
.with_route("/users", "GET", "List users")
.with_route("/users", "POST", "Create user");
assert_eq!(m.name(), "api");
let routes = m.routes().unwrap();
assert_eq!(routes.len(), 2);
assert_eq!(routes[0].path, "/users");
assert_eq!(routes[0].method, "GET");
}
#[test]
fn test_route_module_empty() {
let m = RouteModule::new("empty");
assert!(m.routes().is_none());
}
#[test]
fn test_module_builder_new() {
let builder = ModuleBuilder::new();
assert_eq!(builder.module_count(), 0);
}
#[test]
fn test_module_builder_register() {
let builder = ModuleBuilder::new()
.register(EmptyModule::new("mod1"))
.register(EmptyModule::new("mod2"));
assert_eq!(builder.module_count(), 2);
assert_eq!(builder.module_names(), vec!["mod1", "mod2"]);
}
#[test]
fn test_module_builder_all_routes() {
let builder = ModuleBuilder::new()
.register(RouteModule::new("api")
.with_route("/users", "GET", "List users"))
.register(RouteModule::new("admin")
.with_route("/admin", "GET", "Admin panel"));
let routes = builder.all_routes();
assert_eq!(routes.len(), 2);
}
#[test]
fn test_module_builder_mixed() {
let builder = ModuleBuilder::new()
.register(EmptyModule::new("empty"))
.register(RouteModule::new("routes")
.with_route("/test", "GET", "Test"));
assert_eq!(builder.module_count(), 2);
assert_eq!(builder.all_routes().len(), 1);
}
}