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 pub(crate) handler_timeout: Option<std::time::Duration>,
81 pub(crate) body_read_timeout: Option<std::time::Duration>,
83}
84
85pub(crate) fn join_paths(prefix: &str, rel: &str) -> String {
86 let a = prefix.trim_end_matches('/');
87 let b = rel.trim_start_matches('/');
88 match (a.is_empty(), b.is_empty()) {
89 (true, true) => "/".to_string(),
90 (false, true) => a.to_string(),
91 (true, false) => format!("/{b}"),
92 (false, false) => format!("{a}/{b}"),
93 }
94}
95
96impl Module {
97 pub(crate) fn flatten(
100 self,
101 prefix: &str,
102 parent_env: &DepEnv,
103 parent_mw: &[Arc<dyn Middleware>],
104 ) -> Vec<FlatRoute> {
105 let mut merged = parent_env.clone();
106 merged.merge_from(&self.env);
107
108 let mut mw: Vec<Arc<dyn Middleware>> = parent_mw.to_vec();
109 mw.extend(self.middleware);
110
111 let env = Arc::new(merged.clone());
112 let mw_arc: Arc<[Arc<dyn Middleware>]> = Arc::from(mw.clone());
113
114 let mut out = Vec::new();
115 for (path, methods) in self.routes {
116 let body_limit = methods.body_limit;
117 let handler_timeout = methods.handler_timeout;
118 let body_read_timeout = methods.body_read_timeout;
119 out.push(FlatRoute {
120 path: join_paths(prefix, &path),
121 methods,
122 env: env.clone(),
123 middleware: mw_arc.clone(),
124 body_limit,
125 handler_timeout,
126 body_read_timeout,
127 });
128 }
129 for (sub_prefix, child) in self.mounts {
130 let child_prefix = join_paths(prefix, &sub_prefix);
131 out.extend(child.flatten(&child_prefix, &merged, &mw));
132 }
133 out
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::router::get;
141
142 struct Cfg {
143 tag: &'static str,
144 }
145
146 fn leaf_paths(routes: &[FlatRoute]) -> Vec<String> {
147 routes.iter().map(|r| r.path.clone()).collect()
148 }
149
150 #[test]
151 fn nesting_composes_prefixes() {
152 let comments = Module::new("comments").route("/", get(|| async { "list" }));
153 let todos = Module::new("todos")
154 .route("/", get(|| async { "list" }))
155 .route("/{id}", get(|| async { "one" }))
156 .mount("/{id}/comments", comments);
157
158 let flat = todos.flatten("/todos", &DepEnv::default(), &[]);
159 assert_eq!(
160 leaf_paths(&flat),
161 vec!["/todos", "/todos/{id}", "/todos/{id}/comments"]
162 );
163 }
164
165 #[test]
166 fn module_env_shadows_parent_env() {
167 let parent = {
168 let mut e = DepEnv::default();
169 e.insert_value(Cfg { tag: "app" });
170 e
171 };
172 let child = Module::new("sub")
173 .provide(Cfg { tag: "module" })
174 .route("/", get(|| async { "x" }));
175 let flat = child.flatten("/sub", &parent, &[]);
176 let env = &flat[0].env;
177 let got = env
178 .singletons
179 .get(&std::any::TypeId::of::<Cfg>())
180 .and_then(|v| v.clone().downcast::<Cfg>().ok())
181 .unwrap();
182 assert_eq!(got.tag, "module");
183 }
184
185 #[test]
186 fn middleware_chains_accumulate_parent_first() {
187 struct Named(#[allow(dead_code)] &'static str);
188 impl Middleware for Named {
189 fn handle<'a>(
190 &'a self,
191 ctx: &'a mut crate::RequestCtx,
192 next: crate::middleware::Next<'a>,
193 ) -> crate::middleware::MiddlewareFuture<'a> {
194 next.run(ctx)
195 }
196 }
197 let inner = Module::new("inner")
198 .middleware(Named("inner"))
199 .route("/", get(|| async { "x" }));
200 let outer = Module::new("outer")
201 .middleware(Named("outer"))
202 .mount("/inner", inner);
203 let flat = outer.flatten("/outer", &DepEnv::default(), &[]);
204 assert_eq!(flat[0].middleware.len(), 2, "outer then inner");
205 }
206}