1use std::any::{Any, TypeId};
36use std::collections::HashMap;
37use std::future::Future;
38use std::pin::Pin;
39use std::sync::Arc;
40
41use crate::context::RequestContext;
42use crate::middleware::{BoxFuture, Handler, Middleware, MiddlewareStack};
43use crate::request::{Method, Request};
44use crate::response::{Response, StatusCode};
45use crate::shutdown::ShutdownController;
46use fastapi_router::{Route, RouteLookup, Router};
47
48pub enum StartupHook {
54 Sync(Box<dyn FnOnce() -> Result<(), StartupHookError> + Send>),
56 AsyncFactory(
58 Box<
59 dyn FnOnce() -> Pin<Box<dyn Future<Output = Result<(), StartupHookError>> + Send>>
60 + Send,
61 >,
62 ),
63}
64
65impl StartupHook {
66 pub fn sync<F>(f: F) -> Self
68 where
69 F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,
70 {
71 Self::Sync(Box::new(f))
72 }
73
74 pub fn async_fn<F, Fut>(f: F) -> Self
76 where
77 F: FnOnce() -> Fut + Send + 'static,
78 Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,
79 {
80 Self::AsyncFactory(Box::new(move || Box::pin(f())))
81 }
82
83 pub fn run(
87 self,
88 ) -> Result<
89 Option<Pin<Box<dyn Future<Output = Result<(), StartupHookError>> + Send>>>,
90 StartupHookError,
91 > {
92 match self {
93 Self::Sync(f) => f().map(|()| None),
94 Self::AsyncFactory(f) => Ok(Some(f())),
95 }
96 }
97}
98
99#[derive(Debug)]
101pub struct StartupHookError {
102 pub hook_name: Option<String>,
104 pub message: String,
106 pub abort: bool,
108}
109
110impl StartupHookError {
111 pub fn new(message: impl Into<String>) -> Self {
113 Self {
114 hook_name: None,
115 message: message.into(),
116 abort: true,
117 }
118 }
119
120 #[must_use]
122 pub fn with_hook_name(mut self, name: impl Into<String>) -> Self {
123 self.hook_name = Some(name.into());
124 self
125 }
126
127 #[must_use]
129 pub fn with_abort(mut self, abort: bool) -> Self {
130 self.abort = abort;
131 self
132 }
133
134 pub fn non_fatal(message: impl Into<String>) -> Self {
136 Self {
137 hook_name: None,
138 message: message.into(),
139 abort: false,
140 }
141 }
142}
143
144impl std::fmt::Display for StartupHookError {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 if let Some(name) = &self.hook_name {
147 write!(f, "Startup hook '{}' failed: {}", name, self.message)
148 } else {
149 write!(f, "Startup hook failed: {}", self.message)
150 }
151 }
152}
153
154impl std::error::Error for StartupHookError {}
155
156#[derive(Debug)]
158pub enum StartupOutcome {
159 Success,
161 PartialSuccess {
163 warnings: usize,
165 },
166 Aborted(StartupHookError),
168}
169
170impl StartupOutcome {
171 #[must_use]
173 pub fn can_proceed(&self) -> bool {
174 !matches!(self, Self::Aborted(_))
175 }
176
177 pub fn into_error(self) -> Option<StartupHookError> {
179 match self {
180 Self::Aborted(e) => Some(e),
181 _ => None,
182 }
183 }
184}
185
186pub type BoxHandler = Box<
193 dyn for<'a> Fn(&'a RequestContext, &'a mut Request) -> BoxFuture<'a, Response> + Send + Sync,
194>;
195
196pub type BoxWebSocketHandler = Box<
198 dyn Fn(
199 &RequestContext,
200 &mut Request,
201 crate::websocket::WebSocket,
202 ) -> std::pin::Pin<
203 Box<dyn Future<Output = Result<(), crate::websocket::WebSocketError>> + Send>,
204 > + Send
205 + Sync,
206>;
207
208#[derive(Clone)]
210pub struct RouteEntry {
211 pub method: Method,
213 pub path: String,
215 meta: Option<fastapi_router::Route>,
220 handler: Arc<BoxHandler>,
222}
223
224impl RouteEntry {
225 pub fn new<H, Fut>(method: Method, path: impl Into<String>, handler: H) -> Self
232 where
233 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
234 Fut: Future<Output = Response> + Send + 'static,
235 {
236 Self::new_boxed(method, path, move |ctx, req| {
237 Box::pin(handler(ctx, req)) as BoxFuture<'_, Response>
238 })
239 }
240
241 pub fn new_boxed<H>(method: Method, path: impl Into<String>, handler: H) -> Self
249 where
250 H: for<'a> Fn(&'a RequestContext, &'a mut Request) -> BoxFuture<'a, Response>
251 + Send
252 + Sync
253 + 'static,
254 {
255 let handler: BoxHandler = Box::new(handler);
256 Self {
257 method,
258 path: path.into(),
259 meta: None,
260 handler: Arc::new(handler),
261 }
262 }
263
264 pub fn from_route<H>(route: fastapi_router::Route, handler: H) -> Self
270 where
271 H: for<'a> Fn(&'a RequestContext, &'a mut Request) -> BoxFuture<'a, Response>
272 + Send
273 + Sync
274 + 'static,
275 {
276 let method = route.method;
277 let path = route.path.clone();
278 let mut entry = Self::new_boxed(method, path, handler);
279 entry.meta = Some(route);
280 entry
281 }
282
283 pub fn route_meta(&self) -> Option<&fastapi_router::Route> {
285 self.meta.as_ref()
286 }
287
288 pub async fn call(&self, ctx: &RequestContext, req: &mut Request) -> Response {
290 (self.handler)(ctx, req).await
291 }
292}
293
294impl std::fmt::Debug for RouteEntry {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 f.debug_struct("RouteEntry")
297 .field("method", &self.method)
298 .field("path", &self.path)
299 .field("meta", &self.meta.as_ref().map(|r| r.operation_id.as_str()))
300 .finish_non_exhaustive()
301 }
302}
303
304#[derive(Clone)]
306pub struct WebSocketRouteEntry {
307 pub path: String,
309 handler: Arc<BoxWebSocketHandler>,
311}
312
313impl WebSocketRouteEntry {
314 pub fn new<H, Fut>(path: impl Into<String>, handler: H) -> Self
316 where
317 H: Fn(&RequestContext, &mut Request, crate::websocket::WebSocket) -> Fut
318 + Send
319 + Sync
320 + 'static,
321 Fut: Future<Output = Result<(), crate::websocket::WebSocketError>> + Send + 'static,
322 {
323 let handler: BoxWebSocketHandler = Box::new(move |ctx, req, ws| {
324 let fut = handler(ctx, req, ws);
325 Box::pin(fut)
326 });
327 Self {
328 path: path.into(),
329 handler: Arc::new(handler),
330 }
331 }
332
333 pub async fn call(
335 &self,
336 ctx: &RequestContext,
337 req: &mut Request,
338 ws: crate::websocket::WebSocket,
339 ) -> Result<(), crate::websocket::WebSocketError> {
340 (self.handler)(ctx, req, ws).await
341 }
342}
343
344impl std::fmt::Debug for WebSocketRouteEntry {
345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346 f.debug_struct("WebSocketRouteEntry")
347 .field("path", &self.path)
348 .finish_non_exhaustive()
349 }
350}
351
352#[derive(Default)]
357pub struct StateContainer {
358 state: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
359}
360
361impl StateContainer {
362 #[must_use]
364 pub fn new() -> Self {
365 Self {
366 state: HashMap::new(),
367 }
368 }
369
370 pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
374 self.state.insert(TypeId::of::<T>(), Arc::new(value));
375 }
376
377 pub fn get<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
379 self.state
380 .get(&TypeId::of::<T>())
381 .and_then(|v| Arc::clone(v).downcast::<T>().ok())
382 }
383
384 pub fn contains<T: 'static>(&self) -> bool {
386 self.state.contains_key(&TypeId::of::<T>())
387 }
388
389 pub fn len(&self) -> usize {
391 self.state.len()
392 }
393
394 pub fn is_empty(&self) -> bool {
396 self.state.is_empty()
397 }
398}
399
400impl std::fmt::Debug for StateContainer {
401 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402 f.debug_struct("StateContainer")
403 .field("count", &self.state.len())
404 .finish()
405 }
406}
407
408pub type BoxExceptionHandler = Box<
416 dyn Fn(&RequestContext, Box<dyn std::error::Error + Send + Sync>) -> Response + Send + Sync,
417>;
418
419#[derive(Default)]
454pub struct ExceptionHandlers {
455 handlers: HashMap<TypeId, BoxExceptionHandler>,
456}
457
458impl ExceptionHandlers {
459 #[must_use]
461 pub fn new() -> Self {
462 Self {
463 handlers: HashMap::new(),
464 }
465 }
466
467 #[must_use]
469 pub fn with_defaults() -> Self {
470 let mut handlers = Self::new();
471
472 handlers.register::<crate::HttpError>(|_ctx, err| {
474 use crate::IntoResponse;
475 err.into_response()
476 });
477
478 handlers.register::<crate::ValidationErrors>(|_ctx, err| {
480 use crate::IntoResponse;
481 err.into_response()
482 });
483
484 handlers
485 }
486
487 pub fn register<E>(
492 &mut self,
493 handler: impl Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
494 ) where
495 E: std::error::Error + Send + Sync + 'static,
496 {
497 let boxed_handler: BoxExceptionHandler = Box::new(move |ctx, err| {
498 match err.downcast::<E>() {
500 Ok(typed_err) => handler(ctx, *typed_err),
501 Err(_) => {
502 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
504 }
505 }
506 });
507 self.handlers.insert(TypeId::of::<E>(), boxed_handler);
508 }
509
510 #[must_use]
512 pub fn handler<E>(
513 mut self,
514 handler: impl Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
515 ) -> Self
516 where
517 E: std::error::Error + Send + Sync + 'static,
518 {
519 self.register::<E>(handler);
520 self
521 }
522
523 pub fn handle<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
528 where
529 E: std::error::Error + Send + Sync + 'static,
530 {
531 let type_id = TypeId::of::<E>();
532 self.handlers
533 .get(&type_id)
534 .map(|handler| handler(ctx, Box::new(err)))
535 }
536
537 pub fn handle_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
539 where
540 E: std::error::Error + Send + Sync + 'static,
541 {
542 self.handle(ctx, err)
543 .unwrap_or_else(|| Response::with_status(StatusCode::INTERNAL_SERVER_ERROR))
544 }
545
546 pub fn has_handler<E: 'static>(&self) -> bool {
548 self.handlers.contains_key(&TypeId::of::<E>())
549 }
550
551 pub fn len(&self) -> usize {
553 self.handlers.len()
554 }
555
556 pub fn is_empty(&self) -> bool {
558 self.handlers.is_empty()
559 }
560
561 pub fn merge(&mut self, other: ExceptionHandlers) {
565 self.handlers.extend(other.handlers);
566 }
567}
568
569impl std::fmt::Debug for ExceptionHandlers {
570 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571 f.debug_struct("ExceptionHandlers")
572 .field("count", &self.handlers.len())
573 .finish()
574 }
575}
576
577#[derive(Debug, Clone)]
579pub struct AppConfig {
580 pub name: String,
582 pub version: String,
584 pub debug: bool,
586 pub root_path: String,
590 pub root_path_in_servers: bool,
592 pub trailing_slash_mode: crate::routing::TrailingSlashMode,
594 pub debug_config: crate::error::DebugConfig,
596 pub max_body_size: usize,
598 pub request_timeout_ms: u64,
600}
601
602impl Default for AppConfig {
603 fn default() -> Self {
604 Self {
605 name: String::from("fastapi_rust"),
606 version: String::from("0.1.0"),
607 debug: false,
608 root_path: String::new(),
609 root_path_in_servers: false,
610 trailing_slash_mode: crate::routing::TrailingSlashMode::Strict,
611 debug_config: crate::error::DebugConfig::default(),
612 max_body_size: 1024 * 1024, request_timeout_ms: 30_000, }
615 }
616}
617
618impl AppConfig {
619 #[must_use]
621 pub fn new() -> Self {
622 Self::default()
623 }
624
625 #[must_use]
627 pub fn name(mut self, name: impl Into<String>) -> Self {
628 self.name = name.into();
629 self
630 }
631
632 #[must_use]
634 pub fn version(mut self, version: impl Into<String>) -> Self {
635 self.version = version.into();
636 self
637 }
638
639 #[must_use]
641 pub fn debug(mut self, debug: bool) -> Self {
642 self.debug = debug;
643 self
644 }
645
646 #[must_use]
652 pub fn root_path(mut self, root_path: impl Into<String>) -> Self {
653 let mut rp = root_path.into();
654 while rp.ends_with('/') {
656 rp.pop();
657 }
658 self.root_path = rp;
659 self
660 }
661
662 #[must_use]
664 pub fn root_path_in_servers(mut self, enabled: bool) -> Self {
665 self.root_path_in_servers = enabled;
666 self
667 }
668
669 #[must_use]
671 pub fn trailing_slash_mode(mut self, mode: crate::routing::TrailingSlashMode) -> Self {
672 self.trailing_slash_mode = mode;
673 self
674 }
675
676 #[must_use]
678 pub fn debug_config(mut self, config: crate::error::DebugConfig) -> Self {
679 self.debug_config = config;
680 self
681 }
682
683 #[must_use]
685 pub fn max_body_size(mut self, size: usize) -> Self {
686 self.max_body_size = size;
687 self
688 }
689
690 #[must_use]
692 pub fn request_timeout_ms(mut self, timeout: u64) -> Self {
693 self.request_timeout_ms = timeout;
694 self
695 }
696}
697
698#[derive(Debug, Clone)]
720pub struct OpenApiConfig {
721 pub enabled: bool,
723 pub title: String,
725 pub version: String,
727 pub description: Option<String>,
729 pub openapi_path: String,
731 pub servers: Vec<(String, Option<String>)>,
733 pub tags: Vec<(String, Option<String>)>,
735}
736
737impl Default for OpenApiConfig {
738 fn default() -> Self {
739 Self {
740 enabled: true,
741 title: "FastAPI Rust".to_string(),
742 version: "0.1.0".to_string(),
743 description: None,
744 openapi_path: "/openapi.json".to_string(),
745 servers: Vec::new(),
746 tags: Vec::new(),
747 }
748 }
749}
750
751impl OpenApiConfig {
752 #[must_use]
754 pub fn new() -> Self {
755 Self::default()
756 }
757
758 #[must_use]
760 pub fn title(mut self, title: impl Into<String>) -> Self {
761 self.title = title.into();
762 self
763 }
764
765 #[must_use]
767 pub fn version(mut self, version: impl Into<String>) -> Self {
768 self.version = version.into();
769 self
770 }
771
772 #[must_use]
774 pub fn description(mut self, description: impl Into<String>) -> Self {
775 self.description = Some(description.into());
776 self
777 }
778
779 #[must_use]
781 pub fn path(mut self, path: impl Into<String>) -> Self {
782 self.openapi_path = path.into();
783 self
784 }
785
786 #[must_use]
788 pub fn server(mut self, url: impl Into<String>, description: Option<String>) -> Self {
789 self.servers.push((url.into(), description));
790 self
791 }
792
793 #[must_use]
795 pub fn tag(mut self, name: impl Into<String>, description: Option<String>) -> Self {
796 self.tags.push((name.into(), description));
797 self
798 }
799
800 #[must_use]
802 pub fn disable(mut self) -> Self {
803 self.enabled = false;
804 self
805 }
806}
807
808pub struct AppBuilder {
834 config: AppConfig,
835 routes: Vec<RouteEntry>,
836 ws_routes: Vec<WebSocketRouteEntry>,
837 middleware: Vec<Arc<dyn Middleware>>,
838 state: StateContainer,
839 exception_handlers: ExceptionHandlers,
840 startup_hooks: Vec<StartupHook>,
841 shutdown_hooks: Vec<Box<dyn FnOnce() + Send>>,
842 async_shutdown_hooks: Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
843 openapi_config: Option<OpenApiConfig>,
844 docs_config: Option<crate::docs::DocsConfig>,
845 api_title: Option<String>,
848 api_version: Option<String>,
849 api_description: Option<String>,
850}
851
852impl Default for AppBuilder {
853 fn default() -> Self {
854 Self {
855 config: AppConfig::default(),
856 routes: Vec::new(),
857 ws_routes: Vec::new(),
858 middleware: Vec::new(),
859 state: StateContainer::default(),
860 exception_handlers: ExceptionHandlers::default(),
861 startup_hooks: Vec::new(),
862 shutdown_hooks: Vec::new(),
863 async_shutdown_hooks: Vec::new(),
864 openapi_config: None,
865 docs_config: None,
866 api_title: None,
867 api_version: None,
868 api_description: None,
869 }
870 }
871}
872
873impl AppBuilder {
874 #[must_use]
876 pub fn new() -> Self {
877 Self::default()
878 }
879
880 #[must_use]
887 pub fn title(mut self, title: impl Into<String>) -> Self {
888 let title = title.into();
889 self.config.name.clone_from(&title);
890 self.api_title = Some(title);
891 self
892 }
893
894 #[must_use]
899 pub fn version(mut self, version: impl Into<String>) -> Self {
900 let version = version.into();
901 self.config.version.clone_from(&version);
902 self.api_version = Some(version);
903 self
904 }
905
906 #[must_use]
910 pub fn description(mut self, description: impl Into<String>) -> Self {
911 self.api_description = Some(description.into());
912 self
913 }
914
915 #[must_use]
917 pub fn config(mut self, config: AppConfig) -> Self {
918 self.config = config;
919 self
920 }
921
922 #[must_use]
938 pub fn openapi(mut self, config: OpenApiConfig) -> Self {
939 self.openapi_config = Some(config);
940 self
941 }
942
943 #[must_use]
952 pub fn enable_docs(mut self, mut config: crate::docs::DocsConfig) -> Self {
953 if config.title == crate::docs::DocsConfig::default().title {
955 config.title.clone_from(&self.config.name);
956 }
957
958 match self.openapi_config.take() {
960 Some(mut openapi) => {
961 openapi.openapi_path.clone_from(&config.openapi_path);
962 self.openapi_config = Some(openapi);
963 }
964 None => {
965 self.openapi_config = Some(
966 OpenApiConfig::new()
967 .title(self.config.name.clone())
968 .version(self.config.version.clone())
969 .path(config.openapi_path.clone()),
970 );
971 }
972 }
973
974 self.docs_config = Some(config);
975 self
976 }
977
978 #[must_use]
982 pub fn route<H, Fut>(mut self, path: impl Into<String>, method: Method, handler: H) -> Self
983 where
984 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
985 Fut: Future<Output = Response> + Send + 'static,
986 {
987 self.routes.push(RouteEntry::new(method, path, handler));
988 self
989 }
990
991 #[must_use]
996 pub fn route_entry(mut self, entry: RouteEntry) -> Self {
997 self.routes.push(entry);
998 self
999 }
1000
1001 #[must_use]
1006 pub fn websocket<H, Fut>(mut self, path: impl Into<String>, handler: H) -> Self
1007 where
1008 H: Fn(&RequestContext, &mut Request, crate::websocket::WebSocket) -> Fut
1009 + Send
1010 + Sync
1011 + 'static,
1012 Fut: Future<Output = Result<(), crate::websocket::WebSocketError>> + Send + 'static,
1013 {
1014 self.ws_routes.push(WebSocketRouteEntry::new(path, handler));
1015 self
1016 }
1017
1018 #[must_use]
1020 pub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1021 where
1022 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1023 Fut: Future<Output = Response> + Send + 'static,
1024 {
1025 self.route(path, Method::Get, handler)
1026 }
1027
1028 #[must_use]
1030 pub fn post<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1031 where
1032 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1033 Fut: Future<Output = Response> + Send + 'static,
1034 {
1035 self.route(path, Method::Post, handler)
1036 }
1037
1038 #[must_use]
1040 pub fn put<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1041 where
1042 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1043 Fut: Future<Output = Response> + Send + 'static,
1044 {
1045 self.route(path, Method::Put, handler)
1046 }
1047
1048 #[must_use]
1050 pub fn delete<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1051 where
1052 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1053 Fut: Future<Output = Response> + Send + 'static,
1054 {
1055 self.route(path, Method::Delete, handler)
1056 }
1057
1058 #[must_use]
1060 pub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1061 where
1062 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1063 Fut: Future<Output = Response> + Send + 'static,
1064 {
1065 self.route(path, Method::Patch, handler)
1066 }
1067
1068 #[must_use]
1074 pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
1075 self.middleware.push(Arc::new(middleware));
1076 self
1077 }
1078
1079 #[must_use]
1083 pub fn state<T: Send + Sync + 'static>(mut self, state: T) -> Self {
1084 self.state.insert(state);
1085 self
1086 }
1087
1088 #[must_use]
1116 pub fn exception_handler<E, H>(mut self, handler: H) -> Self
1117 where
1118 E: std::error::Error + Send + Sync + 'static,
1119 H: Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
1120 {
1121 self.exception_handlers.register::<E>(handler);
1122 self
1123 }
1124
1125 #[must_use]
1129 pub fn exception_handlers(mut self, handlers: ExceptionHandlers) -> Self {
1130 self.exception_handlers = handlers;
1131 self
1132 }
1133
1134 #[must_use]
1140 pub fn with_default_exception_handlers(mut self) -> Self {
1141 self.exception_handlers = ExceptionHandlers::with_defaults();
1142 self
1143 }
1144
1145 #[must_use]
1169 pub fn on_startup<F>(mut self, hook: F) -> Self
1170 where
1171 F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,
1172 {
1173 self.startup_hooks.push(StartupHook::Sync(Box::new(hook)));
1174 self
1175 }
1176
1177 #[must_use]
1192 pub fn on_startup_async<F, Fut>(mut self, hook: F) -> Self
1193 where
1194 F: FnOnce() -> Fut + Send + 'static,
1195 Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,
1196 {
1197 self.startup_hooks.push(StartupHook::AsyncFactory(Box::new(
1198 move || Box::pin(hook()),
1199 )));
1200 self
1201 }
1202
1203 #[must_use]
1221 pub fn on_shutdown<F>(mut self, hook: F) -> Self
1222 where
1223 F: FnOnce() + Send + 'static,
1224 {
1225 self.shutdown_hooks.push(Box::new(hook));
1226 self
1227 }
1228
1229 #[must_use]
1243 pub fn on_shutdown_async<F, Fut>(mut self, hook: F) -> Self
1244 where
1245 F: FnOnce() -> Fut + Send + 'static,
1246 Fut: Future<Output = ()> + Send + 'static,
1247 {
1248 self.async_shutdown_hooks
1249 .push(Box::new(move || Box::pin(hook())));
1250 self
1251 }
1252
1253 #[must_use]
1255 pub fn startup_hook_count(&self) -> usize {
1256 self.startup_hooks.len()
1257 }
1258
1259 #[must_use]
1261 pub fn shutdown_hook_count(&self) -> usize {
1262 self.shutdown_hooks.len() + self.async_shutdown_hooks.len()
1263 }
1264
1265 #[must_use]
1273 #[allow(clippy::too_many_lines)]
1274 pub fn build(mut self) -> App {
1275 if let Some(openapi) = self.openapi_config.as_mut() {
1277 if let Some(title) = self.api_title.take() {
1278 openapi.title = title;
1279 }
1280 if let Some(version) = self.api_version.take() {
1281 openapi.version = version;
1282 }
1283 if let Some(description) = self.api_description.take() {
1284 openapi.description = Some(description);
1285 }
1286 }
1287
1288 let (openapi_spec, openapi_path) = if let Some(ref openapi_config) = self.openapi_config {
1290 if openapi_config.enabled {
1291 let spec = self.generate_openapi_spec(openapi_config);
1292 let spec_json =
1293 serde_json::to_string_pretty(&spec).unwrap_or_else(|_| "{}".to_string());
1294 (
1295 Some(Arc::new(spec_json)),
1296 Some(openapi_config.openapi_path.clone()),
1297 )
1298 } else {
1299 (None, None)
1300 }
1301 } else {
1302 (None, None)
1303 };
1304
1305 if let (Some(spec), Some(path)) = (&openapi_spec, &openapi_path) {
1307 let spec_clone = Arc::clone(spec);
1308 self.routes.push(RouteEntry::new(
1309 Method::Get,
1310 path.clone(),
1311 move |_ctx: &RequestContext, _req: &mut Request| {
1312 let spec = Arc::clone(&spec_clone);
1313 async move {
1314 Response::ok()
1315 .header("content-type", b"application/json".to_vec())
1316 .body(crate::response::ResponseBody::Bytes(
1317 spec.as_bytes().to_vec(),
1318 ))
1319 }
1320 },
1321 ));
1322 }
1323
1324 if let (Some(openapi_url), Some(docs_config)) = (openapi_path.clone(), self.docs_config) {
1328 let docs_config = Arc::new(docs_config);
1329 let openapi_url = Arc::new(openapi_url);
1330
1331 if let Some(docs_path) = docs_config.docs_path.clone() {
1332 let cfg = Arc::clone(&docs_config);
1333 let url = Arc::clone(&openapi_url);
1334 self.routes.push(RouteEntry::new(
1335 Method::Get,
1336 docs_path.clone(),
1337 move |_ctx: &RequestContext, _req: &mut Request| {
1338 let cfg = Arc::clone(&cfg);
1339 let url = Arc::clone(&url);
1340 async move { crate::docs::swagger_ui_response(&cfg, &url) }
1341 },
1342 ));
1343
1344 let docs_prefix = docs_path.trim_end_matches('/');
1346 let oauth2_redirect_path = if docs_prefix.is_empty() {
1347 "/oauth2-redirect".to_string()
1348 } else {
1349 format!("{docs_prefix}/oauth2-redirect")
1350 };
1351 self.routes.push(RouteEntry::new(
1352 Method::Get,
1353 oauth2_redirect_path,
1354 |_ctx: &RequestContext, _req: &mut Request| async move {
1355 crate::docs::oauth2_redirect_response()
1356 },
1357 ));
1358 }
1359
1360 if let Some(redoc_path) = docs_config.redoc_path.clone() {
1361 let cfg = Arc::clone(&docs_config);
1362 let url = Arc::clone(&openapi_url);
1363 self.routes.push(RouteEntry::new(
1364 Method::Get,
1365 redoc_path,
1366 move |_ctx: &RequestContext, _req: &mut Request| {
1367 let cfg = Arc::clone(&cfg);
1368 let url = Arc::clone(&url);
1369 async move { crate::docs::redoc_response(&cfg, &url) }
1370 },
1371 ));
1372 }
1373 }
1374
1375 let mut middleware_stack = MiddlewareStack::with_capacity(self.middleware.len());
1376 for mw in self.middleware {
1377 middleware_stack.push_arc(mw);
1378 }
1379
1380 let mut router = Router::new();
1382 for entry in &self.routes {
1383 let route = entry
1384 .route_meta()
1385 .cloned()
1386 .unwrap_or_else(|| Route::new(entry.method, &entry.path));
1387 router
1388 .add(route)
1389 .expect("route conflict during App::build()");
1390 }
1391
1392 let mut ws_router = Router::new();
1394 for entry in &self.ws_routes {
1395 ws_router
1396 .add(Route::new(Method::Get, &entry.path))
1397 .expect("websocket route conflict during App::build()");
1398 }
1399
1400 App {
1401 config: self.config,
1402 routes: self.routes,
1403 ws_routes: self.ws_routes,
1404 router,
1405 ws_router,
1406 middleware: middleware_stack,
1407 state: Arc::new(self.state),
1408 exception_handlers: Arc::new(self.exception_handlers),
1409 dependency_overrides: Arc::new(crate::dependency::DependencyOverrides::new()),
1410 startup_hooks: parking_lot::Mutex::new(self.startup_hooks),
1411 shutdown_hooks: parking_lot::Mutex::new(self.shutdown_hooks),
1412 async_shutdown_hooks: parking_lot::Mutex::new(self.async_shutdown_hooks),
1413 openapi_spec,
1414 }
1415 }
1416
1417 fn generate_openapi_spec(&self, config: &OpenApiConfig) -> fastapi_openapi::OpenApi {
1419 use fastapi_openapi::{OpenApiBuilder, Operation, Response as OAResponse};
1420 use std::collections::HashMap;
1421
1422 let mut builder = OpenApiBuilder::new(&config.title, &config.version);
1423
1424 if let Some(ref desc) = config.description {
1426 builder = builder.description(desc);
1427 }
1428
1429 for (url, desc) in &config.servers {
1431 builder = builder.server(url, desc.clone());
1432 }
1433
1434 for (name, desc) in &config.tags {
1436 builder = builder.tag(name, desc.clone());
1437 }
1438
1439 for entry in &self.routes {
1441 if let Some(route) = entry.route_meta() {
1442 builder.add_route(route);
1443 continue;
1444 }
1445
1446 let mut responses = HashMap::new();
1448 responses.insert(
1449 "200".to_string(),
1450 OAResponse {
1451 description: "Successful response".to_string(),
1452 content: HashMap::new(),
1453 },
1454 );
1455
1456 let operation = Operation {
1457 operation_id: Some(format!(
1458 "{}_{}",
1459 entry.method.as_str().to_lowercase(),
1460 entry
1461 .path
1462 .replace('/', "_")
1463 .replace(['{', '}'], "")
1464 .trim_matches('_')
1465 )),
1466 summary: None,
1467 description: None,
1468 tags: Vec::new(),
1469 parameters: Vec::new(),
1470 request_body: None,
1471 responses,
1472 deprecated: false,
1473 };
1474
1475 builder = builder.operation(entry.method.as_str(), &entry.path, operation);
1476 }
1477
1478 builder.build()
1479 }
1480}
1481
1482impl std::fmt::Debug for AppBuilder {
1483 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1484 f.debug_struct("AppBuilder")
1485 .field("config", &self.config)
1486 .field("routes", &self.routes.len())
1487 .field("middleware", &self.middleware.len())
1488 .field("state", &self.state)
1489 .field("exception_handlers", &self.exception_handlers)
1490 .field("startup_hooks", &self.startup_hooks.len())
1491 .field("shutdown_hooks", &self.shutdown_hook_count())
1492 .finish()
1493 }
1494}
1495
1496pub struct App {
1501 config: AppConfig,
1502 routes: Vec<RouteEntry>,
1503 ws_routes: Vec<WebSocketRouteEntry>,
1504 router: Router,
1505 ws_router: Router,
1506 middleware: MiddlewareStack,
1507 state: Arc<StateContainer>,
1508 exception_handlers: Arc<ExceptionHandlers>,
1509 dependency_overrides: Arc<crate::dependency::DependencyOverrides>,
1510 startup_hooks: parking_lot::Mutex<Vec<StartupHook>>,
1511 shutdown_hooks: parking_lot::Mutex<Vec<Box<dyn FnOnce() + Send>>>,
1512 async_shutdown_hooks: parking_lot::Mutex<
1513 Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
1514 >,
1515 openapi_spec: Option<Arc<String>>,
1517}
1518
1519impl App {
1520 #[must_use]
1522 pub fn builder() -> AppBuilder {
1523 AppBuilder::new()
1524 }
1525
1526 #[cfg(feature = "testing")]
1531 #[must_use]
1532 pub fn test_client(self: Arc<Self>) -> crate::testing::TestClient<Arc<Self>> {
1533 crate::testing::TestClient::new(self)
1534 }
1535
1536 #[cfg(feature = "testing")]
1538 #[must_use]
1539 pub fn test_client_with_seed(
1540 self: Arc<Self>,
1541 seed: u64,
1542 ) -> crate::testing::TestClient<Arc<Self>> {
1543 crate::testing::TestClient::with_seed(self, seed)
1544 }
1545
1546 #[must_use]
1548 pub fn config(&self) -> &AppConfig {
1549 &self.config
1550 }
1551
1552 #[must_use]
1554 pub fn route_count(&self) -> usize {
1555 self.routes.len()
1556 }
1557
1558 #[must_use]
1560 pub fn websocket_route_count(&self) -> usize {
1561 self.ws_routes.len()
1562 }
1563
1564 #[must_use]
1566 pub fn has_websocket_route(&self, path: &str) -> bool {
1567 matches!(
1568 self.ws_router.lookup(path, Method::Get),
1569 RouteLookup::Match(_)
1570 )
1571 }
1572
1573 pub fn routes(&self) -> impl Iterator<Item = (Method, &str)> {
1577 self.routes.iter().map(|r| (r.method, r.path.as_str()))
1578 }
1579
1580 #[must_use]
1594 pub fn openapi_spec(&self) -> Option<&str> {
1595 self.openapi_spec.as_ref().map(|s| s.as_str())
1596 }
1597
1598 #[must_use]
1600 pub fn state(&self) -> &Arc<StateContainer> {
1601 &self.state
1602 }
1603
1604 pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
1606 self.state.get::<T>()
1607 }
1608
1609 #[must_use]
1611 pub fn exception_handlers(&self) -> &Arc<ExceptionHandlers> {
1612 &self.exception_handlers
1613 }
1614
1615 pub fn override_dependency_value<T>(&self, value: T)
1620 where
1621 T: crate::dependency::FromDependency,
1622 {
1623 self.dependency_overrides.insert_value(value);
1624 }
1625
1626 pub fn clear_dependency_overrides(&self) {
1628 self.dependency_overrides.clear();
1629 }
1630
1631 #[must_use]
1633 pub fn dependency_overrides(&self) -> Arc<crate::dependency::DependencyOverrides> {
1634 Arc::clone(&self.dependency_overrides)
1635 }
1636
1637 pub fn take_background_tasks(req: &mut Request) -> Option<crate::request::BackgroundTasks> {
1642 req.take_extension::<crate::request::BackgroundTasks>()
1643 }
1644
1645 pub fn handle_error<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
1650 where
1651 E: std::error::Error + Send + Sync + 'static,
1652 {
1653 self.exception_handlers.handle(ctx, err)
1654 }
1655
1656 pub fn handle_error_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
1658 where
1659 E: std::error::Error + Send + Sync + 'static,
1660 {
1661 self.exception_handlers.handle_or_default(ctx, err)
1662 }
1663
1664 pub async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response {
1669 match self.router.lookup(req.path(), req.method()) {
1671 RouteLookup::Match(route_match) => {
1672 let entry = self.routes.iter().find(|e| {
1674 e.method == route_match.route.method && e.path == route_match.route.path
1675 });
1676
1677 let Some(entry) = entry else {
1678 return Response::with_status(StatusCode::INTERNAL_SERVER_ERROR);
1680 };
1681
1682 if !route_match.params.is_empty() {
1684 let path_params = crate::extract::PathParams::from_pairs(
1685 route_match
1686 .params
1687 .iter()
1688 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
1689 .collect(),
1690 );
1691 req.insert_extension(path_params);
1692 }
1693
1694 let handler = RouteHandler { entry };
1696 self.middleware.execute(&handler, ctx, req).await
1697 }
1698 RouteLookup::MethodNotAllowed { allowed } => {
1699 if req.method() == Method::Options {
1702 let mut methods = allowed.methods().to_vec();
1703 if !methods.contains(&Method::Options) {
1704 methods.push(Method::Options);
1705 }
1706 let allow = fastapi_router::AllowedMethods::new(methods);
1707 Response::with_status(StatusCode::NO_CONTENT)
1708 .header("allow", allow.header_value().as_bytes().to_vec())
1709 } else {
1710 Response::with_status(StatusCode::METHOD_NOT_ALLOWED)
1711 .header("allow", allowed.header_value().as_bytes().to_vec())
1712 }
1713 }
1714 RouteLookup::NotFound => Response::with_status(StatusCode::NOT_FOUND),
1715 }
1716 }
1717
1718 pub async fn handle_websocket(
1723 &self,
1724 ctx: &RequestContext,
1725 req: &mut Request,
1726 ws: crate::websocket::WebSocket,
1727 ) -> Result<(), crate::websocket::WebSocketError> {
1728 match self.ws_router.lookup(req.path(), Method::Get) {
1729 RouteLookup::Match(route_match) => {
1730 let entry = self
1731 .ws_routes
1732 .iter()
1733 .find(|e| e.path == route_match.route.path);
1734 let Some(entry) = entry else {
1735 return Err(crate::websocket::WebSocketError::Protocol(
1736 "websocket route missing handler",
1737 ));
1738 };
1739
1740 if !route_match.params.is_empty() {
1741 let path_params = crate::extract::PathParams::from_pairs(
1742 route_match
1743 .params
1744 .iter()
1745 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
1746 .collect(),
1747 );
1748 req.insert_extension(path_params);
1749 }
1750
1751 entry.call(ctx, req, ws).await
1752 }
1753 _ => Err(crate::websocket::WebSocketError::Protocol(
1754 "no websocket route matched",
1755 )),
1756 }
1757 }
1758
1759 pub async fn run_startup_hooks(&self) -> StartupOutcome {
1776 let hooks: Vec<StartupHook> = std::mem::take(&mut *self.startup_hooks.lock());
1777 let mut warnings = 0;
1778
1779 for hook in hooks {
1780 match hook.run() {
1781 Ok(None) => {
1782 }
1784 Ok(Some(fut)) => {
1785 match fut.await {
1787 Ok(()) => {}
1788 Err(e) if e.abort => {
1789 return StartupOutcome::Aborted(e);
1790 }
1791 Err(_) => {
1792 warnings += 1;
1793 }
1794 }
1795 }
1796 Err(e) if e.abort => {
1797 return StartupOutcome::Aborted(e);
1798 }
1799 Err(_) => {
1800 warnings += 1;
1801 }
1802 }
1803 }
1804
1805 if warnings > 0 {
1806 StartupOutcome::PartialSuccess { warnings }
1807 } else {
1808 StartupOutcome::Success
1809 }
1810 }
1811
1812 pub async fn run_shutdown_hooks(&self) {
1819 let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
1821 for hook in async_hooks.into_iter().rev() {
1822 let fut = hook();
1823 fut.await;
1824 }
1825
1826 let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
1828 for hook in sync_hooks.into_iter().rev() {
1829 hook();
1830 }
1831 }
1832
1833 pub fn transfer_shutdown_hooks(&self, controller: &ShutdownController) {
1840 let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
1842 for hook in sync_hooks {
1843 controller.register_hook(hook);
1844 }
1845
1846 let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
1848 for hook in async_hooks {
1849 controller.register_async_hook(move || hook());
1850 }
1851 }
1852
1853 #[must_use]
1855 pub fn pending_startup_hooks(&self) -> usize {
1856 self.startup_hooks.lock().len()
1857 }
1858
1859 #[must_use]
1861 pub fn pending_shutdown_hooks(&self) -> usize {
1862 self.shutdown_hooks.lock().len() + self.async_shutdown_hooks.lock().len()
1863 }
1864}
1865
1866impl std::fmt::Debug for App {
1867 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1868 f.debug_struct("App")
1869 .field("config", &self.config)
1870 .field("routes", &self.routes.len())
1871 .field("middleware", &self.middleware.len())
1872 .field("state", &self.state)
1873 .field("exception_handlers", &self.exception_handlers)
1874 .field("startup_hooks", &self.startup_hooks.lock().len())
1875 .field("shutdown_hooks", &self.pending_shutdown_hooks())
1876 .finish()
1877 }
1878}
1879
1880impl Handler for App {
1882 fn call<'a>(
1883 &'a self,
1884 ctx: &'a RequestContext,
1885 req: &'a mut Request,
1886 ) -> BoxFuture<'a, Response> {
1887 Box::pin(async move { self.handle(ctx, req).await })
1888 }
1889
1890 fn dependency_overrides(&self) -> Option<Arc<crate::dependency::DependencyOverrides>> {
1891 Some(Arc::clone(&self.dependency_overrides))
1892 }
1893}
1894
1895struct RouteHandler<'a> {
1897 entry: &'a RouteEntry,
1898}
1899
1900impl<'a> Handler for RouteHandler<'a> {
1901 fn call<'b>(
1902 &'b self,
1903 ctx: &'b RequestContext,
1904 req: &'b mut Request,
1905 ) -> BoxFuture<'b, Response> {
1906 let handler = self.entry.handler.clone();
1907 Box::pin(async move { handler(ctx, req).await })
1908 }
1909}
1910
1911#[cfg(test)]
1912mod tests {
1913 use super::*;
1914
1915 use crate::response::ResponseBody;
1916
1917 fn test_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
1919 std::future::ready(Response::ok().body(ResponseBody::Bytes(b"Hello, World!".to_vec())))
1920 }
1921
1922 fn health_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
1923 std::future::ready(Response::ok().body(ResponseBody::Bytes(b"OK".to_vec())))
1924 }
1925
1926 fn test_context() -> RequestContext {
1927 let cx = asupersync::Cx::for_testing();
1928 RequestContext::new(cx, 1)
1929 }
1930
1931 #[test]
1932 fn builder_title_and_version_set_app_config() {
1933 let app = App::builder().title("My API").version("2.3.4").build();
1934 assert_eq!(app.config().name, "My API");
1935 assert_eq!(app.config().version, "2.3.4");
1936 assert!(app.openapi_spec().is_none());
1938 }
1939
1940 #[test]
1941 fn builder_metadata_populates_openapi_info_and_wins_over_openapi_config() {
1942 let app = App::builder()
1943 .openapi(OpenApiConfig::new().title("ignored").version("0.0.0"))
1944 .title("My API")
1945 .version("2.3.4")
1946 .description("A sample API")
1947 .build();
1948 let spec: serde_json::Value =
1949 serde_json::from_str(app.openapi_spec().expect("openapi enabled")).unwrap();
1950 assert_eq!(spec["info"]["title"], "My API");
1951 assert_eq!(spec["info"]["version"], "2.3.4");
1952 assert_eq!(spec["info"]["description"], "A sample API");
1953 }
1954
1955 #[test]
1956 fn app_builder_creates_app() {
1957 let app = App::builder()
1958 .config(AppConfig::new().name("Test App"))
1959 .get("/", test_handler)
1960 .get("/health", health_handler)
1961 .build();
1962
1963 assert_eq!(app.route_count(), 2);
1964 assert_eq!(app.config().name, "Test App");
1965 }
1966
1967 #[test]
1968 fn app_config_builder() {
1969 let config = AppConfig::new()
1970 .name("My API")
1971 .version("1.0.0")
1972 .debug(true)
1973 .max_body_size(2 * 1024 * 1024)
1974 .request_timeout_ms(60_000);
1975
1976 assert_eq!(config.name, "My API");
1977 assert_eq!(config.version, "1.0.0");
1978 assert!(config.debug);
1979 assert_eq!(config.max_body_size, 2 * 1024 * 1024);
1980 assert_eq!(config.request_timeout_ms, 60_000);
1981 }
1982
1983 #[test]
1984 fn state_container_insert_and_get() {
1985 #[derive(Debug, PartialEq)]
1986 struct MyState {
1987 value: i32,
1988 }
1989
1990 let mut container = StateContainer::new();
1991 container.insert(MyState { value: 42 });
1992
1993 let state = container.get::<MyState>();
1994 assert!(state.is_some());
1995 assert_eq!(state.unwrap().value, 42);
1996 }
1997
1998 #[test]
1999 fn state_container_multiple_types() {
2000 struct TypeA(i32);
2001 struct TypeB(String);
2002
2003 let mut container = StateContainer::new();
2004 container.insert(TypeA(1));
2005 container.insert(TypeB("hello".to_string()));
2006
2007 assert!(container.contains::<TypeA>());
2008 assert!(container.contains::<TypeB>());
2009 assert!(!container.contains::<i64>());
2010
2011 assert_eq!(container.get::<TypeA>().unwrap().0, 1);
2012 assert_eq!(container.get::<TypeB>().unwrap().0, "hello");
2013 }
2014
2015 #[test]
2016 fn app_builder_with_state() {
2017 struct DbPool {
2018 connection_count: usize,
2019 }
2020
2021 let app = App::builder()
2022 .state(DbPool {
2023 connection_count: 10,
2024 })
2025 .get("/", test_handler)
2026 .build();
2027
2028 let pool = app.get_state::<DbPool>();
2029 assert!(pool.is_some());
2030 assert_eq!(pool.unwrap().connection_count, 10);
2031 }
2032
2033 #[test]
2034 fn app_handles_get_request() {
2035 let app = App::builder().get("/", test_handler).build();
2036
2037 let ctx = test_context();
2038 let mut req = Request::new(Method::Get, "/");
2039
2040 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
2041 assert_eq!(response.status().as_u16(), 200);
2042 }
2043
2044 #[test]
2045 fn app_returns_404_for_unknown_path() {
2046 let app = App::builder().get("/", test_handler).build();
2047
2048 let ctx = test_context();
2049 let mut req = Request::new(Method::Get, "/unknown");
2050
2051 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
2052 assert_eq!(response.status().as_u16(), 404);
2053 }
2054
2055 #[test]
2056 fn app_returns_405_for_wrong_method() {
2057 let app = App::builder().get("/", test_handler).build();
2058
2059 let ctx = test_context();
2060 let mut req = Request::new(Method::Post, "/");
2061
2062 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
2063 assert_eq!(response.status().as_u16(), 405);
2064 }
2065
2066 #[test]
2067 fn app_builder_all_methods() {
2068 let app = App::builder()
2069 .get("/get", test_handler)
2070 .post("/post", test_handler)
2071 .put("/put", test_handler)
2072 .delete("/delete", test_handler)
2073 .patch("/patch", test_handler)
2074 .build();
2075
2076 assert_eq!(app.route_count(), 5);
2077 }
2078
2079 #[test]
2080 fn route_entry_debug() {
2081 let entry = RouteEntry::new(Method::Get, "/test", test_handler);
2082 let debug = format!("{:?}", entry);
2083 assert!(debug.contains("RouteEntry"));
2084 assert!(debug.contains("Get"));
2085 assert!(debug.contains("/test"));
2086 }
2087
2088 #[test]
2089 fn app_with_middleware() {
2090 use crate::middleware::NoopMiddleware;
2091
2092 let app = App::builder()
2093 .middleware(NoopMiddleware)
2094 .middleware(NoopMiddleware)
2095 .get("/", test_handler)
2096 .build();
2097
2098 let ctx = test_context();
2099 let mut req = Request::new(Method::Get, "/");
2100
2101 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
2102 assert_eq!(response.status().as_u16(), 200);
2103 }
2104
2105 #[derive(Debug)]
2111 struct TestError {
2112 message: String,
2113 code: u32,
2114 }
2115
2116 impl std::fmt::Display for TestError {
2117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2118 write!(f, "TestError({}): {}", self.code, self.message)
2119 }
2120 }
2121
2122 impl std::error::Error for TestError {}
2123
2124 #[derive(Debug)]
2126 struct AnotherError(String);
2127
2128 impl std::fmt::Display for AnotherError {
2129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2130 write!(f, "AnotherError: {}", self.0)
2131 }
2132 }
2133
2134 impl std::error::Error for AnotherError {}
2135
2136 #[test]
2139 fn exception_handlers_new_is_empty() {
2140 let handlers = ExceptionHandlers::new();
2141 assert!(handlers.is_empty());
2142 assert_eq!(handlers.len(), 0);
2143 }
2144
2145 #[test]
2146 fn exception_handlers_register_single() {
2147 let mut handlers = ExceptionHandlers::new();
2148 handlers.register::<TestError>(|_ctx, err| {
2149 Response::with_status(StatusCode::BAD_REQUEST)
2150 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2151 });
2152
2153 assert!(handlers.has_handler::<TestError>());
2154 assert!(!handlers.has_handler::<AnotherError>());
2155 assert_eq!(handlers.len(), 1);
2156 }
2157
2158 #[test]
2159 fn exception_handlers_register_multiple() {
2160 let mut handlers = ExceptionHandlers::new();
2161 handlers.register::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2162 handlers.register::<AnotherError>(|_ctx, _err| {
2163 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2164 });
2165
2166 assert!(handlers.has_handler::<TestError>());
2167 assert!(handlers.has_handler::<AnotherError>());
2168 assert_eq!(handlers.len(), 2);
2169 }
2170
2171 #[test]
2172 fn exception_handlers_builder_pattern() {
2173 let handlers = ExceptionHandlers::new()
2174 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2175 .handler::<AnotherError>(|_ctx, _err| {
2176 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2177 });
2178
2179 assert!(handlers.has_handler::<TestError>());
2180 assert!(handlers.has_handler::<AnotherError>());
2181 assert_eq!(handlers.len(), 2);
2182 }
2183
2184 #[test]
2185 fn exception_handlers_with_defaults() {
2186 let handlers = ExceptionHandlers::with_defaults();
2187
2188 assert!(handlers.has_handler::<crate::HttpError>());
2189 assert!(handlers.has_handler::<crate::ValidationErrors>());
2190 assert_eq!(handlers.len(), 2);
2191 }
2192
2193 #[test]
2194 fn exception_handlers_merge() {
2195 let mut handlers1 = ExceptionHandlers::new()
2196 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2197
2198 let handlers2 = ExceptionHandlers::new().handler::<AnotherError>(|_ctx, _err| {
2199 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2200 });
2201
2202 handlers1.merge(handlers2);
2203
2204 assert!(handlers1.has_handler::<TestError>());
2205 assert!(handlers1.has_handler::<AnotherError>());
2206 assert_eq!(handlers1.len(), 2);
2207 }
2208
2209 #[test]
2212 fn exception_handlers_handle_registered_error() {
2213 let handlers = ExceptionHandlers::new().handler::<TestError>(|_ctx, err| {
2214 Response::with_status(StatusCode::BAD_REQUEST)
2215 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2216 });
2217
2218 let ctx = test_context();
2219 let err = TestError {
2220 message: "test error".into(),
2221 code: 42,
2222 };
2223
2224 let response = handlers.handle(&ctx, err);
2225 assert!(response.is_some());
2226
2227 let response = response.unwrap();
2228 assert_eq!(response.status().as_u16(), 400);
2229 }
2230
2231 #[test]
2232 fn exception_handlers_handle_unregistered_error() {
2233 let handlers = ExceptionHandlers::new()
2234 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2235
2236 let ctx = test_context();
2237 let err = AnotherError("unhandled".into());
2238
2239 let response = handlers.handle(&ctx, err);
2240 assert!(response.is_none());
2241 }
2242
2243 #[test]
2244 fn exception_handlers_handle_or_default_registered() {
2245 let handlers = ExceptionHandlers::new()
2246 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2247
2248 let ctx = test_context();
2249 let err = TestError {
2250 message: "test".into(),
2251 code: 1,
2252 };
2253
2254 let response = handlers.handle_or_default(&ctx, err);
2255 assert_eq!(response.status().as_u16(), 400);
2256 }
2257
2258 #[test]
2259 fn exception_handlers_handle_or_default_unregistered() {
2260 let handlers = ExceptionHandlers::new();
2261
2262 let ctx = test_context();
2263 let err = TestError {
2264 message: "test".into(),
2265 code: 1,
2266 };
2267
2268 let response = handlers.handle_or_default(&ctx, err);
2269 assert_eq!(response.status().as_u16(), 500);
2270 }
2271
2272 #[test]
2273 fn exception_handlers_error_values_passed_to_handler() {
2274 use std::sync::atomic::{AtomicU32, Ordering};
2275
2276 let captured_code = Arc::new(AtomicU32::new(0));
2277 let captured_code_clone = captured_code.clone();
2278
2279 let handlers = ExceptionHandlers::new().handler::<TestError>(move |_ctx, err| {
2280 captured_code_clone.store(err.code, Ordering::SeqCst);
2281 Response::with_status(StatusCode::BAD_REQUEST)
2282 });
2283
2284 let ctx = test_context();
2285 let err = TestError {
2286 message: "test".into(),
2287 code: 12345,
2288 };
2289
2290 let _ = handlers.handle(&ctx, err);
2291 assert_eq!(captured_code.load(Ordering::SeqCst), 12345);
2292 }
2293
2294 #[test]
2297 fn app_builder_exception_handler_single() {
2298 let app = App::builder()
2299 .exception_handler::<TestError, _>(|_ctx, err| {
2300 Response::with_status(StatusCode::BAD_REQUEST)
2301 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2302 })
2303 .get("/", test_handler)
2304 .build();
2305
2306 assert!(app.exception_handlers().has_handler::<TestError>());
2307 }
2308
2309 #[test]
2310 fn app_builder_exception_handler_multiple() {
2311 let app = App::builder()
2312 .exception_handler::<TestError, _>(|_ctx, _err| {
2313 Response::with_status(StatusCode::BAD_REQUEST)
2314 })
2315 .exception_handler::<AnotherError, _>(|_ctx, _err| {
2316 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2317 })
2318 .get("/", test_handler)
2319 .build();
2320
2321 assert!(app.exception_handlers().has_handler::<TestError>());
2322 assert!(app.exception_handlers().has_handler::<AnotherError>());
2323 }
2324
2325 #[test]
2326 fn app_builder_with_default_exception_handlers() {
2327 let app = App::builder()
2328 .with_default_exception_handlers()
2329 .get("/", test_handler)
2330 .build();
2331
2332 assert!(app.exception_handlers().has_handler::<crate::HttpError>());
2333 assert!(
2334 app.exception_handlers()
2335 .has_handler::<crate::ValidationErrors>()
2336 );
2337 }
2338
2339 #[test]
2340 fn app_builder_exception_handlers_registry() {
2341 let handlers = ExceptionHandlers::new()
2342 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2343 .handler::<AnotherError>(|_ctx, _err| {
2344 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2345 });
2346
2347 let app = App::builder()
2348 .exception_handlers(handlers)
2349 .get("/", test_handler)
2350 .build();
2351
2352 assert!(app.exception_handlers().has_handler::<TestError>());
2353 assert!(app.exception_handlers().has_handler::<AnotherError>());
2354 }
2355
2356 #[test]
2357 fn app_handle_error_registered() {
2358 let app = App::builder()
2359 .exception_handler::<TestError, _>(|_ctx, _err| {
2360 Response::with_status(StatusCode::BAD_REQUEST)
2361 })
2362 .get("/", test_handler)
2363 .build();
2364
2365 let ctx = test_context();
2366 let err = TestError {
2367 message: "test".into(),
2368 code: 1,
2369 };
2370
2371 let response = app.handle_error(&ctx, err);
2372 assert!(response.is_some());
2373 assert_eq!(response.unwrap().status().as_u16(), 400);
2374 }
2375
2376 #[test]
2377 fn app_handle_error_unregistered() {
2378 let app = App::builder().get("/", test_handler).build();
2379
2380 let ctx = test_context();
2381 let err = TestError {
2382 message: "test".into(),
2383 code: 1,
2384 };
2385
2386 let response = app.handle_error(&ctx, err);
2387 assert!(response.is_none());
2388 }
2389
2390 #[test]
2391 fn app_handle_error_or_default() {
2392 let app = App::builder().get("/", test_handler).build();
2393
2394 let ctx = test_context();
2395 let err = TestError {
2396 message: "test".into(),
2397 code: 1,
2398 };
2399
2400 let response = app.handle_error_or_default(&ctx, err);
2401 assert_eq!(response.status().as_u16(), 500);
2402 }
2403
2404 #[test]
2407 fn exception_handlers_override_on_register() {
2408 let handlers = ExceptionHandlers::new()
2409 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2410 .handler::<TestError>(|_ctx, _err| {
2411 Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
2412 });
2413
2414 assert_eq!(handlers.len(), 1);
2416
2417 let ctx = test_context();
2418 let err = TestError {
2419 message: "test".into(),
2420 code: 1,
2421 };
2422
2423 let response = handlers.handle(&ctx, err);
2425 assert!(response.is_some());
2426 assert_eq!(response.unwrap().status().as_u16(), 422);
2427 }
2428
2429 #[test]
2430 fn exception_handlers_merge_overrides() {
2431 let mut handlers1 = ExceptionHandlers::new()
2432 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2433
2434 let handlers2 = ExceptionHandlers::new().handler::<TestError>(|_ctx, _err| {
2435 Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
2436 });
2437
2438 handlers1.merge(handlers2);
2439
2440 assert_eq!(handlers1.len(), 1);
2442
2443 let ctx = test_context();
2444 let err = TestError {
2445 message: "test".into(),
2446 code: 1,
2447 };
2448
2449 let response = handlers1.handle(&ctx, err);
2451 assert!(response.is_some());
2452 assert_eq!(response.unwrap().status().as_u16(), 422);
2453 }
2454
2455 #[test]
2456 fn exception_handlers_override_default_http_error() {
2457 let mut handlers = ExceptionHandlers::with_defaults();
2459
2460 handlers.register::<crate::HttpError>(|_ctx, err| {
2462 let detail = err.detail.as_deref().unwrap_or("Unknown error");
2464 Response::with_status(err.status)
2465 .header("x-custom-error", b"true".to_vec())
2466 .body(ResponseBody::Bytes(detail.as_bytes().to_vec()))
2467 });
2468
2469 assert_eq!(handlers.len(), 2);
2471
2472 let ctx = test_context();
2473 let err = crate::HttpError::bad_request().with_detail("test error");
2474
2475 let response = handlers.handle(&ctx, err);
2476 assert!(response.is_some());
2477
2478 let response = response.unwrap();
2479 assert_eq!(response.status().as_u16(), 400);
2480
2481 let custom_header = response
2483 .headers()
2484 .iter()
2485 .find(|(name, _)| name.eq_ignore_ascii_case("x-custom-error"))
2486 .map(|(_, v)| v.as_slice());
2487 assert_eq!(custom_header, Some(b"true".as_slice()));
2488 }
2489
2490 #[test]
2491 fn exception_handlers_override_default_validation_errors() {
2492 let mut handlers = ExceptionHandlers::with_defaults();
2494
2495 handlers.register::<crate::ValidationErrors>(|_ctx, errs| {
2497 Response::with_status(StatusCode::BAD_REQUEST)
2499 .header("x-error-count", errs.len().to_string().as_bytes().to_vec())
2500 });
2501
2502 let ctx = test_context();
2503 let mut errs = crate::ValidationErrors::new();
2504 errs.push(crate::ValidationError::missing(
2505 crate::error::loc::body_field("name"),
2506 ));
2507 errs.push(crate::ValidationError::missing(
2508 crate::error::loc::body_field("email"),
2509 ));
2510
2511 let response = handlers.handle(&ctx, errs);
2512 assert!(response.is_some());
2513
2514 let response = response.unwrap();
2515 assert_eq!(response.status().as_u16(), 400);
2517
2518 let count_header = response
2520 .headers()
2521 .iter()
2522 .find(|(name, _)| name.eq_ignore_ascii_case("x-error-count"))
2523 .map(|(_, v)| v.as_slice());
2524 assert_eq!(count_header, Some(b"2".as_slice()));
2525 }
2526
2527 #[test]
2528 fn exception_handlers_debug_format() {
2529 let handlers = ExceptionHandlers::new()
2530 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2531
2532 let debug = format!("{:?}", handlers);
2533 assert!(debug.contains("ExceptionHandlers"));
2534 assert!(debug.contains("count"));
2535 assert!(debug.contains("1"));
2536 }
2537
2538 #[test]
2539 fn app_debug_includes_exception_handlers() {
2540 let app = App::builder()
2541 .exception_handler::<TestError, _>(|_ctx, _err| {
2542 Response::with_status(StatusCode::BAD_REQUEST)
2543 })
2544 .get("/", test_handler)
2545 .build();
2546
2547 let debug = format!("{:?}", app);
2548 assert!(debug.contains("exception_handlers"));
2549 }
2550
2551 #[test]
2552 fn app_builder_debug_includes_exception_handlers() {
2553 let builder = App::builder().exception_handler::<TestError, _>(|_ctx, _err| {
2554 Response::with_status(StatusCode::BAD_REQUEST)
2555 });
2556
2557 let debug = format!("{:?}", builder);
2558 assert!(debug.contains("exception_handlers"));
2559 }
2560
2561 #[test]
2568 fn app_builder_startup_hook_registration() {
2569 let builder = App::builder().on_startup(|| Ok(())).on_startup(|| Ok(()));
2570
2571 assert_eq!(builder.startup_hook_count(), 2);
2572 }
2573
2574 #[test]
2575 fn app_builder_shutdown_hook_registration() {
2576 let builder = App::builder().on_shutdown(|| {}).on_shutdown(|| {});
2577
2578 assert_eq!(builder.shutdown_hook_count(), 2);
2579 }
2580
2581 #[test]
2582 fn app_builder_mixed_hooks() {
2583 let builder = App::builder()
2584 .on_startup(|| Ok(()))
2585 .on_shutdown(|| {})
2586 .on_startup(|| Ok(()))
2587 .on_shutdown(|| {});
2588
2589 assert_eq!(builder.startup_hook_count(), 2);
2590 assert_eq!(builder.shutdown_hook_count(), 2);
2591 }
2592
2593 #[test]
2594 fn app_pending_hooks_count() {
2595 let app = App::builder()
2596 .on_startup(|| Ok(()))
2597 .on_startup(|| Ok(()))
2598 .on_shutdown(|| {})
2599 .get("/", test_handler)
2600 .build();
2601
2602 assert_eq!(app.pending_startup_hooks(), 2);
2603 assert_eq!(app.pending_shutdown_hooks(), 1);
2604 }
2605
2606 #[test]
2609 fn startup_hooks_run_in_fifo_order() {
2610 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2611
2612 let order1 = Arc::clone(&order);
2613 let order2 = Arc::clone(&order);
2614 let order3 = Arc::clone(&order);
2615
2616 let app = App::builder()
2617 .on_startup(move || {
2618 order1.lock().push(1);
2619 Ok(())
2620 })
2621 .on_startup(move || {
2622 order2.lock().push(2);
2623 Ok(())
2624 })
2625 .on_startup(move || {
2626 order3.lock().push(3);
2627 Ok(())
2628 })
2629 .get("/", test_handler)
2630 .build();
2631
2632 let outcome = futures_executor::block_on(app.run_startup_hooks());
2633 assert!(outcome.can_proceed());
2634
2635 assert_eq!(*order.lock(), vec![1, 2, 3]);
2637
2638 assert_eq!(app.pending_startup_hooks(), 0);
2640 }
2641
2642 #[test]
2645 fn shutdown_hooks_run_in_lifo_order() {
2646 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2647
2648 let order1 = Arc::clone(&order);
2649 let order2 = Arc::clone(&order);
2650 let order3 = Arc::clone(&order);
2651
2652 let app = App::builder()
2653 .on_shutdown(move || {
2654 order1.lock().push(1);
2655 })
2656 .on_shutdown(move || {
2657 order2.lock().push(2);
2658 })
2659 .on_shutdown(move || {
2660 order3.lock().push(3);
2661 })
2662 .get("/", test_handler)
2663 .build();
2664
2665 futures_executor::block_on(app.run_shutdown_hooks());
2666
2667 assert_eq!(*order.lock(), vec![3, 2, 1]);
2669
2670 assert_eq!(app.pending_shutdown_hooks(), 0);
2672 }
2673
2674 #[test]
2677 fn startup_hooks_success_outcome() {
2678 let app = App::builder()
2679 .on_startup(|| Ok(()))
2680 .on_startup(|| Ok(()))
2681 .get("/", test_handler)
2682 .build();
2683
2684 let outcome = futures_executor::block_on(app.run_startup_hooks());
2685 assert!(matches!(outcome, StartupOutcome::Success));
2686 assert!(outcome.can_proceed());
2687 }
2688
2689 #[test]
2692 fn startup_hooks_fatal_error_aborts() {
2693 let app = App::builder()
2694 .on_startup(|| Ok(()))
2695 .on_startup(|| Err(StartupHookError::new("database connection failed")))
2696 .on_startup(|| Ok(())) .get("/", test_handler)
2698 .build();
2699
2700 let outcome = futures_executor::block_on(app.run_startup_hooks());
2701 assert!(!outcome.can_proceed());
2702
2703 if let StartupOutcome::Aborted(err) = outcome {
2704 assert!(err.message.contains("database connection failed"));
2705 assert!(err.abort);
2706 } else {
2707 panic!("Expected Aborted outcome");
2708 }
2709 }
2710
2711 #[test]
2714 fn startup_hooks_non_fatal_error_continues() {
2715 let app = App::builder()
2716 .on_startup(|| Ok(()))
2717 .on_startup(|| Err(StartupHookError::non_fatal("optional feature unavailable")))
2718 .on_startup(|| Ok(())) .get("/", test_handler)
2720 .build();
2721
2722 let outcome = futures_executor::block_on(app.run_startup_hooks());
2723 assert!(outcome.can_proceed());
2724
2725 if let StartupOutcome::PartialSuccess { warnings } = outcome {
2726 assert_eq!(warnings, 1);
2727 } else {
2728 panic!("Expected PartialSuccess outcome");
2729 }
2730 }
2731
2732 #[test]
2735 fn startup_hook_error_builder() {
2736 let err = StartupHookError::new("test error")
2737 .with_hook_name("database_init")
2738 .with_abort(false);
2739
2740 assert_eq!(err.hook_name.as_deref(), Some("database_init"));
2741 assert_eq!(err.message, "test error");
2742 assert!(!err.abort);
2743 }
2744
2745 #[test]
2746 fn startup_hook_error_display() {
2747 let err = StartupHookError::new("connection failed").with_hook_name("redis_init");
2748
2749 let display = format!("{}", err);
2750 assert!(display.contains("redis_init"));
2751 assert!(display.contains("connection failed"));
2752 }
2753
2754 #[test]
2755 fn startup_hook_error_non_fatal() {
2756 let err = StartupHookError::non_fatal("optional feature");
2757 assert!(!err.abort);
2758 }
2759
2760 #[test]
2763 fn transfer_shutdown_hooks_to_controller() {
2764 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2765
2766 let order1 = Arc::clone(&order);
2767 let order2 = Arc::clone(&order);
2768
2769 let app = App::builder()
2770 .on_shutdown(move || {
2771 order1.lock().push(1);
2772 })
2773 .on_shutdown(move || {
2774 order2.lock().push(2);
2775 })
2776 .get("/", test_handler)
2777 .build();
2778
2779 let controller = ShutdownController::new();
2780 app.transfer_shutdown_hooks(&controller);
2781
2782 assert_eq!(app.pending_shutdown_hooks(), 0);
2784
2785 assert_eq!(controller.hook_count(), 2);
2787
2788 while let Some(hook) = controller.pop_hook() {
2790 hook.run();
2791 }
2792
2793 assert_eq!(*order.lock(), vec![2, 1]);
2795 }
2796
2797 #[test]
2800 fn app_debug_includes_hooks() {
2801 let app = App::builder()
2802 .on_startup(|| Ok(()))
2803 .on_shutdown(|| {})
2804 .get("/", test_handler)
2805 .build();
2806
2807 let debug = format!("{:?}", app);
2808 assert!(debug.contains("startup_hooks"));
2809 assert!(debug.contains("shutdown_hooks"));
2810 }
2811
2812 #[test]
2813 fn app_builder_debug_includes_hooks() {
2814 let builder = App::builder().on_startup(|| Ok(())).on_shutdown(|| {});
2815
2816 let debug = format!("{:?}", builder);
2817 assert!(debug.contains("startup_hooks"));
2818 assert!(debug.contains("shutdown_hooks"));
2819 }
2820
2821 #[test]
2824 fn startup_outcome_success() {
2825 let outcome = StartupOutcome::Success;
2826 assert!(outcome.can_proceed());
2827 assert!(outcome.into_error().is_none());
2828 }
2829
2830 #[test]
2831 fn startup_outcome_partial_success() {
2832 let outcome = StartupOutcome::PartialSuccess { warnings: 2 };
2833 assert!(outcome.can_proceed());
2834 assert!(outcome.into_error().is_none());
2835 }
2836
2837 #[test]
2838 fn startup_outcome_aborted() {
2839 let err = StartupHookError::new("fatal");
2840 let outcome = StartupOutcome::Aborted(err);
2841 assert!(!outcome.can_proceed());
2842
2843 let err = outcome.into_error();
2844 assert!(err.is_some());
2845 assert_eq!(err.unwrap().message, "fatal");
2846 }
2847
2848 #[test]
2851 fn startup_hooks_multiple_non_fatal_errors() {
2852 let app = App::builder()
2853 .on_startup(|| Err(StartupHookError::non_fatal("warning 1")))
2854 .on_startup(|| Ok(()))
2855 .on_startup(|| Err(StartupHookError::non_fatal("warning 2")))
2856 .on_startup(|| Err(StartupHookError::non_fatal("warning 3")))
2857 .get("/", test_handler)
2858 .build();
2859
2860 let outcome = futures_executor::block_on(app.run_startup_hooks());
2861 assert!(outcome.can_proceed());
2862
2863 if let StartupOutcome::PartialSuccess { warnings } = outcome {
2864 assert_eq!(warnings, 3);
2865 } else {
2866 panic!("Expected PartialSuccess");
2867 }
2868 }
2869
2870 #[test]
2873 fn empty_startup_hooks() {
2874 let app = App::builder().get("/", test_handler).build();
2875
2876 let outcome = futures_executor::block_on(app.run_startup_hooks());
2877 assert!(matches!(outcome, StartupOutcome::Success));
2878 }
2879
2880 #[test]
2881 fn empty_shutdown_hooks() {
2882 let app = App::builder().get("/", test_handler).build();
2883
2884 futures_executor::block_on(app.run_shutdown_hooks());
2886 }
2887
2888 #[test]
2891 fn startup_hooks_consumed_after_run() {
2892 let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2893 let counter_clone = Arc::clone(&counter);
2894
2895 let app = App::builder()
2896 .on_startup(move || {
2897 counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2898 Ok(())
2899 })
2900 .get("/", test_handler)
2901 .build();
2902
2903 futures_executor::block_on(app.run_startup_hooks());
2905 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2906
2907 futures_executor::block_on(app.run_startup_hooks());
2909 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2910 }
2911
2912 #[test]
2913 fn shutdown_hooks_consumed_after_run() {
2914 let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2915 let counter_clone = Arc::clone(&counter);
2916
2917 let app = App::builder()
2918 .on_shutdown(move || {
2919 counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2920 })
2921 .get("/", test_handler)
2922 .build();
2923
2924 futures_executor::block_on(app.run_shutdown_hooks());
2926 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2927
2928 futures_executor::block_on(app.run_shutdown_hooks());
2930 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2931 }
2932
2933 #[test]
2934 fn root_path_strips_trailing_slashes() {
2935 let config = AppConfig::new().root_path("/api/");
2936 assert_eq!(config.root_path, "/api");
2937
2938 let config = AppConfig::new().root_path("/api///");
2939 assert_eq!(config.root_path, "/api");
2940
2941 let config = AppConfig::new().root_path("/api");
2942 assert_eq!(config.root_path, "/api");
2943
2944 let config = AppConfig::new().root_path("");
2946 assert_eq!(config.root_path, "");
2947 }
2948}