1use super::application::BootApplication;
2use super::lazy::LazyModuleLoader;
3use super::registration::{ModuleRegistrationSink, ModuleRegistry};
4use crate::pipeline::PipelineComponents;
5#[cfg(feature = "auth")]
6use crate::AuthGuard;
7use crate::{
8 catch_errors, ApiVersioning, BootError, BootErrorKind, BootResponse, ExceptionFilter,
9 ExecutionInterceptor, Guard, Interceptor, MessagePatternDefinition, Middleware,
10 MiddlewareRoute, Module, ModuleRef, OpenApiDocument, OpenApiInfo, Pipe, ProviderDefinition,
11 ProviderToken, Result, RouteDefinition, SerializationInterceptor, TransportExceptionFilter,
12 TransportGuard, TransportInterceptor, TransportPipe, ValidationOptions,
13 WebSocketExceptionFilter, WebSocketGatewayDefinition, WebSocketGuard, WebSocketInterceptor,
14 WebSocketPipe,
15};
16#[cfg(feature = "compression")]
17use crate::{CompressionInterceptor, CompressionOptions};
18#[cfg(feature = "security")]
19use crate::{
20 CorsMiddleware, CorsOptions, CorsPreflightRoute, CorsResponseInterceptor, CsrfGuard,
21 CsrfOptions, RateLimitGuard, RateLimitOptions, SecurityHeadersInterceptor,
22 SecurityHeadersOptions,
23};
24#[cfg(feature = "session")]
25use crate::{SessionCookieInterceptor, SessionManager, SessionMiddleware, SessionModule};
26use std::collections::{BTreeMap, BTreeSet};
27use std::sync::Arc;
28
29#[derive(Default)]
31pub struct BootApplicationBuilder {
32 modules: Vec<Arc<dyn Module>>,
33 routes: Vec<RouteDefinition>,
34 gateways: Vec<WebSocketGatewayDefinition>,
35 message_patterns: Vec<MessagePatternDefinition>,
36 global_pipeline: PipelineComponents,
37 global_execution_guards: Vec<Arc<dyn Guard>>,
38 global_execution_interceptors: Vec<Arc<dyn ExecutionInterceptor>>,
39 global_websocket_guards: Vec<Arc<dyn WebSocketGuard>>,
40 global_websocket_interceptors: Vec<Arc<dyn WebSocketInterceptor>>,
41 global_websocket_pipes: Vec<Arc<dyn WebSocketPipe>>,
42 global_websocket_filters: Vec<Arc<dyn WebSocketExceptionFilter>>,
43 global_transport_guards: Vec<Arc<dyn TransportGuard>>,
44 global_transport_interceptors: Vec<Arc<dyn TransportInterceptor>>,
45 global_transport_pipes: Vec<Arc<dyn TransportPipe>>,
46 global_transport_filters: Vec<Arc<dyn TransportExceptionFilter>>,
47 global_prefix: Option<String>,
48 global_prefix_exclusions: Vec<MiddlewareRoute>,
49 api_versioning: Option<ApiVersioning>,
50 openapi_routes: Vec<(String, OpenApiInfo)>,
51 openapi_ui_routes: Vec<OpenApiUiRoute>,
52 provider_overrides: BTreeMap<ProviderToken, ProviderDefinition>,
53 module_overrides: BTreeMap<String, Arc<dyn Module>>,
54 #[cfg(feature = "security")]
55 cors_preflight: Option<CorsPreflightRoute>,
56}
57
58impl BootApplicationBuilder {
59 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn import<M>(mut self, module: M) -> Self
65 where
66 M: Module,
67 {
68 self.modules.push(Arc::new(module));
69 self
70 }
71
72 pub fn import_arc(mut self, module: Arc<dyn Module>) -> Self {
74 self.modules.push(module);
75 self
76 }
77
78 pub fn route(mut self, route: RouteDefinition) -> Self {
80 self.routes.push(route);
81 self
82 }
83
84 pub fn gateway(mut self, gateway: WebSocketGatewayDefinition) -> Self {
86 self.gateways.push(gateway);
87 self
88 }
89
90 pub fn message_pattern(mut self, pattern: MessagePatternDefinition) -> Self {
92 self.message_patterns.push(pattern);
93 self
94 }
95
96 pub fn serve_openapi(mut self, path: impl Into<String>, info: OpenApiInfo) -> Self {
98 self.openapi_routes.push((path.into(), info));
99 self
100 }
101
102 pub fn serve_openapi_ui(
104 mut self,
105 path: impl Into<String>,
106 document_path: impl Into<String>,
107 info: OpenApiInfo,
108 ) -> Self {
109 self.openapi_ui_routes.push(OpenApiUiRoute {
110 path: path.into(),
111 document_path: document_path.into(),
112 info,
113 });
114 self
115 }
116
117 pub fn global_prefix(mut self, prefix: impl Into<String>) -> Self {
119 self.global_prefix = Some(prefix.into());
120 self
121 }
122
123 pub fn exclude_global_prefix<I>(mut self, routes: I) -> Self
125 where
126 I: IntoIterator<Item = MiddlewareRoute>,
127 {
128 self.global_prefix_exclusions.extend(routes);
129 self
130 }
131
132 pub fn enable_api_versioning(mut self, versioning: ApiVersioning) -> Self {
134 self.api_versioning = Some(versioning);
135 self
136 }
137
138 pub fn use_global_middleware<M>(mut self, middleware: M) -> Self
140 where
141 M: Middleware,
142 {
143 self.global_pipeline.push_middleware(middleware);
144 self
145 }
146
147 pub fn use_global_pipe<P>(mut self, pipe: P) -> Self
149 where
150 P: Pipe,
151 {
152 self.global_pipeline.push_pipe(pipe);
153 self
154 }
155
156 pub fn use_global_websocket_pipe<P>(mut self, pipe: P) -> Self
158 where
159 P: WebSocketPipe,
160 {
161 self.global_websocket_pipes.push(Arc::new(pipe));
162 self
163 }
164
165 pub fn use_global_websocket_guard<G>(mut self, guard: G) -> Self
167 where
168 G: WebSocketGuard,
169 {
170 self.global_websocket_guards.push(Arc::new(guard));
171 self
172 }
173
174 pub fn use_global_websocket_interceptor<I>(mut self, interceptor: I) -> Self
176 where
177 I: WebSocketInterceptor,
178 {
179 self.global_websocket_interceptors
180 .push(Arc::new(interceptor));
181 self
182 }
183
184 pub fn use_global_transport_pipe<P>(mut self, pipe: P) -> Self
186 where
187 P: TransportPipe,
188 {
189 self.global_transport_pipes.push(Arc::new(pipe));
190 self
191 }
192
193 pub fn use_global_transport_guard<G>(mut self, guard: G) -> Self
195 where
196 G: TransportGuard,
197 {
198 self.global_transport_guards.push(Arc::new(guard));
199 self
200 }
201
202 pub fn use_global_transport_interceptor<I>(mut self, interceptor: I) -> Self
204 where
205 I: TransportInterceptor,
206 {
207 self.global_transport_interceptors
208 .push(Arc::new(interceptor));
209 self
210 }
211
212 pub fn use_global_guard<G>(mut self, guard: G) -> Self
214 where
215 G: Guard,
216 {
217 self.global_pipeline.push_guard(guard);
218 self
219 }
220
221 pub fn use_global_execution_guard<G>(mut self, guard: G) -> Self
223 where
224 G: Guard,
225 {
226 let guard: Arc<dyn Guard> = Arc::new(guard);
227 self.global_pipeline.push_guard_arc(Arc::clone(&guard));
228 self.global_execution_guards.push(guard);
229 self
230 }
231
232 #[cfg(feature = "auth")]
234 pub fn use_global_auth(mut self) -> Self {
235 self.global_pipeline.push_guard(AuthGuard::new());
236 self
237 }
238
239 #[cfg(feature = "auth")]
241 pub fn use_global_auth_strategy(mut self, strategy: impl Into<String>) -> Self {
242 self.global_pipeline
243 .push_guard(AuthGuard::new().strategy(strategy));
244 self
245 }
246
247 pub fn use_global_interceptor<I>(mut self, interceptor: I) -> Self
249 where
250 I: Interceptor,
251 {
252 self.global_pipeline.push_interceptor(interceptor);
253 self
254 }
255
256 pub fn use_global_execution_interceptor<I>(mut self, interceptor: I) -> Self
258 where
259 I: ExecutionInterceptor,
260 {
261 let interceptor: Arc<dyn ExecutionInterceptor> = Arc::new(interceptor);
262 self.global_pipeline
263 .push_execution_interceptor_arc(Arc::clone(&interceptor));
264 self.global_execution_interceptors.push(interceptor);
265 self
266 }
267
268 pub fn use_global_serialization(mut self) -> Self {
270 self.global_pipeline
271 .push_interceptor(SerializationInterceptor::new());
272 self
273 }
274
275 #[cfg(feature = "compression")]
277 pub fn use_global_compression(mut self, options: CompressionOptions) -> Self {
278 self.global_pipeline
279 .push_interceptor(CompressionInterceptor::with_options(options));
280 self
281 }
282
283 #[cfg(feature = "security")]
285 pub fn use_global_cors(mut self, options: CorsOptions) -> Self {
286 self.global_pipeline
287 .push_middleware(CorsMiddleware::with_options(options.clone()));
288 self.global_pipeline
289 .push_interceptor(CorsResponseInterceptor::with_options(options.clone()));
290 self.cors_preflight = Some(CorsPreflightRoute::with_options(options));
291 self
292 }
293
294 #[cfg(feature = "security")]
296 pub fn use_global_security_headers(mut self, options: SecurityHeadersOptions) -> Self {
297 self.global_pipeline
298 .push_interceptor(SecurityHeadersInterceptor::with_options(options));
299 self
300 }
301
302 #[cfg(feature = "security")]
304 pub fn use_global_csrf(mut self, options: CsrfOptions) -> Self {
305 self.global_pipeline
306 .push_guard(CsrfGuard::with_options(options));
307 self
308 }
309
310 #[cfg(feature = "security")]
312 pub fn use_global_rate_limit(mut self, options: RateLimitOptions) -> Self {
313 self.global_pipeline
314 .push_guard(RateLimitGuard::with_options(options));
315 self
316 }
317
318 #[cfg(feature = "session")]
320 pub fn use_global_sessions(mut self, manager: SessionManager) -> Self {
321 self.global_pipeline
322 .push_middleware(SessionMiddleware::new(manager.clone()));
323 self.global_pipeline
324 .push_interceptor(SessionCookieInterceptor::new(manager));
325 self
326 }
327
328 #[cfg(feature = "session")]
330 pub fn use_global_session_module(mut self, module: SessionModule) -> Self {
331 let manager = module.manager();
332 self = self.use_global_sessions(manager);
333 self.modules.push(Arc::new(module.global()));
334 self
335 }
336
337 pub fn use_global_filter<F>(mut self, filter: F) -> Self
339 where
340 F: ExceptionFilter,
341 {
342 self.global_pipeline.push_filter(filter);
343 self
344 }
345
346 pub fn use_global_catch_filter<I, F>(mut self, kinds: I, filter: F) -> Self
348 where
349 I: IntoIterator<Item = BootErrorKind>,
350 F: ExceptionFilter,
351 {
352 self.global_pipeline.push_catch_filter(kinds, filter);
353 self
354 }
355
356 pub fn use_global_websocket_filter<F>(mut self, filter: F) -> Self
358 where
359 F: WebSocketExceptionFilter,
360 {
361 self.global_websocket_filters.push(Arc::new(filter));
362 self
363 }
364
365 pub fn use_global_websocket_catch_filter<I, F>(mut self, kinds: I, filter: F) -> Self
367 where
368 I: IntoIterator<Item = BootErrorKind>,
369 F: WebSocketExceptionFilter,
370 {
371 self.global_websocket_filters
372 .push(Arc::new(catch_errors(kinds, filter)));
373 self
374 }
375
376 pub fn use_global_transport_filter<F>(mut self, filter: F) -> Self
378 where
379 F: TransportExceptionFilter,
380 {
381 self.global_transport_filters.push(Arc::new(filter));
382 self
383 }
384
385 pub fn use_global_transport_catch_filter<I, F>(mut self, kinds: I, filter: F) -> Self
387 where
388 I: IntoIterator<Item = BootErrorKind>,
389 F: TransportExceptionFilter,
390 {
391 self.global_transport_filters
392 .push(Arc::new(catch_errors(kinds, filter)));
393 self
394 }
395
396 pub fn use_global_validation(mut self) -> Self {
398 self.global_pipeline.enable_validation();
399 self
400 }
401
402 pub fn use_global_validation_options(mut self, options: ValidationOptions) -> Self {
404 self.global_pipeline.enable_validation_with_options(options);
405 self
406 }
407
408 pub fn override_provider(mut self, provider: ProviderDefinition) -> Self {
413 self.provider_overrides
414 .insert(provider.token().clone(), provider);
415 self
416 }
417
418 pub fn override_module<M>(mut self, target_name: impl Into<String>, module: M) -> Self
425 where
426 M: Module,
427 {
428 self.module_overrides
429 .insert(target_name.into(), Arc::new(module));
430 self
431 }
432
433 pub fn override_module_arc(
435 mut self,
436 target_name: impl Into<String>,
437 module: Arc<dyn Module>,
438 ) -> Self {
439 self.module_overrides.insert(target_name.into(), module);
440 self
441 }
442
443 pub fn build(self) -> Result<BootApplication> {
445 let module_ref = ModuleRef::new();
446 let global_ref = ModuleRef::new();
447 let lazy_module_loader = LazyModuleLoader::new(global_ref.clone());
448 global_ref.insert_arc(Arc::new(lazy_module_loader.clone()))?;
449 module_ref.add_visible_scope(global_ref.clone())?;
450 let mut registry =
451 ModuleRegistry::new(global_ref, self.provider_overrides, self.module_overrides);
452 let mut modules = Vec::new();
453 let mut module_instances = Vec::new();
454 let mut routes = self
455 .routes
456 .into_iter()
457 .map(|route| route.with_pipeline_prefix(&self.global_pipeline))
458 .collect::<Vec<_>>();
459 let mut gateways = self.gateways;
460 let mut message_patterns = self.message_patterns;
461
462 {
463 let mut sink = ModuleRegistrationSink {
464 modules: &mut modules,
465 module_instances: &mut module_instances,
466 routes: &mut routes,
467 gateways: &mut gateways,
468 message_patterns: &mut message_patterns,
469 };
470
471 for module in &self.modules {
472 let registered = registry.register_module(
473 Arc::clone(module),
474 &self.global_pipeline,
475 &mut sink,
476 )?;
477 module_ref.add_visible_scope(registered.module_ref)?;
478 }
479 }
480 for (name, registered) in registry.registered_modules() {
481 lazy_module_loader.seed_module(name, registered.module_ref, registered.exports)?;
482 }
483
484 let mut routes = apply_global_prefix(
485 routes,
486 self.global_prefix.as_deref(),
487 &self.global_prefix_exclusions,
488 )?;
489 let gateways = apply_global_gateway_prefix(gateways, self.global_prefix.as_deref())?
490 .into_iter()
491 .map(|gateway| {
492 gateway
493 .with_guard_prefix(&self.global_websocket_guards)
494 .with_interceptor_prefix(&self.global_websocket_interceptors)
495 .with_execution_pipeline_prefix(
496 &self.global_execution_guards,
497 &self.global_execution_interceptors,
498 )
499 .with_pipe_prefix(&self.global_websocket_pipes)
500 .with_filter_prefix(&self.global_websocket_filters)
501 .with_validation_prefix(
502 self.global_pipeline.validation_enabled,
503 self.global_pipeline.validation_options,
504 )
505 })
506 .collect::<Vec<_>>();
507 let message_patterns = message_patterns
508 .into_iter()
509 .map(|pattern| {
510 pattern
511 .with_guard_prefix(&self.global_transport_guards)
512 .with_interceptor_prefix(&self.global_transport_interceptors)
513 .with_execution_pipeline_prefix(
514 &self.global_execution_guards,
515 &self.global_execution_interceptors,
516 )
517 .with_pipe_prefix(&self.global_transport_pipes)
518 .with_filter_prefix(&self.global_transport_filters)
519 .with_validation_prefix(
520 self.global_pipeline.validation_enabled,
521 self.global_pipeline.validation_options,
522 )
523 })
524 .collect::<Vec<_>>();
525 let documented_routes = routes.clone();
526
527 for (path, info) in self.openapi_routes {
528 let document = OpenApiDocument::from_routes(info, &documented_routes);
529 let route =
530 openapi_json_route(path, document)?.with_pipeline_prefix(&self.global_pipeline);
531 let route = apply_global_prefix_to_route(
532 route,
533 self.global_prefix.as_deref(),
534 &self.global_prefix_exclusions,
535 )?;
536 routes.push(route);
537 }
538 for ui in self.openapi_ui_routes {
539 add_openapi_ui_routes(
540 &mut routes,
541 ui,
542 &documented_routes,
543 self.global_prefix.as_deref(),
544 &self.global_prefix_exclusions,
545 &self.global_pipeline,
546 )?;
547 }
548
549 #[cfg(feature = "security")]
550 if let Some(cors_preflight) = &self.cors_preflight {
551 add_cors_preflight_routes(&mut routes, cors_preflight, &self.global_pipeline)?;
552 }
553
554 routes = routes
555 .into_iter()
556 .map(|route| route.with_default_module_ref(module_ref.clone()))
557 .collect();
558
559 validate_unique_routes(&routes, self.api_versioning.as_ref())?;
560 validate_unique_gateways(&gateways)?;
561 validate_unique_message_patterns(&message_patterns)?;
562 validate_gateway_route_conflicts(&routes, &gateways)?;
563
564 Ok(BootApplication {
565 routes,
566 gateways,
567 message_patterns,
568 modules,
569 module_ref,
570 module_instances,
571 api_versioning: self.api_versioning,
572 })
573 }
574
575 pub async fn build_async(self) -> Result<BootApplication> {
577 let module_ref = ModuleRef::new();
578 let global_ref = ModuleRef::new();
579 let lazy_module_loader = LazyModuleLoader::new(global_ref.clone());
580 global_ref.insert_arc(Arc::new(lazy_module_loader.clone()))?;
581 module_ref.add_visible_scope(global_ref.clone())?;
582 let mut registry =
583 ModuleRegistry::new(global_ref, self.provider_overrides, self.module_overrides);
584 let mut modules = Vec::new();
585 let mut module_instances = Vec::new();
586 let mut routes = self
587 .routes
588 .into_iter()
589 .map(|route| route.with_pipeline_prefix(&self.global_pipeline))
590 .collect::<Vec<_>>();
591 let mut gateways = self.gateways;
592 let mut message_patterns = self.message_patterns;
593
594 {
595 let mut sink = ModuleRegistrationSink {
596 modules: &mut modules,
597 module_instances: &mut module_instances,
598 routes: &mut routes,
599 gateways: &mut gateways,
600 message_patterns: &mut message_patterns,
601 };
602
603 for module in &self.modules {
604 let registered = registry
605 .register_module_async(Arc::clone(module), &self.global_pipeline, &mut sink)
606 .await?;
607 module_ref.add_visible_scope(registered.module_ref)?;
608 }
609 }
610 for (name, registered) in registry.registered_modules() {
611 lazy_module_loader.seed_module(name, registered.module_ref, registered.exports)?;
612 }
613
614 let mut routes = apply_global_prefix(
615 routes,
616 self.global_prefix.as_deref(),
617 &self.global_prefix_exclusions,
618 )?;
619 let gateways = apply_global_gateway_prefix(gateways, self.global_prefix.as_deref())?
620 .into_iter()
621 .map(|gateway| {
622 gateway
623 .with_guard_prefix(&self.global_websocket_guards)
624 .with_interceptor_prefix(&self.global_websocket_interceptors)
625 .with_execution_pipeline_prefix(
626 &self.global_execution_guards,
627 &self.global_execution_interceptors,
628 )
629 .with_pipe_prefix(&self.global_websocket_pipes)
630 .with_filter_prefix(&self.global_websocket_filters)
631 .with_validation_prefix(
632 self.global_pipeline.validation_enabled,
633 self.global_pipeline.validation_options,
634 )
635 })
636 .collect::<Vec<_>>();
637 let message_patterns = message_patterns
638 .into_iter()
639 .map(|pattern| {
640 pattern
641 .with_guard_prefix(&self.global_transport_guards)
642 .with_interceptor_prefix(&self.global_transport_interceptors)
643 .with_execution_pipeline_prefix(
644 &self.global_execution_guards,
645 &self.global_execution_interceptors,
646 )
647 .with_pipe_prefix(&self.global_transport_pipes)
648 .with_filter_prefix(&self.global_transport_filters)
649 .with_validation_prefix(
650 self.global_pipeline.validation_enabled,
651 self.global_pipeline.validation_options,
652 )
653 })
654 .collect::<Vec<_>>();
655 let documented_routes = routes.clone();
656
657 for (path, info) in self.openapi_routes {
658 let document = OpenApiDocument::from_routes(info, &documented_routes);
659 let route =
660 openapi_json_route(path, document)?.with_pipeline_prefix(&self.global_pipeline);
661 let route = apply_global_prefix_to_route(
662 route,
663 self.global_prefix.as_deref(),
664 &self.global_prefix_exclusions,
665 )?;
666 routes.push(route);
667 }
668 for ui in self.openapi_ui_routes {
669 add_openapi_ui_routes(
670 &mut routes,
671 ui,
672 &documented_routes,
673 self.global_prefix.as_deref(),
674 &self.global_prefix_exclusions,
675 &self.global_pipeline,
676 )?;
677 }
678
679 #[cfg(feature = "security")]
680 if let Some(cors_preflight) = &self.cors_preflight {
681 add_cors_preflight_routes(&mut routes, cors_preflight, &self.global_pipeline)?;
682 }
683
684 routes = routes
685 .into_iter()
686 .map(|route| route.with_default_module_ref(module_ref.clone()))
687 .collect();
688
689 validate_unique_routes(&routes, self.api_versioning.as_ref())?;
690 validate_unique_gateways(&gateways)?;
691 validate_unique_message_patterns(&message_patterns)?;
692 validate_gateway_route_conflicts(&routes, &gateways)?;
693
694 Ok(BootApplication {
695 routes,
696 gateways,
697 message_patterns,
698 modules,
699 module_ref,
700 module_instances,
701 api_versioning: self.api_versioning,
702 })
703 }
704}
705
706#[derive(Clone)]
707struct OpenApiUiRoute {
708 path: String,
709 document_path: String,
710 info: OpenApiInfo,
711}
712
713fn apply_global_gateway_prefix(
714 gateways: Vec<WebSocketGatewayDefinition>,
715 prefix: Option<&str>,
716) -> Result<Vec<WebSocketGatewayDefinition>> {
717 match prefix {
718 Some(prefix) => gateways
719 .into_iter()
720 .map(|gateway| gateway.with_path_prefix(prefix))
721 .collect(),
722 None => Ok(gateways),
723 }
724}
725
726fn openapi_json_route(path: String, document: OpenApiDocument) -> Result<RouteDefinition> {
727 RouteDefinition::get(path, move |_| {
728 let document = document.clone();
729 async move { BootResponse::json(&document) }
730 })
731 .map(RouteDefinition::hide_from_openapi)
732}
733
734fn add_openapi_ui_routes(
735 routes: &mut Vec<RouteDefinition>,
736 ui: OpenApiUiRoute,
737 documented_routes: &[RouteDefinition],
738 global_prefix: Option<&str>,
739 global_prefix_exclusions: &[MiddlewareRoute],
740 pipeline: &PipelineComponents,
741) -> Result<()> {
742 let document = OpenApiDocument::from_routes(ui.info.clone(), documented_routes);
743 let document_path = ui.document_path;
744 let ui_path = ui.path;
745 let json_route = openapi_json_route(document_path, document)?.with_pipeline_prefix(pipeline);
746 let json_route =
747 apply_global_prefix_to_route(json_route, global_prefix, global_prefix_exclusions)?;
748 let public_document_path = json_route.path().to_string();
749 let ui_route =
750 openapi_ui_route(ui_path, public_document_path, ui.info)?.with_pipeline_prefix(pipeline);
751 let ui_route = apply_global_prefix_to_route(ui_route, global_prefix, global_prefix_exclusions)?;
752 routes.push(json_route);
753 routes.push(ui_route);
754 Ok(())
755}
756
757fn openapi_ui_route(
758 path: String,
759 document_path: String,
760 info: OpenApiInfo,
761) -> Result<RouteDefinition> {
762 RouteDefinition::get(path, move |_| {
763 let document_path = document_path.clone();
764 let title = info.title.clone();
765 async move { Ok(openapi_ui_response(&title, &document_path)) }
766 })
767 .map(RouteDefinition::hide_from_openapi)
768}
769
770fn openapi_ui_response(title: &str, document_path: &str) -> BootResponse {
771 BootResponse::text_with_status(200, openapi_ui_html(title, document_path))
772 .with_header("content-type", "text/html; charset=utf-8")
773}
774
775fn openapi_ui_html(title: &str, document_path: &str) -> String {
776 format!(
777 r##"<!doctype html>
778<html lang="en">
779<head>
780 <meta charset="utf-8">
781 <title>{title}</title>
782 <meta name="viewport" content="width=device-width, initial-scale=1">
783 <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
784</head>
785<body>
786 <div id="swagger-ui"></div>
787 <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
788 <script>
789 window.ui = SwaggerUIBundle({{
790 url: {document_path:?},
791 dom_id: "#swagger-ui",
792 deepLinking: true,
793 presets: [SwaggerUIBundle.presets.apis],
794 layout: "BaseLayout"
795 }});
796 </script>
797</body>
798</html>"##,
799 title = escape_html(title),
800 document_path = document_path
801 )
802}
803
804fn escape_html(value: &str) -> String {
805 value
806 .replace('&', "&")
807 .replace('<', "<")
808 .replace('>', ">")
809 .replace('"', """)
810 .replace('\'', "'")
811}
812
813#[cfg(feature = "security")]
814fn add_cors_preflight_routes(
815 routes: &mut Vec<RouteDefinition>,
816 cors_preflight: &CorsPreflightRoute,
817 pipeline: &PipelineComponents,
818) -> Result<()> {
819 let existing_options = routes
820 .iter()
821 .filter(|route| route.method() == crate::HttpMethod::Options)
822 .map(RouteDefinition::path_shape)
823 .collect::<BTreeSet<_>>();
824 let mut generated = BTreeSet::new();
825 let source_routes = routes.clone();
826
827 for route in source_routes {
828 if route.method() == crate::HttpMethod::Options {
829 continue;
830 }
831
832 let path_shape = route.path_shape();
833 if existing_options.contains(&path_shape) || !generated.insert(path_shape) {
834 continue;
835 }
836
837 let preflight = RouteDefinition::options(route.path().to_string(), cors_preflight.clone())?
838 .hide_from_openapi()
839 .with_pipeline_prefix(pipeline);
840 routes.push(preflight);
841 }
842
843 Ok(())
844}
845
846fn apply_global_prefix(
847 routes: Vec<RouteDefinition>,
848 prefix: Option<&str>,
849 exclusions: &[MiddlewareRoute],
850) -> Result<Vec<RouteDefinition>> {
851 routes
852 .into_iter()
853 .map(|route| apply_global_prefix_to_route(route, prefix, exclusions))
854 .collect()
855}
856
857fn apply_global_prefix_to_route(
858 route: RouteDefinition,
859 prefix: Option<&str>,
860 exclusions: &[MiddlewareRoute],
861) -> Result<RouteDefinition> {
862 let Some(prefix) = prefix else {
863 return Ok(route);
864 };
865 if global_prefix_excludes(&route, exclusions) {
866 return Ok(route);
867 }
868 route.with_path_prefix(prefix)
869}
870
871fn global_prefix_excludes(route: &RouteDefinition, exclusions: &[MiddlewareRoute]) -> bool {
872 exclusions
873 .iter()
874 .any(|exclusion| exclusion.matches(route.method(), &[route.path().to_string()]))
875}
876
877fn validate_unique_routes(
878 routes: &[RouteDefinition],
879 versioning: Option<&ApiVersioning>,
880) -> Result<()> {
881 for (index, route) in routes.iter().enumerate() {
882 for existing in routes.iter().take(index) {
883 if existing.method() != route.method()
884 || existing.path_shape_key() != route.path_shape_key()
885 || existing.host_shape_key() != route.host_shape_key()
886 {
887 continue;
888 }
889
890 let duplicate = match versioning {
891 Some(versioning) => existing
892 .versioning()
893 .overlaps(route.versioning(), versioning.default_version()),
894 None => true,
895 };
896 if !duplicate {
897 continue;
898 }
899
900 let host = route
901 .host()
902 .map(|host| format!(" host {host}"))
903 .unwrap_or_default();
904 return Err(BootError::DuplicateRoute(format!(
905 "{} {}{} version {}",
906 route.method().as_str(),
907 route.path(),
908 host,
909 route.versioning()
910 )));
911 }
912 }
913
914 Ok(())
915}
916
917fn validate_unique_gateways(gateways: &[WebSocketGatewayDefinition]) -> Result<()> {
918 let mut seen = BTreeSet::new();
919
920 for gateway in gateways {
921 if !seen.insert(gateway.path_shape()) {
922 return Err(BootError::DuplicateRoute(format!("WS {}", gateway.path())));
923 }
924 }
925
926 Ok(())
927}
928
929fn validate_gateway_route_conflicts(
930 routes: &[RouteDefinition],
931 gateways: &[WebSocketGatewayDefinition],
932) -> Result<()> {
933 let get_routes = routes
934 .iter()
935 .filter(|route| route.method().matches(crate::HttpMethod::Get))
936 .map(RouteDefinition::path_shape)
937 .collect::<BTreeSet<_>>();
938
939 for gateway in gateways {
940 if get_routes.contains(&gateway.path_shape()) {
941 return Err(BootError::DuplicateRoute(format!("GET {}", gateway.path())));
942 }
943 }
944
945 Ok(())
946}
947
948fn validate_unique_message_patterns(patterns: &[MessagePatternDefinition]) -> Result<()> {
949 let mut seen = BTreeSet::new();
950
951 for pattern in patterns {
952 if !seen.insert(pattern.pattern()) {
953 return Err(BootError::DuplicateRoute(format!(
954 "message pattern {}",
955 pattern.pattern()
956 )));
957 }
958 }
959
960 Ok(())
961}