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