intrepid_core/routing/
router.rs1use std::{collections::HashMap, sync::Arc};
2use tower::Service;
3use uuid::Uuid;
4
5use crate::{Frame, FrameFuture, Handler, MessageFrame};
6
7use super::{path_error::PathError, Endpoint, RouteId, Routes};
8
9#[derive(Clone, Default)]
11pub struct Router<State> {
12 routes: Arc<Routes>,
13 actions: HashMap<RouteId, Endpoint<State>>,
14}
15
16impl<State> Router<State>
17where
18 State: Clone + Send + Sync + 'static,
19{
20 pub fn new() -> Self {
22 Self {
23 routes: Arc::new(Routes::default()),
24 actions: HashMap::new(),
25 }
26 }
27
28 pub fn capture<T>(&self, path: impl AsRef<str>) -> Result<T, PathError>
30 where
31 T: serde::de::DeserializeOwned,
32 {
33 self.routes.capture(path)
34 }
35
36 pub fn handle_frame_with_state(&self, frame: Frame, state: State) -> FrameFuture {
38 let endpoint = match &frame.clone() {
39 Frame::Message(MessageFrame { uri, .. }) => {
40 if let Some(endpoint) = self
41 .routes
42 .at(uri)
43 .map(|(found_it, _)| found_it)
44 .and_then(|route_id| self.actions.get(route_id))
45 {
46 endpoint
47 } else {
48 return FrameFuture::empty();
49 }
50 }
51 _ => return FrameFuture::empty(),
52 };
53
54 let endpoint = endpoint.clone().into_inner();
55
56 endpoint.into_actionable(state).call(frame)
57 }
58
59 fn insert_endpoint(
60 &mut self,
61 route: impl Into<String>,
62 endpoint: Endpoint<State>,
63 ) -> crate::Result<()>
64 where
65 State: Clone + Send + 'static,
66 {
67 let route = {
68 let route = route.into();
69
70 match route.as_str() {
71 "" => "/".to_string(),
72 _ => route,
73 }
74 };
75
76 let initial_fetch = {
77 let key = route.as_str();
78 let fetch_by_path = self.routes.get_id(key);
79
80 fetch_by_path
81 .and_then(|route_id| self.actions.get(&route_id).map(|action| (route_id, action)))
82 };
83
84 #[expect(
86 unused_variables,
87 reason = "we want to add some feedback to indicate that the route already exists"
88 )]
89 if let Some((route_id, action)) = initial_fetch {
90 self.actions.insert(route_id, endpoint);
93 } else {
94 let route_id = Uuid::new_v4().into();
95
96 self.insert_route(&route, route_id);
97 self.actions.insert(route_id, endpoint);
98 }
99
100 Ok(())
101 }
102
103 fn insert_route(&mut self, route: &str, id: RouteId) {
104 let routes = Arc::make_mut(&mut self.routes);
105
106 routes.insert(route, id);
107 }
108
109 pub fn insert<ActionHandler, Args>(
111 &mut self,
112 route: impl Into<String>,
113 given_action: ActionHandler,
114 ) -> crate::Result<()>
115 where
116 ActionHandler: Handler<Args, State> + Clone + Send + Sync + 'static,
117 Args: Clone + Send + Sync + 'static,
118 {
119 let endpoint = Endpoint::new(given_action);
120
121 self.insert_endpoint(route, endpoint)
122 }
123
124 pub fn routes(&self) -> Vec<String> {
126 self.routes.paths()
127 }
128
129 pub fn endpoints(&self) -> Vec<Endpoint<State>>
132 where
133 State: Clone + Send + 'static,
134 {
135 let mut endpoints = Vec::new();
136
137 for endpoint in self.actions.values() {
138 endpoints.push(endpoint.clone());
139 }
140
141 endpoints
142 }
143
144 pub fn scope(&mut self, route: impl Into<String>, other: Router<State>) -> crate::Result<()>
146 where
147 State: Clone + Send + 'static,
148 {
149 let route = route.into();
150 let Router { routes, actions } = other;
151
152 for (id, action) in actions {
153 let path = routes.get_path(id).unwrap();
154 let route = {
155 let candidate = if path.starts_with('/') {
156 format!("{route}{path}")
157 } else {
158 format!("{route}/{path}")
159 };
160
161 match candidate.as_str() {
162 "/" => "/".to_string(),
163 partial if partial.ends_with('/') => partial[..partial.len() - 1].to_string(),
164 _ => candidate,
165 }
166 };
167
168 let nested_action = action.clone();
169
170 self.insert_endpoint(route, nested_action)?;
171 }
172
173 Ok(())
174 }
175}
176
177impl<State> std::fmt::Debug for Router<State> {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 f.debug_struct("Router")
180 .field("routes", &self.routes)
181 .finish()
182 }
183}