1use super::builder::BootApplicationBuilder;
2use crate::versioning::ApiVersionCandidate;
3use crate::{
4 ApiVersioning, BootError, BootRequest, BootResponse, DiscoveryService, HttpAdapter, HttpMethod,
5 LazyModuleLoader, MessagePatternDefinition, MessageTransport, Module, ModuleRef,
6 OpenApiDocument, OpenApiInfo, ProviderToken, Reflector, Result, RouteDefinition,
7 TransportMessage, TransportReply, WebSocketGatewayDefinition,
8};
9use std::collections::BTreeMap;
10use std::fmt;
11use std::net::SocketAddr;
12use std::sync::Arc;
13
14#[derive(Clone)]
15pub(crate) struct ModuleInstance {
16 pub module: Arc<dyn Module>,
17 pub module_ref: ModuleRef,
18 pub imports: Vec<String>,
19 pub exports: Vec<ProviderToken>,
20 pub is_global: bool,
21 pub route_prefix: Option<String>,
22}
23
24pub struct RouteMatch<'a> {
26 route: &'a RouteDefinition,
27 params: BTreeMap<String, String>,
28}
29
30impl<'a> RouteMatch<'a> {
31 pub fn route(&self) -> &'a RouteDefinition {
32 self.route
33 }
34
35 pub fn params(&self) -> &BTreeMap<String, String> {
36 &self.params
37 }
38
39 pub fn param(&self, name: &str) -> Option<&str> {
40 self.params.get(name).map(String::as_str)
41 }
42
43 pub fn into_params(self) -> BTreeMap<String, String> {
44 self.params
45 }
46}
47
48impl fmt::Debug for RouteMatch<'_> {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 f.debug_struct("RouteMatch")
51 .field("method", &self.route.method())
52 .field("path", &self.route.path())
53 .field("params", &self.params)
54 .finish()
55 }
56}
57
58#[derive(Clone)]
60pub struct BootApplication {
61 pub(crate) routes: Vec<RouteDefinition>,
62 pub(crate) gateways: Vec<WebSocketGatewayDefinition>,
63 pub(crate) message_patterns: Vec<MessagePatternDefinition>,
64 pub(crate) modules: Vec<String>,
65 pub(crate) module_ref: ModuleRef,
66 pub(crate) module_instances: Vec<ModuleInstance>,
67 pub(crate) api_versioning: Option<ApiVersioning>,
68}
69
70impl BootApplication {
71 pub fn builder() -> BootApplicationBuilder {
73 BootApplicationBuilder::new()
74 }
75
76 pub fn module_names(&self) -> &[String] {
78 &self.modules
79 }
80
81 pub fn routes(&self) -> &[RouteDefinition] {
83 &self.routes
84 }
85
86 pub fn api_versioning(&self) -> Option<&ApiVersioning> {
88 self.api_versioning.as_ref()
89 }
90
91 pub fn gateways(&self) -> &[WebSocketGatewayDefinition] {
93 &self.gateways
94 }
95
96 pub fn message_patterns(&self) -> &[MessagePatternDefinition] {
98 &self.message_patterns
99 }
100
101 pub fn discovery(&self) -> Result<DiscoveryService> {
103 DiscoveryService::from_app(self)
104 }
105
106 pub fn reflector(&self) -> Result<Reflector> {
108 Reflector::from_app(self)
109 }
110
111 pub fn openapi(&self, info: OpenApiInfo) -> OpenApiDocument {
113 OpenApiDocument::from_routes(info, &self.routes)
114 }
115
116 pub fn route_for(&self, method: HttpMethod, path: &str) -> Option<&RouteDefinition> {
118 let candidates = self.path_candidates(path);
119 self.route_for_candidates(method, &candidates)
120 .map(|matched| matched.route)
121 }
122
123 pub fn route_match(&self, method: HttpMethod, path: &str) -> Result<Option<RouteMatch<'_>>> {
125 let candidates = self.path_candidates(path);
126 let Some(matched) = self.route_for_candidates(method, &candidates) else {
127 return Ok(None);
128 };
129 let Some(params) = matched.route.path_params(&matched.path)? else {
130 return Ok(None);
131 };
132 Ok(Some(RouteMatch {
133 route: matched.route,
134 params,
135 }))
136 }
137
138 pub fn gateway_for(&self, path: &str) -> Option<&WebSocketGatewayDefinition> {
139 self.gateways
140 .iter()
141 .find(|gateway| gateway.matches_path(path))
142 }
143
144 pub fn message_pattern_for(&self, pattern: &str) -> Option<&MessagePatternDefinition> {
145 self.message_patterns
146 .iter()
147 .find(|definition| definition.pattern() == pattern)
148 }
149
150 pub fn allowed_methods(&self, path: &str) -> Vec<HttpMethod> {
152 let candidates = self.path_candidates(path);
153 let Some((candidate_index, best_specificity)) =
154 best_path_specificity(&self.routes, &candidates, self.api_versioning.as_ref())
155 else {
156 return Vec::new();
157 };
158 let candidate = &candidates[candidate_index];
159
160 let mut methods = Vec::new();
161 let mut has_wildcard = false;
162 for route in &self.routes {
163 if !route_matches_candidate(
164 route,
165 candidate,
166 &best_specificity,
167 self.api_versioning.as_ref(),
168 ) {
169 continue;
170 }
171
172 if route.method().is_wildcard() {
173 has_wildcard = true;
174 continue;
175 }
176
177 if !methods.contains(&route.method()) {
178 methods.push(route.method());
179 }
180 }
181 if has_wildcard {
182 return HttpMethod::standard_methods().to_vec();
183 }
184 methods
185 }
186
187 pub fn allowed_methods_header(&self, path: &str) -> Option<String> {
189 let methods = self.allowed_methods(path);
190 if methods.is_empty() {
191 return None;
192 }
193
194 Some(
195 methods
196 .into_iter()
197 .map(|method| method.as_str())
198 .collect::<Vec<_>>()
199 .join(","),
200 )
201 }
202
203 pub fn module_ref(&self) -> &ModuleRef {
205 &self.module_ref
206 }
207
208 pub fn lazy_module_loader(&self) -> Result<Arc<LazyModuleLoader>> {
210 self.get::<LazyModuleLoader>()
211 }
212
213 pub fn get<T>(&self) -> Result<Arc<T>>
215 where
216 T: Send + Sync + 'static,
217 {
218 self.module_ref.get::<T>()
219 }
220
221 pub fn get_named<T>(&self, token: &str) -> Result<Arc<T>>
223 where
224 T: Send + Sync + 'static,
225 {
226 self.module_ref.get_named::<T>(token)
227 }
228
229 pub fn get_optional<T>(&self) -> Result<Option<Arc<T>>>
231 where
232 T: Send + Sync + 'static,
233 {
234 self.module_ref.get_optional::<T>()
235 }
236
237 pub fn get_optional_named<T>(&self, token: &str) -> Result<Option<Arc<T>>>
239 where
240 T: Send + Sync + 'static,
241 {
242 self.module_ref.get_optional_named::<T>(token)
243 }
244
245 pub async fn call(&self, request: BootRequest) -> Result<BootResponse> {
247 let candidates = self.request_candidates(&request);
248 let host = request.host();
249 let Some((candidate_index, best_specificity)) = best_request_specificity(
250 &self.routes,
251 &candidates,
252 self.api_versioning.as_ref(),
253 host,
254 ) else {
255 return Err(BootError::NotFound(format!(
256 "{} {}",
257 request.method.as_str(),
258 request.path
259 )));
260 };
261 let candidate = &candidates[candidate_index];
262
263 if let Some(route) = matching_route_with_specificity(
264 &self.routes,
265 candidate,
266 &best_specificity,
267 self.api_versioning.as_ref(),
268 host,
269 |route| route.method() == request.method,
270 ) {
271 let request = request.with_matched_path(candidate.path.clone());
272 return route.call(request).await;
273 }
274
275 if let Some(route) = matching_route_with_specificity(
276 &self.routes,
277 candidate,
278 &best_specificity,
279 self.api_versioning.as_ref(),
280 host,
281 |route| route.method().matches(request.method),
282 ) {
283 let request = request.with_matched_path(candidate.path.clone());
284 return route.call(request).await;
285 }
286
287 if let Some(route) = matching_route_with_specificity(
288 &self.routes,
289 candidate,
290 &best_specificity,
291 self.api_versioning.as_ref(),
292 host,
293 |_| true,
294 ) {
295 let request = request.with_matched_path(candidate.path.clone());
296 return route.call(request).await;
297 }
298
299 Err(BootError::NotFound(format!(
300 "{} {}",
301 request.method.as_str(),
302 request.path
303 )))
304 }
305
306 pub async fn handle(&self, request: BootRequest) -> BootResponse {
308 match self.call(request).await {
309 Ok(response) => response,
310 Err(error) => BootResponse::from_error(&error),
311 }
312 }
313
314 pub async fn dispatch_message(
316 &self,
317 message: TransportMessage,
318 ) -> Result<Option<TransportReply>> {
319 let pattern = message.pattern.clone();
320 let Some(definition) = self.message_pattern_for(&pattern) else {
321 return Err(BootError::NotFound(format!("message pattern {pattern}")));
322 };
323 definition.dispatch(message).await
324 }
325
326 pub async fn emit_message(&self, message: TransportMessage) -> Result<()> {
328 self.dispatch_message(message).await.map(|_| ())
329 }
330
331 pub async fn bootstrap(&self) -> Result<()> {
333 for instance in &self.module_instances {
334 instance.module_ref.bootstrap_local_providers().await?;
335 instance
336 .module
337 .on_application_bootstrap(instance.module_ref.clone())
338 .await?;
339 }
340 for gateway in &self.gateways {
341 gateway.after_init().await?;
342 }
343 Ok(())
344 }
345
346 pub async fn shutdown(&self) -> Result<()> {
348 self.shutdown_inner(None).await
349 }
350
351 pub async fn shutdown_with_signal(&self, signal: impl Into<String>) -> Result<()> {
353 self.shutdown_inner(Some(signal.into())).await
354 }
355
356 async fn shutdown_inner(&self, signal: Option<String>) -> Result<()> {
357 for instance in self.module_instances.iter().rev() {
358 instance
359 .module
360 .on_module_destroy(instance.module_ref.clone(), signal.clone())
361 .await?;
362 instance
363 .module_ref
364 .destroy_local_providers(signal.clone())
365 .await?;
366 }
367 for instance in self.module_instances.iter().rev() {
368 instance
369 .module
370 .before_application_shutdown(instance.module_ref.clone(), signal.clone())
371 .await?;
372 instance
373 .module_ref
374 .before_application_shutdown_local_providers(signal.clone())
375 .await?;
376 }
377 for instance in self.module_instances.iter().rev() {
378 instance
379 .module
380 .on_application_shutdown_with_signal(instance.module_ref.clone(), signal.clone())
381 .await?;
382 instance
383 .module_ref
384 .shutdown_local_providers(signal.clone())
385 .await?;
386 }
387 Ok(())
388 }
389
390 pub fn into_adapter<A>(self, adapter: &A) -> Result<A::Output>
392 where
393 A: HttpAdapter,
394 {
395 adapter.build(self)
396 }
397
398 pub fn into_message_transport<T>(self, transport: &T) -> Result<T::Output>
400 where
401 T: MessageTransport,
402 {
403 transport.build(self)
404 }
405
406 pub async fn serve_with<A>(self, adapter: &A, addr: SocketAddr) -> Result<()>
408 where
409 A: HttpAdapter,
410 {
411 let app_for_shutdown = self.clone();
412 if let Err(error) = self.bootstrap().await {
413 let _ = app_for_shutdown.shutdown().await;
414 return Err(error);
415 }
416 let serve_result = adapter.serve(self, addr).await;
417 let shutdown_result = app_for_shutdown.shutdown().await;
418
419 match (serve_result, shutdown_result) {
420 (Err(error), _) => Err(error),
421 (Ok(()), Err(error)) => Err(error),
422 (Ok(()), Ok(())) => Ok(()),
423 }
424 }
425
426 fn request_candidates(&self, request: &BootRequest) -> Vec<ApiVersionCandidate> {
427 match self.api_versioning.as_ref() {
428 Some(versioning) => versioning.request_candidates(request),
429 None => vec![ApiVersionCandidate {
430 path: request.path.clone(),
431 version: None,
432 }],
433 }
434 }
435
436 fn path_candidates(&self, path: &str) -> Vec<ApiVersionCandidate> {
437 match self.api_versioning.as_ref() {
438 Some(versioning) => versioning.path_candidates(path),
439 None => vec![ApiVersionCandidate {
440 path: path.to_string(),
441 version: None,
442 }],
443 }
444 }
445
446 fn route_for_candidates<'a>(
447 &'a self,
448 method: HttpMethod,
449 candidates: &[ApiVersionCandidate],
450 ) -> Option<MatchedRoute<'a>> {
451 let (candidate_index, best_specificity) =
452 best_path_specificity(&self.routes, candidates, self.api_versioning.as_ref())?;
453 let candidate = &candidates[candidate_index];
454 let exact = self.routes.iter().find(|route| {
455 route_matches_candidate(
456 route,
457 candidate,
458 &best_specificity,
459 self.api_versioning.as_ref(),
460 ) && route.method() == method
461 });
462 let route = exact.or_else(|| {
463 self.routes.iter().find(|route| {
464 route_matches_candidate(
465 route,
466 candidate,
467 &best_specificity,
468 self.api_versioning.as_ref(),
469 ) && route.method().matches(method)
470 })
471 })?;
472
473 Some(MatchedRoute {
474 route,
475 path: candidate.path.clone(),
476 })
477 }
478}
479
480struct MatchedRoute<'a> {
481 route: &'a RouteDefinition,
482 path: String,
483}
484
485fn best_path_specificity(
486 routes: &[RouteDefinition],
487 candidates: &[ApiVersionCandidate],
488 versioning: Option<&ApiVersioning>,
489) -> Option<(usize, Vec<u8>)> {
490 let mut best = None;
491
492 for (candidate_index, candidate) in candidates.iter().enumerate() {
493 for route in routes {
494 if route_matches_path_and_version(route, candidate, versioning) {
495 let specificity = route.path_specificity();
496 if best
497 .as_ref()
498 .map(|(_, best_specificity)| specificity > *best_specificity)
499 .unwrap_or(true)
500 {
501 best = Some((candidate_index, specificity));
502 }
503 }
504 }
505 }
506
507 best
508}
509
510fn matching_route_with_specificity<'a, P>(
511 routes: &'a [RouteDefinition],
512 candidate: &ApiVersionCandidate,
513 specificity: &[u8],
514 versioning: Option<&ApiVersioning>,
515 host: Option<&str>,
516 mut predicate: P,
517) -> Option<&'a RouteDefinition>
518where
519 P: FnMut(&RouteDefinition) -> bool,
520{
521 routes.iter().find(|route| {
522 route_matches_request_candidate(route, candidate, specificity, versioning, host)
523 && predicate(route)
524 })
525}
526
527fn best_request_specificity(
528 routes: &[RouteDefinition],
529 candidates: &[ApiVersionCandidate],
530 versioning: Option<&ApiVersioning>,
531 host: Option<&str>,
532) -> Option<(usize, Vec<u8>)> {
533 let mut best = None;
534
535 for (candidate_index, candidate) in candidates.iter().enumerate() {
536 for route in routes {
537 if route_matches_path_version_and_host(route, candidate, versioning, host) {
538 let specificity = request_specificity(route);
539 if best
540 .as_ref()
541 .map(|(_, best_specificity)| specificity > *best_specificity)
542 .unwrap_or(true)
543 {
544 best = Some((candidate_index, specificity));
545 }
546 }
547 }
548 }
549
550 best
551}
552
553fn route_matches_candidate(
554 route: &RouteDefinition,
555 candidate: &ApiVersionCandidate,
556 specificity: &[u8],
557 versioning: Option<&ApiVersioning>,
558) -> bool {
559 route_matches_path_and_version(route, candidate, versioning)
560 && route.path_specificity().as_slice() == specificity
561}
562
563fn route_matches_request_candidate(
564 route: &RouteDefinition,
565 candidate: &ApiVersionCandidate,
566 specificity: &[u8],
567 versioning: Option<&ApiVersioning>,
568 host: Option<&str>,
569) -> bool {
570 route_matches_path_version_and_host(route, candidate, versioning, host)
571 && request_specificity(route).as_slice() == specificity
572}
573
574fn route_matches_path_and_version(
575 route: &RouteDefinition,
576 candidate: &ApiVersionCandidate,
577 versioning: Option<&ApiVersioning>,
578) -> bool {
579 if !route.matches_path_shape(&candidate.path) {
580 return false;
581 }
582
583 match versioning {
584 Some(versioning) => route
585 .versioning()
586 .matches(candidate.version.as_deref(), versioning.default_version()),
587 None => true,
588 }
589}
590
591fn route_matches_path_version_and_host(
592 route: &RouteDefinition,
593 candidate: &ApiVersionCandidate,
594 versioning: Option<&ApiVersioning>,
595 host: Option<&str>,
596) -> bool {
597 route_matches_path_and_version(route, candidate, versioning) && route.matches_host(host)
598}
599
600fn request_specificity(route: &RouteDefinition) -> Vec<u8> {
601 let mut specificity = route.path_specificity();
602 specificity.push(2);
603 specificity.extend(route.host_specificity());
604 specificity
605}