a3s_boot/app/
application.rs1use super::builder::BootApplicationBuilder;
2use crate::{
3 BootError, BootRequest, BootResponse, HttpAdapter, HttpMethod, Module, ModuleRef, Result,
4 RouteDefinition,
5};
6use std::collections::BTreeMap;
7use std::fmt;
8use std::net::SocketAddr;
9use std::sync::Arc;
10
11pub struct RouteMatch<'a> {
13 route: &'a RouteDefinition,
14 params: BTreeMap<String, String>,
15}
16
17impl<'a> RouteMatch<'a> {
18 pub fn route(&self) -> &'a RouteDefinition {
19 self.route
20 }
21
22 pub fn params(&self) -> &BTreeMap<String, String> {
23 &self.params
24 }
25
26 pub fn param(&self, name: &str) -> Option<&str> {
27 self.params.get(name).map(String::as_str)
28 }
29
30 pub fn into_params(self) -> BTreeMap<String, String> {
31 self.params
32 }
33}
34
35impl fmt::Debug for RouteMatch<'_> {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 f.debug_struct("RouteMatch")
38 .field("method", &self.route.method())
39 .field("path", &self.route.path())
40 .field("params", &self.params)
41 .finish()
42 }
43}
44
45#[derive(Clone)]
47pub struct BootApplication {
48 pub(crate) routes: Vec<RouteDefinition>,
49 pub(crate) modules: Vec<String>,
50 pub(crate) module_ref: ModuleRef,
51 pub(crate) module_instances: Vec<Arc<dyn Module>>,
52}
53
54impl BootApplication {
55 pub fn builder() -> BootApplicationBuilder {
57 BootApplicationBuilder::new()
58 }
59
60 pub fn module_names(&self) -> &[String] {
62 &self.modules
63 }
64
65 pub fn routes(&self) -> &[RouteDefinition] {
67 &self.routes
68 }
69
70 pub fn route_for(&self, method: HttpMethod, path: &str) -> Option<&RouteDefinition> {
72 let best_specificity = best_path_specificity(&self.routes, path)?;
73 self.routes.iter().find(|route| {
74 route.matches_path_shape(path)
75 && route.path_specificity().as_slice() == best_specificity.as_slice()
76 && route.method() == method
77 })
78 }
79
80 pub fn route_match(&self, method: HttpMethod, path: &str) -> Result<Option<RouteMatch<'_>>> {
82 let Some(route) = self.route_for(method, path) else {
83 return Ok(None);
84 };
85 let Some(params) = route.path_params(path)? else {
86 return Ok(None);
87 };
88 Ok(Some(RouteMatch { route, params }))
89 }
90
91 pub fn allowed_methods(&self, path: &str) -> Vec<HttpMethod> {
93 let Some(best_specificity) = best_path_specificity(&self.routes, path) else {
94 return Vec::new();
95 };
96
97 let mut methods = Vec::new();
98 for route in matching_routes_with_specificity(&self.routes, path, &best_specificity) {
99 if !methods.contains(&route.method()) {
100 methods.push(route.method());
101 }
102 }
103 methods
104 }
105
106 pub fn allowed_methods_header(&self, path: &str) -> Option<String> {
108 let methods = self.allowed_methods(path);
109 if methods.is_empty() {
110 return None;
111 }
112
113 Some(
114 methods
115 .into_iter()
116 .map(|method| method.as_str())
117 .collect::<Vec<_>>()
118 .join(","),
119 )
120 }
121
122 pub fn module_ref(&self) -> &ModuleRef {
124 &self.module_ref
125 }
126
127 pub fn get<T>(&self) -> Result<Arc<T>>
129 where
130 T: Send + Sync + 'static,
131 {
132 self.module_ref.get::<T>()
133 }
134
135 pub fn get_named<T>(&self, token: &str) -> Result<Arc<T>>
137 where
138 T: Send + Sync + 'static,
139 {
140 self.module_ref.get_named::<T>(token)
141 }
142
143 pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>>
145 where
146 T: Send + Sync + 'static,
147 {
148 self.module_ref.get_optional::<T>()
149 }
150
151 pub fn get_optional_named<T>(&self, token: &str) -> Result<Option<Arc<T>>>
153 where
154 T: Send + Sync + 'static,
155 {
156 self.module_ref.get_optional_named::<T>(token)
157 }
158
159 pub async fn call(&self, request: BootRequest) -> Result<BootResponse> {
161 let path = request.path.clone();
162 let Some(best_specificity) = best_path_specificity(&self.routes, &path) else {
163 return Err(BootError::NotFound(format!(
164 "{} {}",
165 request.method.as_str(),
166 request.path
167 )));
168 };
169
170 if let Some(route) =
171 matching_route_with_specificity(&self.routes, &path, &best_specificity, |route| {
172 route.method() == request.method
173 })
174 {
175 return route.call(request).await;
176 }
177
178 if let Some(route) =
179 matching_route_with_specificity(&self.routes, &path, &best_specificity, |_| true)
180 {
181 return route.call(request).await;
182 }
183
184 Err(BootError::NotFound(format!(
185 "{} {}",
186 request.method.as_str(),
187 request.path
188 )))
189 }
190
191 pub async fn handle(&self, request: BootRequest) -> BootResponse {
193 match self.call(request).await {
194 Ok(response) => response,
195 Err(error) => BootResponse::from_error(&error),
196 }
197 }
198
199 pub async fn bootstrap(&self) -> Result<()> {
201 for module in &self.module_instances {
202 module
203 .on_application_bootstrap(self.module_ref.clone())
204 .await?;
205 }
206 Ok(())
207 }
208
209 pub async fn shutdown(&self) -> Result<()> {
211 for module in self.module_instances.iter().rev() {
212 module
213 .on_application_shutdown(self.module_ref.clone())
214 .await?;
215 }
216 Ok(())
217 }
218
219 pub fn into_adapter<A>(self, adapter: &A) -> Result<A::Output>
221 where
222 A: HttpAdapter,
223 {
224 adapter.build(self)
225 }
226
227 pub async fn serve_with<A>(self, adapter: &A, addr: SocketAddr) -> Result<()>
229 where
230 A: HttpAdapter,
231 {
232 let app_for_shutdown = self.clone();
233 if let Err(error) = self.bootstrap().await {
234 let _ = app_for_shutdown.shutdown().await;
235 return Err(error);
236 }
237 let serve_result = adapter.serve(self, addr).await;
238 let shutdown_result = app_for_shutdown.shutdown().await;
239
240 match (serve_result, shutdown_result) {
241 (Err(error), _) => Err(error),
242 (Ok(()), Err(error)) => Err(error),
243 (Ok(()), Ok(())) => Ok(()),
244 }
245 }
246}
247
248fn best_path_specificity(routes: &[RouteDefinition], path: &str) -> Option<Vec<u8>> {
249 let mut best_specificity = None;
250
251 for route in routes {
252 if route.matches_path_shape(path) {
253 let specificity = route.path_specificity();
254 if best_specificity
255 .as_ref()
256 .map(|best| specificity > *best)
257 .unwrap_or(true)
258 {
259 best_specificity = Some(specificity);
260 }
261 }
262 }
263
264 best_specificity
265}
266
267fn matching_routes_with_specificity<'a>(
268 routes: &'a [RouteDefinition],
269 path: &'a str,
270 specificity: &'a [u8],
271) -> impl Iterator<Item = &'a RouteDefinition> + 'a {
272 routes.iter().filter(move |route| {
273 route.matches_path_shape(path) && route.path_specificity().as_slice() == specificity
274 })
275}
276
277fn matching_route_with_specificity<'a, P>(
278 routes: &'a [RouteDefinition],
279 path: &'a str,
280 specificity: &'a [u8],
281 mut predicate: P,
282) -> Option<&'a RouteDefinition>
283where
284 P: FnMut(&RouteDefinition) -> bool,
285{
286 matching_routes_with_specificity(routes, path, specificity).find(|route| predicate(route))
287}