1pub(crate) mod matcher;
2pub(crate) mod names;
3#[cfg(test)]
4mod tests;
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use crate::request::Request;
10use crate::response::Response;
11use crate::server::ConnectionInfo;
12use matcher::Segment;
13
14pub use names::{build_path, name_route, named_pattern, named_routes, url_for};
15
16#[derive(Clone)]
25pub struct PathParams {
26 params: HashMap<String, String>,
27}
28
29impl PathParams {
30 #[doc(hidden)]
33 pub fn from_map(params: HashMap<String, String>) -> Self {
34 PathParams { params }
35 }
36
37 pub fn get(&self, name: &str) -> Option<&str> {
39 self.params.get(name).map(String::as_str)
40 }
41}
42
43#[doc(hidden)]
48pub fn request_host<'a>(request: &'a Request, connection: &'a ConnectionInfo) -> &'a str {
49 request.headers.iter()
50 .find(|h| h.name.eq_ignore_ascii_case("host"))
51 .map(|h| h.value.as_ref())
52 .filter(|v: &&str| !v.is_empty())
53 .or(connection.sni_hostname.as_deref())
54 .unwrap_or("")
55}
56
57#[doc(hidden)]
60pub fn host_matches(expected: &str, actual: &str) -> bool {
61 let actual = if expected.contains(':') {
62 actual
63 } else if let Some(rest) = actual.strip_prefix('[') {
64 rest.split(']').next().unwrap_or(rest)
66 } else {
67 actual.rsplit_once(':').map_or(actual, |(host, port)| if port.bytes().all(|b| b.is_ascii_digit()) { host } else { actual })
68 };
69 let expected = expected.trim_start_matches('[').trim_end_matches(']');
70 actual.eq_ignore_ascii_case(expected)
71}
72
73type HandlerFn =
74 Arc<dyn Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static>;
75
76#[derive(Clone)]
77struct Route {
78 method: String,
79 segments: Vec<Segment>,
80 handler: HandlerFn,
81}
82
83#[derive(Clone)]
124pub struct RouteInfo {
125 pub method: String,
126 pub pattern: String,
127}
128
129#[derive(Clone, Debug, PartialEq, Eq)]
134pub struct RouteConflict {
135 pub method: String,
136 pub pattern: String,
138 pub earlier: String,
140 pub kind: RouteConflictKind,
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
145pub enum RouteConflictKind {
146 Duplicate,
148 Shadowed,
151}
152
153impl std::fmt::Display for RouteConflict {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 match self.kind {
156 RouteConflictKind::Duplicate => write!(
157 f,
158 "route {} {} is registered twice (first as {} {}); the second never runs",
159 self.method, self.pattern, self.method, self.earlier
160 ),
161 RouteConflictKind::Shadowed => write!(
162 f,
163 "route {} {} never runs: {} {}, registered before it, matches every path it does",
164 self.method, self.pattern, self.method, self.earlier
165 ),
166 }
167 }
168}
169
170impl RouteConflict {
171 #[doc(hidden)]
173 pub fn warn_once(&self) {
174 use std::collections::HashSet;
175 use std::sync::{Mutex, OnceLock};
176 static SEEN: OnceLock<Mutex<HashSet<String>>> = OnceLock::new();
177 let message = self.to_string();
178 let fresh = SEEN
179 .get_or_init(|| Mutex::new(HashSet::new()))
180 .lock()
181 .map(|mut seen| seen.insert(message.clone()))
182 .unwrap_or(false);
183 if fresh {
184 eprintln!("rws: warning: {message}");
185 }
186 }
187}
188
189#[doc(hidden)]
191pub fn conflicts<'a>(routes: impl Iterator<Item = (&'a str, &'a [Segment])>) -> Vec<RouteConflict> {
192 let all: Vec<(&str, &[Segment])> = routes.collect();
193 (1..all.len())
194 .filter_map(|i| conflict_with(all[..i].iter().copied(), all[i].0, all[i].1))
195 .collect()
196}
197
198pub(crate) fn conflict_with<'a>(
200 earlier: impl Iterator<Item = (&'a str, &'a [Segment])>,
201 method: &str,
202 segments: &[Segment],
203) -> Option<RouteConflict> {
204 if matcher::never_matches(segments) {
205 return None;
206 }
207 for (m, segs) in earlier {
208 if m != method {
209 continue;
210 }
211 let kind = if matcher::equivalent(segs, segments) {
212 RouteConflictKind::Duplicate
213 } else if matcher::covers(segs, segments) {
214 RouteConflictKind::Shadowed
215 } else {
216 continue;
217 };
218 return Some(RouteConflict {
219 method: method.to_string(),
220 pattern: matcher::segments_to_pattern(segments),
221 earlier: matcher::segments_to_pattern(segs),
222 kind,
223 });
224 }
225 None
226}
227
228#[derive(Clone)]
229pub struct Router {
230 routes: Vec<Route>,
231 host: Option<String>,
234}
235
236impl Router {
237 pub fn new() -> Self {
238 Router { routes: Vec::new(), host: None }
239 }
240
241 pub fn with_host(mut self, host: &str) -> Self {
245 self.host = Some(host.to_string());
246 self
247 }
248
249 pub fn get<F>(self, pattern: &str, handler: F) -> Self
251 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
252 self.add("GET", pattern, handler)
253 }
254
255 #[cfg(not(target_arch = "wasm32"))]
273 pub fn websocket<F>(self, pattern: &str, handler: F) -> Self
274 where
275 F: Fn(crate::websocket::WebSocketConnection, PathParams) + Send + Sync + 'static,
276 {
277 let handler = Arc::new(handler);
278 self.get(pattern, move |req, params, _| {
279 let (handler, params) = (Arc::clone(&handler), params.clone());
280 crate::websocket::WebSocket::upgrade(req, move |ws| handler(ws, params))
281 })
282 }
283
284 pub fn post<F>(self, pattern: &str, handler: F) -> Self
286 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
287 self.add("POST", pattern, handler)
288 }
289
290 pub fn put<F>(self, pattern: &str, handler: F) -> Self
292 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
293 self.add("PUT", pattern, handler)
294 }
295
296 pub fn patch<F>(self, pattern: &str, handler: F) -> Self
298 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
299 self.add("PATCH", pattern, handler)
300 }
301
302 pub fn delete<F>(self, pattern: &str, handler: F) -> Self
304 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
305 self.add("DELETE", pattern, handler)
306 }
307
308 #[doc(hidden)]
309 pub fn add<F>(self, method: &str, pattern: &str, handler: F) -> Self
310 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
311 let router = self.add_unchecked(method, pattern, handler);
312 if let Some(conflict) = router.last_route_conflict() {
313 conflict.warn_once();
314 }
315 router
316 }
317
318 pub(crate) fn add_unchecked<F>(mut self, method: &str, pattern: &str, handler: F) -> Self
321 where F: Fn(&Request, &PathParams, &ConnectionInfo) -> Response + Send + Sync + 'static {
322 self.routes.push(Route {
323 method: method.to_string(),
324 segments: matcher::parse_pattern(pattern),
325 handler: Arc::new(handler),
326 });
327 self
328 }
329
330 pub fn route<H: crate::handler::Handler<()>>(self, handler: H) -> Self {
343 let unit = Arc::new(());
344 self.add(H::METHOD, H::PATTERN, move |request, params, connection| {
345 handler.handle(request, params, connection, &unit)
346 })
347 }
348
349 pub fn route_conflicts(&self) -> Vec<RouteConflict> {
355 conflicts(self.routes.iter().map(|r| (r.method.as_str(), r.segments.as_slice())))
356 }
357
358 pub(crate) fn last_pattern(&self) -> Option<String> {
360 self.routes.last().map(|r| matcher::segments_to_pattern(&r.segments))
361 }
362
363 fn last_route_conflict(&self) -> Option<RouteConflict> {
364 let (last, earlier) = self.routes.split_last()?;
365 conflict_with(earlier.iter().map(|r| (r.method.as_str(), r.segments.as_slice())), &last.method, &last.segments)
366 }
367
368 pub fn route_entries(&self) -> Vec<RouteInfo> {
373 self.routes.iter().map(|r| RouteInfo {
374 method: r.method.clone(),
375 pattern: matcher::segments_to_pattern(&r.segments),
376 }).collect()
377 }
378
379 pub fn handle(&self, request: &Request, connection: &ConnectionInfo) -> Option<Response> {
387 let (route, params) = self.find(request, connection)?;
388 Some((route.handler)(request, ¶ms, connection))
389 }
390
391 pub fn matches(&self, request: &Request, connection: &ConnectionInfo) -> bool {
394 self.find(request, connection).is_some()
395 }
396
397 fn find(&self, request: &Request, connection: &ConnectionInfo) -> Option<(&Route, PathParams)> {
398 if let Some(required_host) = &self.host {
399 if !host_matches(required_host, request_host(request, connection)) {
400 return None;
401 }
402 }
403
404 let path = request.request_uri.split('?').next().unwrap_or(&request.request_uri);
405 let path_segs: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
406
407 for route in &self.routes {
408 if route.method != request.method {
409 continue;
410 }
411 if let Some(params) = matcher::try_match(&route.segments, &path_segs) {
412 return Some((route, PathParams::from_map(params)));
413 }
414 }
415 None
416 }
417}