1use crate::dep::{DepEnv, DepFactory};
6use crate::middleware::Middleware;
7use crate::router::MethodRouter;
8use std::sync::Arc;
9
10pub struct Module {
12 pub(crate) name: String,
13 pub(crate) routes: Vec<(String, MethodRouter)>,
14 pub(crate) mounts: Vec<(String, Module)>,
15 pub(crate) env: DepEnv,
16 pub(crate) middleware: Vec<Arc<dyn Middleware>>,
17}
18
19impl Module {
20 pub fn new(name: impl Into<String>) -> Self {
21 Self {
22 name: name.into(),
23 routes: Vec::new(),
24 mounts: Vec::new(),
25 env: DepEnv::default(),
26 middleware: Vec::new(),
27 }
28 }
29
30 pub fn route(mut self, path: &str, methods: MethodRouter) -> Self {
32 self.routes.push((path.to_string(), methods));
33 self
34 }
35
36 pub fn mount(mut self, prefix: &str, child: Module) -> Self {
38 self.mounts.push((prefix.to_string(), child));
39 self
40 }
41
42 pub fn provide<T: Send + Sync + 'static>(mut self, value: T) -> Self {
44 self.env.insert_value(value);
45 self
46 }
47
48 pub fn provide_dep<F, Args, T>(mut self, factory: F) -> Self
50 where
51 F: DepFactory<Args, T>,
52 T: Send + Sync + 'static,
53 {
54 self.env.insert_factory(factory);
55 self
56 }
57
58 pub fn middleware<M: Middleware>(mut self, mw: M) -> Self {
60 self.middleware.push(Arc::new(mw));
61 self
62 }
63
64 pub fn name(&self) -> &str {
67 &self.name
68 }
69}
70
71pub(crate) struct FlatRoute {
73 pub(crate) path: String,
74 pub(crate) methods: MethodRouter,
75 pub(crate) env: Arc<DepEnv>,
76 pub(crate) middleware: Arc<[Arc<dyn Middleware>]>,
77 pub(crate) body_limit: Option<usize>,
79}
80
81pub(crate) fn join_paths(prefix: &str, rel: &str) -> String {
82 let a = prefix.trim_end_matches('/');
83 let b = rel.trim_start_matches('/');
84 match (a.is_empty(), b.is_empty()) {
85 (true, true) => "/".to_string(),
86 (false, true) => a.to_string(),
87 (true, false) => format!("/{b}"),
88 (false, false) => format!("{a}/{b}"),
89 }
90}
91
92impl Module {
93 pub(crate) fn flatten(
96 self,
97 prefix: &str,
98 parent_env: &DepEnv,
99 parent_mw: &[Arc<dyn Middleware>],
100 ) -> Vec<FlatRoute> {
101 let mut merged = parent_env.clone();
102 merged.merge_from(&self.env);
103
104 let mut mw: Vec<Arc<dyn Middleware>> = parent_mw.to_vec();
105 mw.extend(self.middleware);
106
107 let env = Arc::new(merged.clone());
108 let mw_arc: Arc<[Arc<dyn Middleware>]> = Arc::from(mw.clone());
109
110 let mut out = Vec::new();
111 for (path, methods) in self.routes {
112 let body_limit = methods.body_limit;
113 out.push(FlatRoute {
114 path: join_paths(prefix, &path),
115 methods,
116 env: env.clone(),
117 middleware: mw_arc.clone(),
118 body_limit,
119 });
120 }
121 for (sub_prefix, child) in self.mounts {
122 let child_prefix = join_paths(prefix, &sub_prefix);
123 out.extend(child.flatten(&child_prefix, &merged, &mw));
124 }
125 out
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::router::get;
133
134 struct Cfg {
135 tag: &'static str,
136 }
137
138 fn leaf_paths(routes: &[FlatRoute]) -> Vec<String> {
139 routes.iter().map(|r| r.path.clone()).collect()
140 }
141
142 #[test]
143 fn nesting_composes_prefixes() {
144 let comments = Module::new("comments").route("/", get(|| async { "list" }));
145 let todos = Module::new("todos")
146 .route("/", get(|| async { "list" }))
147 .route("/{id}", get(|| async { "one" }))
148 .mount("/{id}/comments", comments);
149
150 let flat = todos.flatten("/todos", &DepEnv::default(), &[]);
151 assert_eq!(
152 leaf_paths(&flat),
153 vec!["/todos", "/todos/{id}", "/todos/{id}/comments"]
154 );
155 }
156
157 #[test]
158 fn module_env_shadows_parent_env() {
159 let parent = {
160 let mut e = DepEnv::default();
161 e.insert_value(Cfg { tag: "app" });
162 e
163 };
164 let child = Module::new("sub")
165 .provide(Cfg { tag: "module" })
166 .route("/", get(|| async { "x" }));
167 let flat = child.flatten("/sub", &parent, &[]);
168 let env = &flat[0].env;
169 let got = env
170 .singletons
171 .get(&std::any::TypeId::of::<Cfg>())
172 .and_then(|v| v.clone().downcast::<Cfg>().ok())
173 .unwrap();
174 assert_eq!(got.tag, "module");
175 }
176
177 #[test]
178 fn middleware_chains_accumulate_parent_first() {
179 struct Named(#[allow(dead_code)] &'static str);
180 impl Middleware for Named {
181 fn handle<'a>(
182 &'a self,
183 ctx: &'a mut crate::RequestCtx,
184 next: crate::middleware::Next<'a>,
185 ) -> crate::middleware::MiddlewareFuture<'a> {
186 next.run(ctx)
187 }
188 }
189 let inner = Module::new("inner")
190 .middleware(Named("inner"))
191 .route("/", get(|| async { "x" }));
192 let outer = Module::new("outer")
193 .middleware(Named("outer"))
194 .mount("/inner", inner);
195 let flat = outer.flatten("/outer", &DepEnv::default(), &[]);
196 assert_eq!(flat[0].middleware.len(), 2, "outer then inner");
197 }
198}