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