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, RateLimitProvider, 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 = "security")]
320 pub fn use_global_rate_limit_provider<P>(
321 mut self,
322 options: RateLimitOptions,
323 provider: P,
324 ) -> Self
325 where
326 P: RateLimitProvider,
327 {
328 self.global_pipeline
329 .push_guard(RateLimitGuard::with_provider(options, provider));
330 self
331 }
332
333 #[cfg(feature = "session")]
335 pub fn use_global_sessions(mut self, manager: SessionManager) -> Self {
336 self.global_pipeline
337 .push_middleware(SessionMiddleware::new(manager.clone()));
338 self.global_pipeline
339 .push_interceptor(SessionCookieInterceptor::new(manager));
340 self
341 }
342
343 #[cfg(feature = "session")]
345 pub fn use_global_session_module(mut self, module: SessionModule) -> Self {
346 let manager = module.manager();
347 self = self.use_global_sessions(manager);
348 self.modules.push(Arc::new(module.global()));
349 self
350 }
351
352 pub fn use_global_filter<F>(mut self, filter: F) -> Self
354 where
355 F: ExceptionFilter,
356 {
357 self.global_pipeline.push_filter(filter);
358 self
359 }
360
361 pub fn use_global_catch_filter<I, F>(mut self, kinds: I, filter: F) -> Self
363 where
364 I: IntoIterator<Item = BootErrorKind>,
365 F: ExceptionFilter,
366 {
367 self.global_pipeline.push_catch_filter(kinds, filter);
368 self
369 }
370
371 pub fn use_global_websocket_filter<F>(mut self, filter: F) -> Self
373 where
374 F: WebSocketExceptionFilter,
375 {
376 self.global_websocket_filters.push(Arc::new(filter));
377 self
378 }
379
380 pub fn use_global_websocket_catch_filter<I, F>(mut self, kinds: I, filter: F) -> Self
382 where
383 I: IntoIterator<Item = BootErrorKind>,
384 F: WebSocketExceptionFilter,
385 {
386 self.global_websocket_filters
387 .push(Arc::new(catch_errors(kinds, filter)));
388 self
389 }
390
391 pub fn use_global_transport_filter<F>(mut self, filter: F) -> Self
393 where
394 F: TransportExceptionFilter,
395 {
396 self.global_transport_filters.push(Arc::new(filter));
397 self
398 }
399
400 pub fn use_global_transport_catch_filter<I, F>(mut self, kinds: I, filter: F) -> Self
402 where
403 I: IntoIterator<Item = BootErrorKind>,
404 F: TransportExceptionFilter,
405 {
406 self.global_transport_filters
407 .push(Arc::new(catch_errors(kinds, filter)));
408 self
409 }
410
411 pub fn use_global_validation(mut self) -> Self {
413 self.global_pipeline.enable_validation();
414 self
415 }
416
417 pub fn use_global_validation_options(mut self, options: ValidationOptions) -> Self {
419 self.global_pipeline.enable_validation_with_options(options);
420 self
421 }
422
423 pub fn override_provider(mut self, provider: ProviderDefinition) -> Self {
428 self.provider_overrides
429 .insert(provider.token().clone(), provider);
430 self
431 }
432
433 pub fn override_module<M>(mut self, target_name: impl Into<String>, module: M) -> Self
440 where
441 M: Module,
442 {
443 self.module_overrides
444 .insert(target_name.into(), Arc::new(module));
445 self
446 }
447
448 pub fn override_module_arc(
450 mut self,
451 target_name: impl Into<String>,
452 module: Arc<dyn Module>,
453 ) -> Self {
454 self.module_overrides.insert(target_name.into(), module);
455 self
456 }
457
458 pub fn build(self) -> Result<BootApplication> {
460 let module_ref = ModuleRef::new();
461 let global_ref = ModuleRef::new();
462 let lazy_module_loader = LazyModuleLoader::new(global_ref.clone());
463 global_ref.insert_arc(Arc::new(lazy_module_loader.clone()))?;
464 module_ref.add_visible_scope(global_ref.clone())?;
465 let mut registry =
466 ModuleRegistry::new(global_ref, self.provider_overrides, self.module_overrides);
467 let mut modules = Vec::new();
468 let mut module_instances = Vec::new();
469 let mut routes = self.routes;
470 let mut gateways = self.gateways;
471 let mut message_patterns = self.message_patterns;
472
473 for module in &self.modules {
474 let registered = registry.register_module(Arc::clone(module))?;
475 module_ref.add_visible_scope(registered.module_ref)?;
476 }
477 let provider_enhancers = registry.provider_enhancers();
478 let mut effective_global_pipeline = self.global_pipeline.clone();
479 effective_global_pipeline.append(&provider_enhancers.http);
480 routes = routes
481 .into_iter()
482 .map(|route| route.with_pipeline_prefix(&effective_global_pipeline))
483 .collect();
484 {
485 let mut sink = ModuleRegistrationSink {
486 modules: &mut modules,
487 module_instances: &mut module_instances,
488 routes: &mut routes,
489 gateways: &mut gateways,
490 message_patterns: &mut message_patterns,
491 };
492 registry.finalize(&effective_global_pipeline, &mut sink)?;
493 }
494 for (name, registered) in registry.registered_modules() {
495 lazy_module_loader.seed_module(name, registered.module_ref, registered.exports)?;
496 }
497
498 let mut routes = apply_global_prefix(
499 routes,
500 self.global_prefix.as_deref(),
501 &self.global_prefix_exclusions,
502 )?;
503 let gateways = apply_global_gateway_prefix(gateways, self.global_prefix.as_deref())?
504 .into_iter()
505 .map(|gateway| {
506 gateway
507 .with_provider_enhancer_prefix(&provider_enhancers)
508 .with_guard_prefix(&self.global_websocket_guards)
509 .with_interceptor_prefix(&self.global_websocket_interceptors)
510 .with_execution_pipeline_prefix(
511 &self.global_execution_guards,
512 &self.global_execution_interceptors,
513 )
514 .with_pipe_prefix(&self.global_websocket_pipes)
515 .with_filter_prefix(&self.global_websocket_filters)
516 .with_validation_prefix(
517 self.global_pipeline.validation_enabled,
518 self.global_pipeline.validation_options,
519 )
520 .with_default_module_ref(module_ref.clone())
521 })
522 .collect::<Vec<_>>();
523 let message_patterns = message_patterns
524 .into_iter()
525 .map(|pattern| {
526 pattern
527 .with_provider_enhancer_prefix(&provider_enhancers)
528 .with_guard_prefix(&self.global_transport_guards)
529 .with_interceptor_prefix(&self.global_transport_interceptors)
530 .with_execution_pipeline_prefix(
531 &self.global_execution_guards,
532 &self.global_execution_interceptors,
533 )
534 .with_pipe_prefix(&self.global_transport_pipes)
535 .with_filter_prefix(&self.global_transport_filters)
536 .with_validation_prefix(
537 self.global_pipeline.validation_enabled,
538 self.global_pipeline.validation_options,
539 )
540 .with_default_module_ref(module_ref.clone())
541 })
542 .collect::<Vec<_>>();
543 let documented_routes = routes.clone();
544
545 for (path, info) in self.openapi_routes {
546 let document = OpenApiDocument::from_routes(info, &documented_routes);
547 let route = openapi_json_route(path, document)?
548 .with_pipeline_prefix(&effective_global_pipeline);
549 let route = apply_global_prefix_to_route(
550 route,
551 self.global_prefix.as_deref(),
552 &self.global_prefix_exclusions,
553 )?;
554 routes.push(route);
555 }
556 for ui in self.openapi_ui_routes {
557 add_openapi_ui_routes(
558 &mut routes,
559 ui,
560 &documented_routes,
561 self.global_prefix.as_deref(),
562 &self.global_prefix_exclusions,
563 &effective_global_pipeline,
564 )?;
565 }
566
567 #[cfg(feature = "security")]
568 if let Some(cors_preflight) = &self.cors_preflight {
569 add_cors_preflight_routes(&mut routes, cors_preflight, &effective_global_pipeline)?;
570 }
571
572 routes = routes
573 .into_iter()
574 .map(|route| route.with_default_module_ref(module_ref.clone()))
575 .collect();
576
577 validate_unique_routes(&routes, self.api_versioning.as_ref())?;
578 validate_unique_gateways(&gateways)?;
579 validate_unique_message_patterns(&message_patterns)?;
580 validate_gateway_route_conflicts(&routes, &gateways)?;
581
582 Ok(BootApplication {
583 routes,
584 gateways,
585 message_patterns,
586 modules,
587 module_ref,
588 module_instances,
589 api_versioning: self.api_versioning,
590 global_filters: effective_global_pipeline.filters.clone(),
591 })
592 }
593
594 pub async fn build_async(self) -> Result<BootApplication> {
596 let module_ref = ModuleRef::new();
597 let global_ref = ModuleRef::new();
598 let lazy_module_loader = LazyModuleLoader::new(global_ref.clone());
599 global_ref.insert_arc(Arc::new(lazy_module_loader.clone()))?;
600 module_ref.add_visible_scope(global_ref.clone())?;
601 let mut registry =
602 ModuleRegistry::new(global_ref, self.provider_overrides, self.module_overrides);
603 let mut modules = Vec::new();
604 let mut module_instances = Vec::new();
605 let mut routes = self.routes;
606 let mut gateways = self.gateways;
607 let mut message_patterns = self.message_patterns;
608
609 for module in &self.modules {
610 let registered = registry.register_module_async(Arc::clone(module)).await?;
611 module_ref.add_visible_scope(registered.module_ref)?;
612 }
613 let provider_enhancers = registry.provider_enhancers();
614 let mut effective_global_pipeline = self.global_pipeline.clone();
615 effective_global_pipeline.append(&provider_enhancers.http);
616 routes = routes
617 .into_iter()
618 .map(|route| route.with_pipeline_prefix(&effective_global_pipeline))
619 .collect();
620 {
621 let mut sink = ModuleRegistrationSink {
622 modules: &mut modules,
623 module_instances: &mut module_instances,
624 routes: &mut routes,
625 gateways: &mut gateways,
626 message_patterns: &mut message_patterns,
627 };
628 registry
629 .finalize_async(&effective_global_pipeline, &mut sink)
630 .await?;
631 }
632 for (name, registered) in registry.registered_modules() {
633 lazy_module_loader.seed_module(name, registered.module_ref, registered.exports)?;
634 }
635
636 let mut routes = apply_global_prefix(
637 routes,
638 self.global_prefix.as_deref(),
639 &self.global_prefix_exclusions,
640 )?;
641 let gateways = apply_global_gateway_prefix(gateways, self.global_prefix.as_deref())?
642 .into_iter()
643 .map(|gateway| {
644 gateway
645 .with_provider_enhancer_prefix(&provider_enhancers)
646 .with_guard_prefix(&self.global_websocket_guards)
647 .with_interceptor_prefix(&self.global_websocket_interceptors)
648 .with_execution_pipeline_prefix(
649 &self.global_execution_guards,
650 &self.global_execution_interceptors,
651 )
652 .with_pipe_prefix(&self.global_websocket_pipes)
653 .with_filter_prefix(&self.global_websocket_filters)
654 .with_validation_prefix(
655 self.global_pipeline.validation_enabled,
656 self.global_pipeline.validation_options,
657 )
658 .with_default_module_ref(module_ref.clone())
659 })
660 .collect::<Vec<_>>();
661 let message_patterns = message_patterns
662 .into_iter()
663 .map(|pattern| {
664 pattern
665 .with_provider_enhancer_prefix(&provider_enhancers)
666 .with_guard_prefix(&self.global_transport_guards)
667 .with_interceptor_prefix(&self.global_transport_interceptors)
668 .with_execution_pipeline_prefix(
669 &self.global_execution_guards,
670 &self.global_execution_interceptors,
671 )
672 .with_pipe_prefix(&self.global_transport_pipes)
673 .with_filter_prefix(&self.global_transport_filters)
674 .with_validation_prefix(
675 self.global_pipeline.validation_enabled,
676 self.global_pipeline.validation_options,
677 )
678 .with_default_module_ref(module_ref.clone())
679 })
680 .collect::<Vec<_>>();
681 let documented_routes = routes.clone();
682
683 for (path, info) in self.openapi_routes {
684 let document = OpenApiDocument::from_routes(info, &documented_routes);
685 let route = openapi_json_route(path, document)?
686 .with_pipeline_prefix(&effective_global_pipeline);
687 let route = apply_global_prefix_to_route(
688 route,
689 self.global_prefix.as_deref(),
690 &self.global_prefix_exclusions,
691 )?;
692 routes.push(route);
693 }
694 for ui in self.openapi_ui_routes {
695 add_openapi_ui_routes(
696 &mut routes,
697 ui,
698 &documented_routes,
699 self.global_prefix.as_deref(),
700 &self.global_prefix_exclusions,
701 &effective_global_pipeline,
702 )?;
703 }
704
705 #[cfg(feature = "security")]
706 if let Some(cors_preflight) = &self.cors_preflight {
707 add_cors_preflight_routes(&mut routes, cors_preflight, &effective_global_pipeline)?;
708 }
709
710 routes = routes
711 .into_iter()
712 .map(|route| route.with_default_module_ref(module_ref.clone()))
713 .collect();
714
715 validate_unique_routes(&routes, self.api_versioning.as_ref())?;
716 validate_unique_gateways(&gateways)?;
717 validate_unique_message_patterns(&message_patterns)?;
718 validate_gateway_route_conflicts(&routes, &gateways)?;
719
720 Ok(BootApplication {
721 routes,
722 gateways,
723 message_patterns,
724 modules,
725 module_ref,
726 module_instances,
727 api_versioning: self.api_versioning,
728 global_filters: effective_global_pipeline.filters.clone(),
729 })
730 }
731}
732
733#[derive(Clone)]
734struct OpenApiUiRoute {
735 path: String,
736 document_path: String,
737 info: OpenApiInfo,
738}
739
740fn apply_global_gateway_prefix(
741 gateways: Vec<WebSocketGatewayDefinition>,
742 prefix: Option<&str>,
743) -> Result<Vec<WebSocketGatewayDefinition>> {
744 match prefix {
745 Some(prefix) => gateways
746 .into_iter()
747 .map(|gateway| gateway.with_path_prefix(prefix))
748 .collect(),
749 None => Ok(gateways),
750 }
751}
752
753fn openapi_json_route(path: String, document: OpenApiDocument) -> Result<RouteDefinition> {
754 RouteDefinition::get(path, move |_| {
755 let document = document.clone();
756 async move { BootResponse::json(&document) }
757 })
758 .map(RouteDefinition::hide_from_openapi)
759}
760
761fn add_openapi_ui_routes(
762 routes: &mut Vec<RouteDefinition>,
763 ui: OpenApiUiRoute,
764 documented_routes: &[RouteDefinition],
765 global_prefix: Option<&str>,
766 global_prefix_exclusions: &[MiddlewareRoute],
767 pipeline: &PipelineComponents,
768) -> Result<()> {
769 let document = OpenApiDocument::from_routes(ui.info.clone(), documented_routes);
770 let document_path = ui.document_path;
771 let ui_path = ui.path;
772 let json_route = openapi_json_route(document_path, document)?.with_pipeline_prefix(pipeline);
773 let json_route =
774 apply_global_prefix_to_route(json_route, global_prefix, global_prefix_exclusions)?;
775 let public_document_path = json_route.path().to_string();
776 let ui_route =
777 openapi_ui_route(ui_path, public_document_path, ui.info)?.with_pipeline_prefix(pipeline);
778 let ui_route = apply_global_prefix_to_route(ui_route, global_prefix, global_prefix_exclusions)?;
779 routes.push(json_route);
780 routes.push(ui_route);
781 Ok(())
782}
783
784fn openapi_ui_route(
785 path: String,
786 document_path: String,
787 info: OpenApiInfo,
788) -> Result<RouteDefinition> {
789 RouteDefinition::get(path, move |_| {
790 let document_path = document_path.clone();
791 let title = info.title.clone();
792 async move { Ok(openapi_ui_response(&title, &document_path)) }
793 })
794 .map(RouteDefinition::hide_from_openapi)
795}
796
797fn openapi_ui_response(title: &str, document_path: &str) -> BootResponse {
798 BootResponse::text_with_status(200, openapi_ui_html(title, document_path))
799 .with_header("content-type", "text/html; charset=utf-8")
800}
801
802fn openapi_ui_html(title: &str, document_path: &str) -> String {
803 format!(
804 r##"<!doctype html>
805<html lang="en">
806<head>
807 <meta charset="utf-8">
808 <title>{title}</title>
809 <meta name="viewport" content="width=device-width, initial-scale=1">
810 <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
811</head>
812<body>
813 <div id="swagger-ui"></div>
814 <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
815 <script>
816 window.ui = SwaggerUIBundle({{
817 url: {document_path:?},
818 dom_id: "#swagger-ui",
819 deepLinking: true,
820 presets: [SwaggerUIBundle.presets.apis],
821 layout: "BaseLayout"
822 }});
823 </script>
824</body>
825</html>"##,
826 title = escape_html(title),
827 document_path = document_path
828 )
829}
830
831fn escape_html(value: &str) -> String {
832 value
833 .replace('&', "&")
834 .replace('<', "<")
835 .replace('>', ">")
836 .replace('"', """)
837 .replace('\'', "'")
838}
839
840#[cfg(feature = "security")]
841fn add_cors_preflight_routes(
842 routes: &mut Vec<RouteDefinition>,
843 cors_preflight: &CorsPreflightRoute,
844 pipeline: &PipelineComponents,
845) -> Result<()> {
846 let existing_options = routes
847 .iter()
848 .filter(|route| route.method() == crate::HttpMethod::Options)
849 .map(RouteDefinition::path_shape)
850 .collect::<BTreeSet<_>>();
851 let mut generated = BTreeSet::new();
852 let source_routes = routes.clone();
853
854 for route in source_routes {
855 if route.method() == crate::HttpMethod::Options {
856 continue;
857 }
858
859 let path_shape = route.path_shape();
860 if existing_options.contains(&path_shape) || !generated.insert(path_shape) {
861 continue;
862 }
863
864 let preflight = RouteDefinition::options(route.path().to_string(), cors_preflight.clone())?
865 .hide_from_openapi()
866 .with_pipeline_prefix(pipeline);
867 routes.push(preflight);
868 }
869
870 Ok(())
871}
872
873fn apply_global_prefix(
874 routes: Vec<RouteDefinition>,
875 prefix: Option<&str>,
876 exclusions: &[MiddlewareRoute],
877) -> Result<Vec<RouteDefinition>> {
878 routes
879 .into_iter()
880 .map(|route| apply_global_prefix_to_route(route, prefix, exclusions))
881 .collect()
882}
883
884fn apply_global_prefix_to_route(
885 route: RouteDefinition,
886 prefix: Option<&str>,
887 exclusions: &[MiddlewareRoute],
888) -> Result<RouteDefinition> {
889 let Some(prefix) = prefix else {
890 return Ok(route);
891 };
892 if global_prefix_excludes(&route, exclusions) {
893 return Ok(route);
894 }
895 route.with_path_prefix(prefix)
896}
897
898fn global_prefix_excludes(route: &RouteDefinition, exclusions: &[MiddlewareRoute]) -> bool {
899 exclusions
900 .iter()
901 .any(|exclusion| exclusion.matches(route.method(), &[route.path().to_string()]))
902}
903
904fn validate_unique_routes(
905 routes: &[RouteDefinition],
906 versioning: Option<&ApiVersioning>,
907) -> Result<()> {
908 for (index, route) in routes.iter().enumerate() {
909 for existing in routes.iter().take(index) {
910 if existing.method() != route.method()
911 || existing.path_shape_key() != route.path_shape_key()
912 || existing.host_shape_key() != route.host_shape_key()
913 {
914 continue;
915 }
916
917 let duplicate = match versioning {
918 Some(versioning) => existing
919 .versioning()
920 .overlaps(route.versioning(), versioning.default_version()),
921 None => true,
922 };
923 if !duplicate {
924 continue;
925 }
926
927 let host = route
928 .host()
929 .map(|host| format!(" host {host}"))
930 .unwrap_or_default();
931 return Err(BootError::DuplicateRoute(format!(
932 "{} {}{} version {}",
933 route.method().as_str(),
934 route.path(),
935 host,
936 route.versioning()
937 )));
938 }
939 }
940
941 Ok(())
942}
943
944fn validate_unique_gateways(gateways: &[WebSocketGatewayDefinition]) -> Result<()> {
945 let mut seen = BTreeSet::new();
946
947 for gateway in gateways {
948 if !seen.insert(gateway.path_shape()) {
949 return Err(BootError::DuplicateRoute(format!("WS {}", gateway.path())));
950 }
951 }
952
953 Ok(())
954}
955
956fn validate_gateway_route_conflicts(
957 routes: &[RouteDefinition],
958 gateways: &[WebSocketGatewayDefinition],
959) -> Result<()> {
960 let get_routes = routes
961 .iter()
962 .filter(|route| route.method().matches(crate::HttpMethod::Get))
963 .map(RouteDefinition::path_shape)
964 .collect::<BTreeSet<_>>();
965
966 for gateway in gateways {
967 if get_routes.contains(&gateway.path_shape()) {
968 return Err(BootError::DuplicateRoute(format!("GET {}", gateway.path())));
969 }
970 }
971
972 Ok(())
973}
974
975fn validate_unique_message_patterns(patterns: &[MessagePatternDefinition]) -> Result<()> {
976 let mut seen = BTreeSet::new();
977
978 for pattern in patterns {
979 if !seen.insert(pattern.pattern()) {
980 return Err(BootError::DuplicateRoute(format!(
981 "message pattern {}",
982 pattern.pattern()
983 )));
984 }
985 }
986
987 Ok(())
988}