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<
190 dyn Fn(
191 &RequestContext,
192 &mut Request,
193 ) -> std::pin::Pin<Box<dyn Future<Output = Response> + Send>>
194 + Send
195 + Sync,
196>;
197
198pub type BoxWebSocketHandler = Box<
200 dyn Fn(
201 &RequestContext,
202 &mut Request,
203 crate::websocket::WebSocket,
204 ) -> std::pin::Pin<
205 Box<dyn Future<Output = Result<(), crate::websocket::WebSocketError>> + Send>,
206 > + Send
207 + Sync,
208>;
209
210#[derive(Clone)]
212pub struct RouteEntry {
213 pub method: Method,
215 pub path: String,
217 meta: Option<fastapi_router::Route>,
222 handler: Arc<BoxHandler>,
224}
225
226impl RouteEntry {
227 pub fn new<H, Fut>(method: Method, path: impl Into<String>, handler: H) -> Self
233 where
234 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
235 Fut: Future<Output = Response> + Send + 'static,
236 {
237 let handler: BoxHandler = Box::new(move |ctx, req| {
238 let fut = handler(ctx, req);
239 Box::pin(fut)
240 });
241 Self {
242 method,
243 path: path.into(),
244 meta: None,
245 handler: Arc::new(handler),
246 }
247 }
248
249 pub fn from_route<H, Fut>(route: fastapi_router::Route, handler: H) -> Self
254 where
255 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
256 Fut: Future<Output = Response> + Send + 'static,
257 {
258 let method = route.method;
259 let path = route.path.clone();
260 let mut entry = Self::new(method, path, handler);
261 entry.meta = Some(route);
262 entry
263 }
264
265 pub fn route_meta(&self) -> Option<&fastapi_router::Route> {
267 self.meta.as_ref()
268 }
269
270 pub async fn call(&self, ctx: &RequestContext, req: &mut Request) -> Response {
272 (self.handler)(ctx, req).await
273 }
274}
275
276impl std::fmt::Debug for RouteEntry {
277 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278 f.debug_struct("RouteEntry")
279 .field("method", &self.method)
280 .field("path", &self.path)
281 .field("meta", &self.meta.as_ref().map(|r| r.operation_id.as_str()))
282 .finish_non_exhaustive()
283 }
284}
285
286#[derive(Clone)]
288pub struct WebSocketRouteEntry {
289 pub path: String,
291 handler: Arc<BoxWebSocketHandler>,
293}
294
295impl WebSocketRouteEntry {
296 pub fn new<H, Fut>(path: impl Into<String>, handler: H) -> Self
298 where
299 H: Fn(&RequestContext, &mut Request, crate::websocket::WebSocket) -> Fut
300 + Send
301 + Sync
302 + 'static,
303 Fut: Future<Output = Result<(), crate::websocket::WebSocketError>> + Send + 'static,
304 {
305 let handler: BoxWebSocketHandler = Box::new(move |ctx, req, ws| {
306 let fut = handler(ctx, req, ws);
307 Box::pin(fut)
308 });
309 Self {
310 path: path.into(),
311 handler: Arc::new(handler),
312 }
313 }
314
315 pub async fn call(
317 &self,
318 ctx: &RequestContext,
319 req: &mut Request,
320 ws: crate::websocket::WebSocket,
321 ) -> Result<(), crate::websocket::WebSocketError> {
322 (self.handler)(ctx, req, ws).await
323 }
324}
325
326impl std::fmt::Debug for WebSocketRouteEntry {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 f.debug_struct("WebSocketRouteEntry")
329 .field("path", &self.path)
330 .finish_non_exhaustive()
331 }
332}
333
334#[derive(Default)]
339pub struct StateContainer {
340 state: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
341}
342
343impl StateContainer {
344 #[must_use]
346 pub fn new() -> Self {
347 Self {
348 state: HashMap::new(),
349 }
350 }
351
352 pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
356 self.state.insert(TypeId::of::<T>(), Arc::new(value));
357 }
358
359 pub fn get<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
361 self.state
362 .get(&TypeId::of::<T>())
363 .and_then(|v| Arc::clone(v).downcast::<T>().ok())
364 }
365
366 pub fn contains<T: 'static>(&self) -> bool {
368 self.state.contains_key(&TypeId::of::<T>())
369 }
370
371 pub fn len(&self) -> usize {
373 self.state.len()
374 }
375
376 pub fn is_empty(&self) -> bool {
378 self.state.is_empty()
379 }
380}
381
382impl std::fmt::Debug for StateContainer {
383 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384 f.debug_struct("StateContainer")
385 .field("count", &self.state.len())
386 .finish()
387 }
388}
389
390pub type BoxExceptionHandler = Box<
398 dyn Fn(&RequestContext, Box<dyn std::error::Error + Send + Sync>) -> Response + Send + Sync,
399>;
400
401#[derive(Default)]
436pub struct ExceptionHandlers {
437 handlers: HashMap<TypeId, BoxExceptionHandler>,
438}
439
440impl ExceptionHandlers {
441 #[must_use]
443 pub fn new() -> Self {
444 Self {
445 handlers: HashMap::new(),
446 }
447 }
448
449 #[must_use]
451 pub fn with_defaults() -> Self {
452 let mut handlers = Self::new();
453
454 handlers.register::<crate::HttpError>(|_ctx, err| {
456 use crate::IntoResponse;
457 err.into_response()
458 });
459
460 handlers.register::<crate::ValidationErrors>(|_ctx, err| {
462 use crate::IntoResponse;
463 err.into_response()
464 });
465
466 handlers
467 }
468
469 pub fn register<E>(
474 &mut self,
475 handler: impl Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
476 ) where
477 E: std::error::Error + Send + Sync + 'static,
478 {
479 let boxed_handler: BoxExceptionHandler = Box::new(move |ctx, err| {
480 match err.downcast::<E>() {
482 Ok(typed_err) => handler(ctx, *typed_err),
483 Err(_) => {
484 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
486 }
487 }
488 });
489 self.handlers.insert(TypeId::of::<E>(), boxed_handler);
490 }
491
492 #[must_use]
494 pub fn handler<E>(
495 mut self,
496 handler: impl Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
497 ) -> Self
498 where
499 E: std::error::Error + Send + Sync + 'static,
500 {
501 self.register::<E>(handler);
502 self
503 }
504
505 pub fn handle<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
510 where
511 E: std::error::Error + Send + Sync + 'static,
512 {
513 let type_id = TypeId::of::<E>();
514 self.handlers
515 .get(&type_id)
516 .map(|handler| handler(ctx, Box::new(err)))
517 }
518
519 pub fn handle_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
521 where
522 E: std::error::Error + Send + Sync + 'static,
523 {
524 self.handle(ctx, err)
525 .unwrap_or_else(|| Response::with_status(StatusCode::INTERNAL_SERVER_ERROR))
526 }
527
528 pub fn has_handler<E: 'static>(&self) -> bool {
530 self.handlers.contains_key(&TypeId::of::<E>())
531 }
532
533 pub fn len(&self) -> usize {
535 self.handlers.len()
536 }
537
538 pub fn is_empty(&self) -> bool {
540 self.handlers.is_empty()
541 }
542
543 pub fn merge(&mut self, other: ExceptionHandlers) {
547 self.handlers.extend(other.handlers);
548 }
549}
550
551impl std::fmt::Debug for ExceptionHandlers {
552 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 f.debug_struct("ExceptionHandlers")
554 .field("count", &self.handlers.len())
555 .finish()
556 }
557}
558
559#[derive(Debug, Clone)]
561pub struct AppConfig {
562 pub name: String,
564 pub version: String,
566 pub debug: bool,
568 pub root_path: String,
572 pub root_path_in_servers: bool,
574 pub trailing_slash_mode: crate::routing::TrailingSlashMode,
576 pub debug_config: crate::error::DebugConfig,
578 pub max_body_size: usize,
580 pub request_timeout_ms: u64,
582}
583
584impl Default for AppConfig {
585 fn default() -> Self {
586 Self {
587 name: String::from("fastapi_rust"),
588 version: String::from("0.1.0"),
589 debug: false,
590 root_path: String::new(),
591 root_path_in_servers: false,
592 trailing_slash_mode: crate::routing::TrailingSlashMode::Strict,
593 debug_config: crate::error::DebugConfig::default(),
594 max_body_size: 1024 * 1024, request_timeout_ms: 30_000, }
597 }
598}
599
600impl AppConfig {
601 #[must_use]
603 pub fn new() -> Self {
604 Self::default()
605 }
606
607 #[must_use]
609 pub fn name(mut self, name: impl Into<String>) -> Self {
610 self.name = name.into();
611 self
612 }
613
614 #[must_use]
616 pub fn version(mut self, version: impl Into<String>) -> Self {
617 self.version = version.into();
618 self
619 }
620
621 #[must_use]
623 pub fn debug(mut self, debug: bool) -> Self {
624 self.debug = debug;
625 self
626 }
627
628 #[must_use]
634 pub fn root_path(mut self, root_path: impl Into<String>) -> Self {
635 let mut rp = root_path.into();
636 while rp.ends_with('/') {
638 rp.pop();
639 }
640 self.root_path = rp;
641 self
642 }
643
644 #[must_use]
646 pub fn root_path_in_servers(mut self, enabled: bool) -> Self {
647 self.root_path_in_servers = enabled;
648 self
649 }
650
651 #[must_use]
653 pub fn trailing_slash_mode(mut self, mode: crate::routing::TrailingSlashMode) -> Self {
654 self.trailing_slash_mode = mode;
655 self
656 }
657
658 #[must_use]
660 pub fn debug_config(mut self, config: crate::error::DebugConfig) -> Self {
661 self.debug_config = config;
662 self
663 }
664
665 #[must_use]
667 pub fn max_body_size(mut self, size: usize) -> Self {
668 self.max_body_size = size;
669 self
670 }
671
672 #[must_use]
674 pub fn request_timeout_ms(mut self, timeout: u64) -> Self {
675 self.request_timeout_ms = timeout;
676 self
677 }
678}
679
680#[derive(Debug, Clone)]
702pub struct OpenApiConfig {
703 pub enabled: bool,
705 pub title: String,
707 pub version: String,
709 pub description: Option<String>,
711 pub openapi_path: String,
713 pub servers: Vec<(String, Option<String>)>,
715 pub tags: Vec<(String, Option<String>)>,
717}
718
719impl Default for OpenApiConfig {
720 fn default() -> Self {
721 Self {
722 enabled: true,
723 title: "FastAPI Rust".to_string(),
724 version: "0.1.0".to_string(),
725 description: None,
726 openapi_path: "/openapi.json".to_string(),
727 servers: Vec::new(),
728 tags: Vec::new(),
729 }
730 }
731}
732
733impl OpenApiConfig {
734 #[must_use]
736 pub fn new() -> Self {
737 Self::default()
738 }
739
740 #[must_use]
742 pub fn title(mut self, title: impl Into<String>) -> Self {
743 self.title = title.into();
744 self
745 }
746
747 #[must_use]
749 pub fn version(mut self, version: impl Into<String>) -> Self {
750 self.version = version.into();
751 self
752 }
753
754 #[must_use]
756 pub fn description(mut self, description: impl Into<String>) -> Self {
757 self.description = Some(description.into());
758 self
759 }
760
761 #[must_use]
763 pub fn path(mut self, path: impl Into<String>) -> Self {
764 self.openapi_path = path.into();
765 self
766 }
767
768 #[must_use]
770 pub fn server(mut self, url: impl Into<String>, description: Option<String>) -> Self {
771 self.servers.push((url.into(), description));
772 self
773 }
774
775 #[must_use]
777 pub fn tag(mut self, name: impl Into<String>, description: Option<String>) -> Self {
778 self.tags.push((name.into(), description));
779 self
780 }
781
782 #[must_use]
784 pub fn disable(mut self) -> Self {
785 self.enabled = false;
786 self
787 }
788}
789
790pub struct AppBuilder {
816 config: AppConfig,
817 routes: Vec<RouteEntry>,
818 ws_routes: Vec<WebSocketRouteEntry>,
819 middleware: Vec<Arc<dyn Middleware>>,
820 state: StateContainer,
821 exception_handlers: ExceptionHandlers,
822 startup_hooks: Vec<StartupHook>,
823 shutdown_hooks: Vec<Box<dyn FnOnce() + Send>>,
824 async_shutdown_hooks: Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
825 openapi_config: Option<OpenApiConfig>,
826 docs_config: Option<crate::docs::DocsConfig>,
827}
828
829impl Default for AppBuilder {
830 fn default() -> Self {
831 Self {
832 config: AppConfig::default(),
833 routes: Vec::new(),
834 ws_routes: Vec::new(),
835 middleware: Vec::new(),
836 state: StateContainer::default(),
837 exception_handlers: ExceptionHandlers::default(),
838 startup_hooks: Vec::new(),
839 shutdown_hooks: Vec::new(),
840 async_shutdown_hooks: Vec::new(),
841 openapi_config: None,
842 docs_config: None,
843 }
844 }
845}
846
847impl AppBuilder {
848 #[must_use]
850 pub fn new() -> Self {
851 Self::default()
852 }
853
854 #[must_use]
856 pub fn config(mut self, config: AppConfig) -> Self {
857 self.config = config;
858 self
859 }
860
861 #[must_use]
877 pub fn openapi(mut self, config: OpenApiConfig) -> Self {
878 self.openapi_config = Some(config);
879 self
880 }
881
882 #[must_use]
891 pub fn enable_docs(mut self, mut config: crate::docs::DocsConfig) -> Self {
892 if config.title == crate::docs::DocsConfig::default().title {
894 config.title.clone_from(&self.config.name);
895 }
896
897 match self.openapi_config.take() {
899 Some(mut openapi) => {
900 openapi.openapi_path.clone_from(&config.openapi_path);
901 self.openapi_config = Some(openapi);
902 }
903 None => {
904 self.openapi_config = Some(
905 OpenApiConfig::new()
906 .title(self.config.name.clone())
907 .version(self.config.version.clone())
908 .path(config.openapi_path.clone()),
909 );
910 }
911 }
912
913 self.docs_config = Some(config);
914 self
915 }
916
917 #[must_use]
921 pub fn route<H, Fut>(mut self, path: impl Into<String>, method: Method, handler: H) -> Self
922 where
923 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
924 Fut: Future<Output = Response> + Send + 'static,
925 {
926 self.routes.push(RouteEntry::new(method, path, handler));
927 self
928 }
929
930 #[must_use]
935 pub fn route_entry(mut self, entry: RouteEntry) -> Self {
936 self.routes.push(entry);
937 self
938 }
939
940 #[must_use]
945 pub fn websocket<H, Fut>(mut self, path: impl Into<String>, handler: H) -> Self
946 where
947 H: Fn(&RequestContext, &mut Request, crate::websocket::WebSocket) -> Fut
948 + Send
949 + Sync
950 + 'static,
951 Fut: Future<Output = Result<(), crate::websocket::WebSocketError>> + Send + 'static,
952 {
953 self.ws_routes.push(WebSocketRouteEntry::new(path, handler));
954 self
955 }
956
957 #[must_use]
959 pub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
960 where
961 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
962 Fut: Future<Output = Response> + Send + 'static,
963 {
964 self.route(path, Method::Get, handler)
965 }
966
967 #[must_use]
969 pub fn post<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
970 where
971 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
972 Fut: Future<Output = Response> + Send + 'static,
973 {
974 self.route(path, Method::Post, handler)
975 }
976
977 #[must_use]
979 pub fn put<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
980 where
981 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
982 Fut: Future<Output = Response> + Send + 'static,
983 {
984 self.route(path, Method::Put, handler)
985 }
986
987 #[must_use]
989 pub fn delete<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
990 where
991 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
992 Fut: Future<Output = Response> + Send + 'static,
993 {
994 self.route(path, Method::Delete, handler)
995 }
996
997 #[must_use]
999 pub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1000 where
1001 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1002 Fut: Future<Output = Response> + Send + 'static,
1003 {
1004 self.route(path, Method::Patch, handler)
1005 }
1006
1007 #[must_use]
1013 pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
1014 self.middleware.push(Arc::new(middleware));
1015 self
1016 }
1017
1018 #[must_use]
1022 pub fn state<T: Send + Sync + 'static>(mut self, state: T) -> Self {
1023 self.state.insert(state);
1024 self
1025 }
1026
1027 #[must_use]
1055 pub fn exception_handler<E, H>(mut self, handler: H) -> Self
1056 where
1057 E: std::error::Error + Send + Sync + 'static,
1058 H: Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
1059 {
1060 self.exception_handlers.register::<E>(handler);
1061 self
1062 }
1063
1064 #[must_use]
1068 pub fn exception_handlers(mut self, handlers: ExceptionHandlers) -> Self {
1069 self.exception_handlers = handlers;
1070 self
1071 }
1072
1073 #[must_use]
1079 pub fn with_default_exception_handlers(mut self) -> Self {
1080 self.exception_handlers = ExceptionHandlers::with_defaults();
1081 self
1082 }
1083
1084 #[must_use]
1108 pub fn on_startup<F>(mut self, hook: F) -> Self
1109 where
1110 F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,
1111 {
1112 self.startup_hooks.push(StartupHook::Sync(Box::new(hook)));
1113 self
1114 }
1115
1116 #[must_use]
1131 pub fn on_startup_async<F, Fut>(mut self, hook: F) -> Self
1132 where
1133 F: FnOnce() -> Fut + Send + 'static,
1134 Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,
1135 {
1136 self.startup_hooks.push(StartupHook::AsyncFactory(Box::new(
1137 move || Box::pin(hook()),
1138 )));
1139 self
1140 }
1141
1142 #[must_use]
1160 pub fn on_shutdown<F>(mut self, hook: F) -> Self
1161 where
1162 F: FnOnce() + Send + 'static,
1163 {
1164 self.shutdown_hooks.push(Box::new(hook));
1165 self
1166 }
1167
1168 #[must_use]
1182 pub fn on_shutdown_async<F, Fut>(mut self, hook: F) -> Self
1183 where
1184 F: FnOnce() -> Fut + Send + 'static,
1185 Fut: Future<Output = ()> + Send + 'static,
1186 {
1187 self.async_shutdown_hooks
1188 .push(Box::new(move || Box::pin(hook())));
1189 self
1190 }
1191
1192 #[must_use]
1194 pub fn startup_hook_count(&self) -> usize {
1195 self.startup_hooks.len()
1196 }
1197
1198 #[must_use]
1200 pub fn shutdown_hook_count(&self) -> usize {
1201 self.shutdown_hooks.len() + self.async_shutdown_hooks.len()
1202 }
1203
1204 #[must_use]
1212 #[allow(clippy::too_many_lines)]
1213 pub fn build(mut self) -> App {
1214 let (openapi_spec, openapi_path) = if let Some(ref openapi_config) = self.openapi_config {
1216 if openapi_config.enabled {
1217 let spec = self.generate_openapi_spec(openapi_config);
1218 let spec_json =
1219 serde_json::to_string_pretty(&spec).unwrap_or_else(|_| "{}".to_string());
1220 (
1221 Some(Arc::new(spec_json)),
1222 Some(openapi_config.openapi_path.clone()),
1223 )
1224 } else {
1225 (None, None)
1226 }
1227 } else {
1228 (None, None)
1229 };
1230
1231 if let (Some(spec), Some(path)) = (&openapi_spec, &openapi_path) {
1233 let spec_clone = Arc::clone(spec);
1234 self.routes.push(RouteEntry::new(
1235 Method::Get,
1236 path.clone(),
1237 move |_ctx: &RequestContext, _req: &mut Request| {
1238 let spec = Arc::clone(&spec_clone);
1239 async move {
1240 Response::ok()
1241 .header("content-type", b"application/json".to_vec())
1242 .body(crate::response::ResponseBody::Bytes(
1243 spec.as_bytes().to_vec(),
1244 ))
1245 }
1246 },
1247 ));
1248 }
1249
1250 if let (Some(openapi_url), Some(docs_config)) = (openapi_path.clone(), self.docs_config) {
1254 let docs_config = Arc::new(docs_config);
1255 let openapi_url = Arc::new(openapi_url);
1256
1257 if let Some(docs_path) = docs_config.docs_path.clone() {
1258 let cfg = Arc::clone(&docs_config);
1259 let url = Arc::clone(&openapi_url);
1260 self.routes.push(RouteEntry::new(
1261 Method::Get,
1262 docs_path.clone(),
1263 move |_ctx: &RequestContext, _req: &mut Request| {
1264 let cfg = Arc::clone(&cfg);
1265 let url = Arc::clone(&url);
1266 async move { crate::docs::swagger_ui_response(&cfg, &url) }
1267 },
1268 ));
1269
1270 let docs_prefix = docs_path.trim_end_matches('/');
1272 let oauth2_redirect_path = if docs_prefix.is_empty() {
1273 "/oauth2-redirect".to_string()
1274 } else {
1275 format!("{docs_prefix}/oauth2-redirect")
1276 };
1277 self.routes.push(RouteEntry::new(
1278 Method::Get,
1279 oauth2_redirect_path,
1280 |_ctx: &RequestContext, _req: &mut Request| async move {
1281 crate::docs::oauth2_redirect_response()
1282 },
1283 ));
1284 }
1285
1286 if let Some(redoc_path) = docs_config.redoc_path.clone() {
1287 let cfg = Arc::clone(&docs_config);
1288 let url = Arc::clone(&openapi_url);
1289 self.routes.push(RouteEntry::new(
1290 Method::Get,
1291 redoc_path,
1292 move |_ctx: &RequestContext, _req: &mut Request| {
1293 let cfg = Arc::clone(&cfg);
1294 let url = Arc::clone(&url);
1295 async move { crate::docs::redoc_response(&cfg, &url) }
1296 },
1297 ));
1298 }
1299 }
1300
1301 let mut middleware_stack = MiddlewareStack::with_capacity(self.middleware.len());
1302 for mw in self.middleware {
1303 middleware_stack.push_arc(mw);
1304 }
1305
1306 let mut router = Router::new();
1308 for entry in &self.routes {
1309 let route = entry
1310 .route_meta()
1311 .cloned()
1312 .unwrap_or_else(|| Route::new(entry.method, &entry.path));
1313 router
1314 .add(route)
1315 .expect("route conflict during App::build()");
1316 }
1317
1318 let mut ws_router = Router::new();
1320 for entry in &self.ws_routes {
1321 ws_router
1322 .add(Route::new(Method::Get, &entry.path))
1323 .expect("websocket route conflict during App::build()");
1324 }
1325
1326 App {
1327 config: self.config,
1328 routes: self.routes,
1329 ws_routes: self.ws_routes,
1330 router,
1331 ws_router,
1332 middleware: middleware_stack,
1333 state: Arc::new(self.state),
1334 exception_handlers: Arc::new(self.exception_handlers),
1335 dependency_overrides: Arc::new(crate::dependency::DependencyOverrides::new()),
1336 startup_hooks: parking_lot::Mutex::new(self.startup_hooks),
1337 shutdown_hooks: parking_lot::Mutex::new(self.shutdown_hooks),
1338 async_shutdown_hooks: parking_lot::Mutex::new(self.async_shutdown_hooks),
1339 openapi_spec,
1340 }
1341 }
1342
1343 fn generate_openapi_spec(&self, config: &OpenApiConfig) -> fastapi_openapi::OpenApi {
1345 use fastapi_openapi::{OpenApiBuilder, Operation, Response as OAResponse};
1346 use std::collections::HashMap;
1347
1348 let mut builder = OpenApiBuilder::new(&config.title, &config.version);
1349
1350 if let Some(ref desc) = config.description {
1352 builder = builder.description(desc);
1353 }
1354
1355 for (url, desc) in &config.servers {
1357 builder = builder.server(url, desc.clone());
1358 }
1359
1360 for (name, desc) in &config.tags {
1362 builder = builder.tag(name, desc.clone());
1363 }
1364
1365 for entry in &self.routes {
1367 if let Some(route) = entry.route_meta() {
1368 builder.add_route(route);
1369 continue;
1370 }
1371
1372 let mut responses = HashMap::new();
1374 responses.insert(
1375 "200".to_string(),
1376 OAResponse {
1377 description: "Successful response".to_string(),
1378 content: HashMap::new(),
1379 },
1380 );
1381
1382 let operation = Operation {
1383 operation_id: Some(format!(
1384 "{}_{}",
1385 entry.method.as_str().to_lowercase(),
1386 entry
1387 .path
1388 .replace('/', "_")
1389 .replace(['{', '}'], "")
1390 .trim_matches('_')
1391 )),
1392 summary: None,
1393 description: None,
1394 tags: Vec::new(),
1395 parameters: Vec::new(),
1396 request_body: None,
1397 responses,
1398 deprecated: false,
1399 };
1400
1401 builder = builder.operation(entry.method.as_str(), &entry.path, operation);
1402 }
1403
1404 builder.build()
1405 }
1406}
1407
1408impl std::fmt::Debug for AppBuilder {
1409 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1410 f.debug_struct("AppBuilder")
1411 .field("config", &self.config)
1412 .field("routes", &self.routes.len())
1413 .field("middleware", &self.middleware.len())
1414 .field("state", &self.state)
1415 .field("exception_handlers", &self.exception_handlers)
1416 .field("startup_hooks", &self.startup_hooks.len())
1417 .field("shutdown_hooks", &self.shutdown_hook_count())
1418 .finish()
1419 }
1420}
1421
1422pub struct App {
1427 config: AppConfig,
1428 routes: Vec<RouteEntry>,
1429 ws_routes: Vec<WebSocketRouteEntry>,
1430 router: Router,
1431 ws_router: Router,
1432 middleware: MiddlewareStack,
1433 state: Arc<StateContainer>,
1434 exception_handlers: Arc<ExceptionHandlers>,
1435 dependency_overrides: Arc<crate::dependency::DependencyOverrides>,
1436 startup_hooks: parking_lot::Mutex<Vec<StartupHook>>,
1437 shutdown_hooks: parking_lot::Mutex<Vec<Box<dyn FnOnce() + Send>>>,
1438 async_shutdown_hooks: parking_lot::Mutex<
1439 Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
1440 >,
1441 openapi_spec: Option<Arc<String>>,
1443}
1444
1445impl App {
1446 #[must_use]
1448 pub fn builder() -> AppBuilder {
1449 AppBuilder::new()
1450 }
1451
1452 #[cfg(feature = "testing")]
1457 #[must_use]
1458 pub fn test_client(self: Arc<Self>) -> crate::testing::TestClient<Arc<Self>> {
1459 crate::testing::TestClient::new(self)
1460 }
1461
1462 #[cfg(feature = "testing")]
1464 #[must_use]
1465 pub fn test_client_with_seed(
1466 self: Arc<Self>,
1467 seed: u64,
1468 ) -> crate::testing::TestClient<Arc<Self>> {
1469 crate::testing::TestClient::with_seed(self, seed)
1470 }
1471
1472 #[must_use]
1474 pub fn config(&self) -> &AppConfig {
1475 &self.config
1476 }
1477
1478 #[must_use]
1480 pub fn route_count(&self) -> usize {
1481 self.routes.len()
1482 }
1483
1484 #[must_use]
1486 pub fn websocket_route_count(&self) -> usize {
1487 self.ws_routes.len()
1488 }
1489
1490 #[must_use]
1492 pub fn has_websocket_route(&self, path: &str) -> bool {
1493 matches!(
1494 self.ws_router.lookup(path, Method::Get),
1495 RouteLookup::Match(_)
1496 )
1497 }
1498
1499 pub fn routes(&self) -> impl Iterator<Item = (Method, &str)> {
1503 self.routes.iter().map(|r| (r.method, r.path.as_str()))
1504 }
1505
1506 #[must_use]
1520 pub fn openapi_spec(&self) -> Option<&str> {
1521 self.openapi_spec.as_ref().map(|s| s.as_str())
1522 }
1523
1524 #[must_use]
1526 pub fn state(&self) -> &Arc<StateContainer> {
1527 &self.state
1528 }
1529
1530 pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
1532 self.state.get::<T>()
1533 }
1534
1535 #[must_use]
1537 pub fn exception_handlers(&self) -> &Arc<ExceptionHandlers> {
1538 &self.exception_handlers
1539 }
1540
1541 pub fn override_dependency_value<T>(&self, value: T)
1546 where
1547 T: crate::dependency::FromDependency,
1548 {
1549 self.dependency_overrides.insert_value(value);
1550 }
1551
1552 pub fn clear_dependency_overrides(&self) {
1554 self.dependency_overrides.clear();
1555 }
1556
1557 #[must_use]
1559 pub fn dependency_overrides(&self) -> Arc<crate::dependency::DependencyOverrides> {
1560 Arc::clone(&self.dependency_overrides)
1561 }
1562
1563 pub fn take_background_tasks(req: &mut Request) -> Option<crate::request::BackgroundTasks> {
1568 req.take_extension::<crate::request::BackgroundTasks>()
1569 }
1570
1571 pub fn handle_error<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
1576 where
1577 E: std::error::Error + Send + Sync + 'static,
1578 {
1579 self.exception_handlers.handle(ctx, err)
1580 }
1581
1582 pub fn handle_error_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
1584 where
1585 E: std::error::Error + Send + Sync + 'static,
1586 {
1587 self.exception_handlers.handle_or_default(ctx, err)
1588 }
1589
1590 pub async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response {
1595 match self.router.lookup(req.path(), req.method()) {
1597 RouteLookup::Match(route_match) => {
1598 let entry = self.routes.iter().find(|e| {
1600 e.method == route_match.route.method && e.path == route_match.route.path
1601 });
1602
1603 let Some(entry) = entry else {
1604 return Response::with_status(StatusCode::INTERNAL_SERVER_ERROR);
1606 };
1607
1608 if !route_match.params.is_empty() {
1610 let path_params = crate::extract::PathParams::from_pairs(
1611 route_match
1612 .params
1613 .iter()
1614 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
1615 .collect(),
1616 );
1617 req.insert_extension(path_params);
1618 }
1619
1620 let handler = RouteHandler { entry };
1622 self.middleware.execute(&handler, ctx, req).await
1623 }
1624 RouteLookup::MethodNotAllowed { allowed } => {
1625 if req.method() == Method::Options {
1628 let mut methods = allowed.methods().to_vec();
1629 if !methods.contains(&Method::Options) {
1630 methods.push(Method::Options);
1631 }
1632 let allow = fastapi_router::AllowedMethods::new(methods);
1633 Response::with_status(StatusCode::NO_CONTENT)
1634 .header("allow", allow.header_value().as_bytes().to_vec())
1635 } else {
1636 Response::with_status(StatusCode::METHOD_NOT_ALLOWED)
1637 .header("allow", allowed.header_value().as_bytes().to_vec())
1638 }
1639 }
1640 RouteLookup::NotFound => Response::with_status(StatusCode::NOT_FOUND),
1641 }
1642 }
1643
1644 pub async fn handle_websocket(
1649 &self,
1650 ctx: &RequestContext,
1651 req: &mut Request,
1652 ws: crate::websocket::WebSocket,
1653 ) -> Result<(), crate::websocket::WebSocketError> {
1654 match self.ws_router.lookup(req.path(), Method::Get) {
1655 RouteLookup::Match(route_match) => {
1656 let entry = self
1657 .ws_routes
1658 .iter()
1659 .find(|e| e.path == route_match.route.path);
1660 let Some(entry) = entry else {
1661 return Err(crate::websocket::WebSocketError::Protocol(
1662 "websocket route missing handler",
1663 ));
1664 };
1665
1666 if !route_match.params.is_empty() {
1667 let path_params = crate::extract::PathParams::from_pairs(
1668 route_match
1669 .params
1670 .iter()
1671 .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
1672 .collect(),
1673 );
1674 req.insert_extension(path_params);
1675 }
1676
1677 entry.call(ctx, req, ws).await
1678 }
1679 _ => Err(crate::websocket::WebSocketError::Protocol(
1680 "no websocket route matched",
1681 )),
1682 }
1683 }
1684
1685 pub async fn run_startup_hooks(&self) -> StartupOutcome {
1702 let hooks: Vec<StartupHook> = std::mem::take(&mut *self.startup_hooks.lock());
1703 let mut warnings = 0;
1704
1705 for hook in hooks {
1706 match hook.run() {
1707 Ok(None) => {
1708 }
1710 Ok(Some(fut)) => {
1711 match fut.await {
1713 Ok(()) => {}
1714 Err(e) if e.abort => {
1715 return StartupOutcome::Aborted(e);
1716 }
1717 Err(_) => {
1718 warnings += 1;
1719 }
1720 }
1721 }
1722 Err(e) if e.abort => {
1723 return StartupOutcome::Aborted(e);
1724 }
1725 Err(_) => {
1726 warnings += 1;
1727 }
1728 }
1729 }
1730
1731 if warnings > 0 {
1732 StartupOutcome::PartialSuccess { warnings }
1733 } else {
1734 StartupOutcome::Success
1735 }
1736 }
1737
1738 pub async fn run_shutdown_hooks(&self) {
1745 let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
1747 for hook in async_hooks.into_iter().rev() {
1748 let fut = hook();
1749 fut.await;
1750 }
1751
1752 let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
1754 for hook in sync_hooks.into_iter().rev() {
1755 hook();
1756 }
1757 }
1758
1759 pub fn transfer_shutdown_hooks(&self, controller: &ShutdownController) {
1766 let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
1768 for hook in sync_hooks {
1769 controller.register_hook(hook);
1770 }
1771
1772 let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
1774 for hook in async_hooks {
1775 controller.register_async_hook(move || hook());
1776 }
1777 }
1778
1779 #[must_use]
1781 pub fn pending_startup_hooks(&self) -> usize {
1782 self.startup_hooks.lock().len()
1783 }
1784
1785 #[must_use]
1787 pub fn pending_shutdown_hooks(&self) -> usize {
1788 self.shutdown_hooks.lock().len() + self.async_shutdown_hooks.lock().len()
1789 }
1790}
1791
1792impl std::fmt::Debug for App {
1793 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1794 f.debug_struct("App")
1795 .field("config", &self.config)
1796 .field("routes", &self.routes.len())
1797 .field("middleware", &self.middleware.len())
1798 .field("state", &self.state)
1799 .field("exception_handlers", &self.exception_handlers)
1800 .field("startup_hooks", &self.startup_hooks.lock().len())
1801 .field("shutdown_hooks", &self.pending_shutdown_hooks())
1802 .finish()
1803 }
1804}
1805
1806impl Handler for App {
1808 fn call<'a>(
1809 &'a self,
1810 ctx: &'a RequestContext,
1811 req: &'a mut Request,
1812 ) -> BoxFuture<'a, Response> {
1813 Box::pin(async move { self.handle(ctx, req).await })
1814 }
1815
1816 fn dependency_overrides(&self) -> Option<Arc<crate::dependency::DependencyOverrides>> {
1817 Some(Arc::clone(&self.dependency_overrides))
1818 }
1819}
1820
1821struct RouteHandler<'a> {
1823 entry: &'a RouteEntry,
1824}
1825
1826impl<'a> Handler for RouteHandler<'a> {
1827 fn call<'b>(
1828 &'b self,
1829 ctx: &'b RequestContext,
1830 req: &'b mut Request,
1831 ) -> BoxFuture<'b, Response> {
1832 let handler = self.entry.handler.clone();
1833 Box::pin(async move { handler(ctx, req).await })
1834 }
1835}
1836
1837#[cfg(test)]
1838mod tests {
1839 use super::*;
1840
1841 use crate::response::ResponseBody;
1842
1843 fn test_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
1845 std::future::ready(Response::ok().body(ResponseBody::Bytes(b"Hello, World!".to_vec())))
1846 }
1847
1848 fn health_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
1849 std::future::ready(Response::ok().body(ResponseBody::Bytes(b"OK".to_vec())))
1850 }
1851
1852 fn test_context() -> RequestContext {
1853 let cx = asupersync::Cx::for_testing();
1854 RequestContext::new(cx, 1)
1855 }
1856
1857 #[test]
1858 fn app_builder_creates_app() {
1859 let app = App::builder()
1860 .config(AppConfig::new().name("Test App"))
1861 .get("/", test_handler)
1862 .get("/health", health_handler)
1863 .build();
1864
1865 assert_eq!(app.route_count(), 2);
1866 assert_eq!(app.config().name, "Test App");
1867 }
1868
1869 #[test]
1870 fn app_config_builder() {
1871 let config = AppConfig::new()
1872 .name("My API")
1873 .version("1.0.0")
1874 .debug(true)
1875 .max_body_size(2 * 1024 * 1024)
1876 .request_timeout_ms(60_000);
1877
1878 assert_eq!(config.name, "My API");
1879 assert_eq!(config.version, "1.0.0");
1880 assert!(config.debug);
1881 assert_eq!(config.max_body_size, 2 * 1024 * 1024);
1882 assert_eq!(config.request_timeout_ms, 60_000);
1883 }
1884
1885 #[test]
1886 fn state_container_insert_and_get() {
1887 #[derive(Debug, PartialEq)]
1888 struct MyState {
1889 value: i32,
1890 }
1891
1892 let mut container = StateContainer::new();
1893 container.insert(MyState { value: 42 });
1894
1895 let state = container.get::<MyState>();
1896 assert!(state.is_some());
1897 assert_eq!(state.unwrap().value, 42);
1898 }
1899
1900 #[test]
1901 fn state_container_multiple_types() {
1902 struct TypeA(i32);
1903 struct TypeB(String);
1904
1905 let mut container = StateContainer::new();
1906 container.insert(TypeA(1));
1907 container.insert(TypeB("hello".to_string()));
1908
1909 assert!(container.contains::<TypeA>());
1910 assert!(container.contains::<TypeB>());
1911 assert!(!container.contains::<i64>());
1912
1913 assert_eq!(container.get::<TypeA>().unwrap().0, 1);
1914 assert_eq!(container.get::<TypeB>().unwrap().0, "hello");
1915 }
1916
1917 #[test]
1918 fn app_builder_with_state() {
1919 struct DbPool {
1920 connection_count: usize,
1921 }
1922
1923 let app = App::builder()
1924 .state(DbPool {
1925 connection_count: 10,
1926 })
1927 .get("/", test_handler)
1928 .build();
1929
1930 let pool = app.get_state::<DbPool>();
1931 assert!(pool.is_some());
1932 assert_eq!(pool.unwrap().connection_count, 10);
1933 }
1934
1935 #[test]
1936 fn app_handles_get_request() {
1937 let app = App::builder().get("/", test_handler).build();
1938
1939 let ctx = test_context();
1940 let mut req = Request::new(Method::Get, "/");
1941
1942 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1943 assert_eq!(response.status().as_u16(), 200);
1944 }
1945
1946 #[test]
1947 fn app_returns_404_for_unknown_path() {
1948 let app = App::builder().get("/", test_handler).build();
1949
1950 let ctx = test_context();
1951 let mut req = Request::new(Method::Get, "/unknown");
1952
1953 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1954 assert_eq!(response.status().as_u16(), 404);
1955 }
1956
1957 #[test]
1958 fn app_returns_405_for_wrong_method() {
1959 let app = App::builder().get("/", test_handler).build();
1960
1961 let ctx = test_context();
1962 let mut req = Request::new(Method::Post, "/");
1963
1964 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1965 assert_eq!(response.status().as_u16(), 405);
1966 }
1967
1968 #[test]
1969 fn app_builder_all_methods() {
1970 let app = App::builder()
1971 .get("/get", test_handler)
1972 .post("/post", test_handler)
1973 .put("/put", test_handler)
1974 .delete("/delete", test_handler)
1975 .patch("/patch", test_handler)
1976 .build();
1977
1978 assert_eq!(app.route_count(), 5);
1979 }
1980
1981 #[test]
1982 fn route_entry_debug() {
1983 let entry = RouteEntry::new(Method::Get, "/test", test_handler);
1984 let debug = format!("{:?}", entry);
1985 assert!(debug.contains("RouteEntry"));
1986 assert!(debug.contains("Get"));
1987 assert!(debug.contains("/test"));
1988 }
1989
1990 #[test]
1991 fn app_with_middleware() {
1992 use crate::middleware::NoopMiddleware;
1993
1994 let app = App::builder()
1995 .middleware(NoopMiddleware)
1996 .middleware(NoopMiddleware)
1997 .get("/", test_handler)
1998 .build();
1999
2000 let ctx = test_context();
2001 let mut req = Request::new(Method::Get, "/");
2002
2003 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
2004 assert_eq!(response.status().as_u16(), 200);
2005 }
2006
2007 #[derive(Debug)]
2013 struct TestError {
2014 message: String,
2015 code: u32,
2016 }
2017
2018 impl std::fmt::Display for TestError {
2019 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2020 write!(f, "TestError({}): {}", self.code, self.message)
2021 }
2022 }
2023
2024 impl std::error::Error for TestError {}
2025
2026 #[derive(Debug)]
2028 struct AnotherError(String);
2029
2030 impl std::fmt::Display for AnotherError {
2031 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2032 write!(f, "AnotherError: {}", self.0)
2033 }
2034 }
2035
2036 impl std::error::Error for AnotherError {}
2037
2038 #[test]
2041 fn exception_handlers_new_is_empty() {
2042 let handlers = ExceptionHandlers::new();
2043 assert!(handlers.is_empty());
2044 assert_eq!(handlers.len(), 0);
2045 }
2046
2047 #[test]
2048 fn exception_handlers_register_single() {
2049 let mut handlers = ExceptionHandlers::new();
2050 handlers.register::<TestError>(|_ctx, err| {
2051 Response::with_status(StatusCode::BAD_REQUEST)
2052 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2053 });
2054
2055 assert!(handlers.has_handler::<TestError>());
2056 assert!(!handlers.has_handler::<AnotherError>());
2057 assert_eq!(handlers.len(), 1);
2058 }
2059
2060 #[test]
2061 fn exception_handlers_register_multiple() {
2062 let mut handlers = ExceptionHandlers::new();
2063 handlers.register::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2064 handlers.register::<AnotherError>(|_ctx, _err| {
2065 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2066 });
2067
2068 assert!(handlers.has_handler::<TestError>());
2069 assert!(handlers.has_handler::<AnotherError>());
2070 assert_eq!(handlers.len(), 2);
2071 }
2072
2073 #[test]
2074 fn exception_handlers_builder_pattern() {
2075 let handlers = ExceptionHandlers::new()
2076 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2077 .handler::<AnotherError>(|_ctx, _err| {
2078 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2079 });
2080
2081 assert!(handlers.has_handler::<TestError>());
2082 assert!(handlers.has_handler::<AnotherError>());
2083 assert_eq!(handlers.len(), 2);
2084 }
2085
2086 #[test]
2087 fn exception_handlers_with_defaults() {
2088 let handlers = ExceptionHandlers::with_defaults();
2089
2090 assert!(handlers.has_handler::<crate::HttpError>());
2091 assert!(handlers.has_handler::<crate::ValidationErrors>());
2092 assert_eq!(handlers.len(), 2);
2093 }
2094
2095 #[test]
2096 fn exception_handlers_merge() {
2097 let mut handlers1 = ExceptionHandlers::new()
2098 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2099
2100 let handlers2 = ExceptionHandlers::new().handler::<AnotherError>(|_ctx, _err| {
2101 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2102 });
2103
2104 handlers1.merge(handlers2);
2105
2106 assert!(handlers1.has_handler::<TestError>());
2107 assert!(handlers1.has_handler::<AnotherError>());
2108 assert_eq!(handlers1.len(), 2);
2109 }
2110
2111 #[test]
2114 fn exception_handlers_handle_registered_error() {
2115 let handlers = ExceptionHandlers::new().handler::<TestError>(|_ctx, err| {
2116 Response::with_status(StatusCode::BAD_REQUEST)
2117 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2118 });
2119
2120 let ctx = test_context();
2121 let err = TestError {
2122 message: "test error".into(),
2123 code: 42,
2124 };
2125
2126 let response = handlers.handle(&ctx, err);
2127 assert!(response.is_some());
2128
2129 let response = response.unwrap();
2130 assert_eq!(response.status().as_u16(), 400);
2131 }
2132
2133 #[test]
2134 fn exception_handlers_handle_unregistered_error() {
2135 let handlers = ExceptionHandlers::new()
2136 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2137
2138 let ctx = test_context();
2139 let err = AnotherError("unhandled".into());
2140
2141 let response = handlers.handle(&ctx, err);
2142 assert!(response.is_none());
2143 }
2144
2145 #[test]
2146 fn exception_handlers_handle_or_default_registered() {
2147 let handlers = ExceptionHandlers::new()
2148 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2149
2150 let ctx = test_context();
2151 let err = TestError {
2152 message: "test".into(),
2153 code: 1,
2154 };
2155
2156 let response = handlers.handle_or_default(&ctx, err);
2157 assert_eq!(response.status().as_u16(), 400);
2158 }
2159
2160 #[test]
2161 fn exception_handlers_handle_or_default_unregistered() {
2162 let handlers = ExceptionHandlers::new();
2163
2164 let ctx = test_context();
2165 let err = TestError {
2166 message: "test".into(),
2167 code: 1,
2168 };
2169
2170 let response = handlers.handle_or_default(&ctx, err);
2171 assert_eq!(response.status().as_u16(), 500);
2172 }
2173
2174 #[test]
2175 fn exception_handlers_error_values_passed_to_handler() {
2176 use std::sync::atomic::{AtomicU32, Ordering};
2177
2178 let captured_code = Arc::new(AtomicU32::new(0));
2179 let captured_code_clone = captured_code.clone();
2180
2181 let handlers = ExceptionHandlers::new().handler::<TestError>(move |_ctx, err| {
2182 captured_code_clone.store(err.code, Ordering::SeqCst);
2183 Response::with_status(StatusCode::BAD_REQUEST)
2184 });
2185
2186 let ctx = test_context();
2187 let err = TestError {
2188 message: "test".into(),
2189 code: 12345,
2190 };
2191
2192 let _ = handlers.handle(&ctx, err);
2193 assert_eq!(captured_code.load(Ordering::SeqCst), 12345);
2194 }
2195
2196 #[test]
2199 fn app_builder_exception_handler_single() {
2200 let app = App::builder()
2201 .exception_handler::<TestError, _>(|_ctx, err| {
2202 Response::with_status(StatusCode::BAD_REQUEST)
2203 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2204 })
2205 .get("/", test_handler)
2206 .build();
2207
2208 assert!(app.exception_handlers().has_handler::<TestError>());
2209 }
2210
2211 #[test]
2212 fn app_builder_exception_handler_multiple() {
2213 let app = App::builder()
2214 .exception_handler::<TestError, _>(|_ctx, _err| {
2215 Response::with_status(StatusCode::BAD_REQUEST)
2216 })
2217 .exception_handler::<AnotherError, _>(|_ctx, _err| {
2218 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2219 })
2220 .get("/", test_handler)
2221 .build();
2222
2223 assert!(app.exception_handlers().has_handler::<TestError>());
2224 assert!(app.exception_handlers().has_handler::<AnotherError>());
2225 }
2226
2227 #[test]
2228 fn app_builder_with_default_exception_handlers() {
2229 let app = App::builder()
2230 .with_default_exception_handlers()
2231 .get("/", test_handler)
2232 .build();
2233
2234 assert!(app.exception_handlers().has_handler::<crate::HttpError>());
2235 assert!(
2236 app.exception_handlers()
2237 .has_handler::<crate::ValidationErrors>()
2238 );
2239 }
2240
2241 #[test]
2242 fn app_builder_exception_handlers_registry() {
2243 let handlers = ExceptionHandlers::new()
2244 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2245 .handler::<AnotherError>(|_ctx, _err| {
2246 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2247 });
2248
2249 let app = App::builder()
2250 .exception_handlers(handlers)
2251 .get("/", test_handler)
2252 .build();
2253
2254 assert!(app.exception_handlers().has_handler::<TestError>());
2255 assert!(app.exception_handlers().has_handler::<AnotherError>());
2256 }
2257
2258 #[test]
2259 fn app_handle_error_registered() {
2260 let app = App::builder()
2261 .exception_handler::<TestError, _>(|_ctx, _err| {
2262 Response::with_status(StatusCode::BAD_REQUEST)
2263 })
2264 .get("/", test_handler)
2265 .build();
2266
2267 let ctx = test_context();
2268 let err = TestError {
2269 message: "test".into(),
2270 code: 1,
2271 };
2272
2273 let response = app.handle_error(&ctx, err);
2274 assert!(response.is_some());
2275 assert_eq!(response.unwrap().status().as_u16(), 400);
2276 }
2277
2278 #[test]
2279 fn app_handle_error_unregistered() {
2280 let app = App::builder().get("/", test_handler).build();
2281
2282 let ctx = test_context();
2283 let err = TestError {
2284 message: "test".into(),
2285 code: 1,
2286 };
2287
2288 let response = app.handle_error(&ctx, err);
2289 assert!(response.is_none());
2290 }
2291
2292 #[test]
2293 fn app_handle_error_or_default() {
2294 let app = App::builder().get("/", test_handler).build();
2295
2296 let ctx = test_context();
2297 let err = TestError {
2298 message: "test".into(),
2299 code: 1,
2300 };
2301
2302 let response = app.handle_error_or_default(&ctx, err);
2303 assert_eq!(response.status().as_u16(), 500);
2304 }
2305
2306 #[test]
2309 fn exception_handlers_override_on_register() {
2310 let handlers = ExceptionHandlers::new()
2311 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2312 .handler::<TestError>(|_ctx, _err| {
2313 Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
2314 });
2315
2316 assert_eq!(handlers.len(), 1);
2318
2319 let ctx = test_context();
2320 let err = TestError {
2321 message: "test".into(),
2322 code: 1,
2323 };
2324
2325 let response = handlers.handle(&ctx, err);
2327 assert!(response.is_some());
2328 assert_eq!(response.unwrap().status().as_u16(), 422);
2329 }
2330
2331 #[test]
2332 fn exception_handlers_merge_overrides() {
2333 let mut handlers1 = ExceptionHandlers::new()
2334 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2335
2336 let handlers2 = ExceptionHandlers::new().handler::<TestError>(|_ctx, _err| {
2337 Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
2338 });
2339
2340 handlers1.merge(handlers2);
2341
2342 assert_eq!(handlers1.len(), 1);
2344
2345 let ctx = test_context();
2346 let err = TestError {
2347 message: "test".into(),
2348 code: 1,
2349 };
2350
2351 let response = handlers1.handle(&ctx, err);
2353 assert!(response.is_some());
2354 assert_eq!(response.unwrap().status().as_u16(), 422);
2355 }
2356
2357 #[test]
2358 fn exception_handlers_override_default_http_error() {
2359 let mut handlers = ExceptionHandlers::with_defaults();
2361
2362 handlers.register::<crate::HttpError>(|_ctx, err| {
2364 let detail = err.detail.as_deref().unwrap_or("Unknown error");
2366 Response::with_status(err.status)
2367 .header("x-custom-error", b"true".to_vec())
2368 .body(ResponseBody::Bytes(detail.as_bytes().to_vec()))
2369 });
2370
2371 assert_eq!(handlers.len(), 2);
2373
2374 let ctx = test_context();
2375 let err = crate::HttpError::bad_request().with_detail("test error");
2376
2377 let response = handlers.handle(&ctx, err);
2378 assert!(response.is_some());
2379
2380 let response = response.unwrap();
2381 assert_eq!(response.status().as_u16(), 400);
2382
2383 let custom_header = response
2385 .headers()
2386 .iter()
2387 .find(|(name, _)| name.eq_ignore_ascii_case("x-custom-error"))
2388 .map(|(_, v)| v.as_slice());
2389 assert_eq!(custom_header, Some(b"true".as_slice()));
2390 }
2391
2392 #[test]
2393 fn exception_handlers_override_default_validation_errors() {
2394 let mut handlers = ExceptionHandlers::with_defaults();
2396
2397 handlers.register::<crate::ValidationErrors>(|_ctx, errs| {
2399 Response::with_status(StatusCode::BAD_REQUEST)
2401 .header("x-error-count", errs.len().to_string().as_bytes().to_vec())
2402 });
2403
2404 let ctx = test_context();
2405 let mut errs = crate::ValidationErrors::new();
2406 errs.push(crate::ValidationError::missing(
2407 crate::error::loc::body_field("name"),
2408 ));
2409 errs.push(crate::ValidationError::missing(
2410 crate::error::loc::body_field("email"),
2411 ));
2412
2413 let response = handlers.handle(&ctx, errs);
2414 assert!(response.is_some());
2415
2416 let response = response.unwrap();
2417 assert_eq!(response.status().as_u16(), 400);
2419
2420 let count_header = response
2422 .headers()
2423 .iter()
2424 .find(|(name, _)| name.eq_ignore_ascii_case("x-error-count"))
2425 .map(|(_, v)| v.as_slice());
2426 assert_eq!(count_header, Some(b"2".as_slice()));
2427 }
2428
2429 #[test]
2430 fn exception_handlers_debug_format() {
2431 let handlers = ExceptionHandlers::new()
2432 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2433
2434 let debug = format!("{:?}", handlers);
2435 assert!(debug.contains("ExceptionHandlers"));
2436 assert!(debug.contains("count"));
2437 assert!(debug.contains("1"));
2438 }
2439
2440 #[test]
2441 fn app_debug_includes_exception_handlers() {
2442 let app = App::builder()
2443 .exception_handler::<TestError, _>(|_ctx, _err| {
2444 Response::with_status(StatusCode::BAD_REQUEST)
2445 })
2446 .get("/", test_handler)
2447 .build();
2448
2449 let debug = format!("{:?}", app);
2450 assert!(debug.contains("exception_handlers"));
2451 }
2452
2453 #[test]
2454 fn app_builder_debug_includes_exception_handlers() {
2455 let builder = App::builder().exception_handler::<TestError, _>(|_ctx, _err| {
2456 Response::with_status(StatusCode::BAD_REQUEST)
2457 });
2458
2459 let debug = format!("{:?}", builder);
2460 assert!(debug.contains("exception_handlers"));
2461 }
2462
2463 #[test]
2470 fn app_builder_startup_hook_registration() {
2471 let builder = App::builder().on_startup(|| Ok(())).on_startup(|| Ok(()));
2472
2473 assert_eq!(builder.startup_hook_count(), 2);
2474 }
2475
2476 #[test]
2477 fn app_builder_shutdown_hook_registration() {
2478 let builder = App::builder().on_shutdown(|| {}).on_shutdown(|| {});
2479
2480 assert_eq!(builder.shutdown_hook_count(), 2);
2481 }
2482
2483 #[test]
2484 fn app_builder_mixed_hooks() {
2485 let builder = App::builder()
2486 .on_startup(|| Ok(()))
2487 .on_shutdown(|| {})
2488 .on_startup(|| Ok(()))
2489 .on_shutdown(|| {});
2490
2491 assert_eq!(builder.startup_hook_count(), 2);
2492 assert_eq!(builder.shutdown_hook_count(), 2);
2493 }
2494
2495 #[test]
2496 fn app_pending_hooks_count() {
2497 let app = App::builder()
2498 .on_startup(|| Ok(()))
2499 .on_startup(|| Ok(()))
2500 .on_shutdown(|| {})
2501 .get("/", test_handler)
2502 .build();
2503
2504 assert_eq!(app.pending_startup_hooks(), 2);
2505 assert_eq!(app.pending_shutdown_hooks(), 1);
2506 }
2507
2508 #[test]
2511 fn startup_hooks_run_in_fifo_order() {
2512 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2513
2514 let order1 = Arc::clone(&order);
2515 let order2 = Arc::clone(&order);
2516 let order3 = Arc::clone(&order);
2517
2518 let app = App::builder()
2519 .on_startup(move || {
2520 order1.lock().push(1);
2521 Ok(())
2522 })
2523 .on_startup(move || {
2524 order2.lock().push(2);
2525 Ok(())
2526 })
2527 .on_startup(move || {
2528 order3.lock().push(3);
2529 Ok(())
2530 })
2531 .get("/", test_handler)
2532 .build();
2533
2534 let outcome = futures_executor::block_on(app.run_startup_hooks());
2535 assert!(outcome.can_proceed());
2536
2537 assert_eq!(*order.lock(), vec![1, 2, 3]);
2539
2540 assert_eq!(app.pending_startup_hooks(), 0);
2542 }
2543
2544 #[test]
2547 fn shutdown_hooks_run_in_lifo_order() {
2548 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2549
2550 let order1 = Arc::clone(&order);
2551 let order2 = Arc::clone(&order);
2552 let order3 = Arc::clone(&order);
2553
2554 let app = App::builder()
2555 .on_shutdown(move || {
2556 order1.lock().push(1);
2557 })
2558 .on_shutdown(move || {
2559 order2.lock().push(2);
2560 })
2561 .on_shutdown(move || {
2562 order3.lock().push(3);
2563 })
2564 .get("/", test_handler)
2565 .build();
2566
2567 futures_executor::block_on(app.run_shutdown_hooks());
2568
2569 assert_eq!(*order.lock(), vec![3, 2, 1]);
2571
2572 assert_eq!(app.pending_shutdown_hooks(), 0);
2574 }
2575
2576 #[test]
2579 fn startup_hooks_success_outcome() {
2580 let app = App::builder()
2581 .on_startup(|| Ok(()))
2582 .on_startup(|| Ok(()))
2583 .get("/", test_handler)
2584 .build();
2585
2586 let outcome = futures_executor::block_on(app.run_startup_hooks());
2587 assert!(matches!(outcome, StartupOutcome::Success));
2588 assert!(outcome.can_proceed());
2589 }
2590
2591 #[test]
2594 fn startup_hooks_fatal_error_aborts() {
2595 let app = App::builder()
2596 .on_startup(|| Ok(()))
2597 .on_startup(|| Err(StartupHookError::new("database connection failed")))
2598 .on_startup(|| Ok(())) .get("/", test_handler)
2600 .build();
2601
2602 let outcome = futures_executor::block_on(app.run_startup_hooks());
2603 assert!(!outcome.can_proceed());
2604
2605 if let StartupOutcome::Aborted(err) = outcome {
2606 assert!(err.message.contains("database connection failed"));
2607 assert!(err.abort);
2608 } else {
2609 panic!("Expected Aborted outcome");
2610 }
2611 }
2612
2613 #[test]
2616 fn startup_hooks_non_fatal_error_continues() {
2617 let app = App::builder()
2618 .on_startup(|| Ok(()))
2619 .on_startup(|| Err(StartupHookError::non_fatal("optional feature unavailable")))
2620 .on_startup(|| Ok(())) .get("/", test_handler)
2622 .build();
2623
2624 let outcome = futures_executor::block_on(app.run_startup_hooks());
2625 assert!(outcome.can_proceed());
2626
2627 if let StartupOutcome::PartialSuccess { warnings } = outcome {
2628 assert_eq!(warnings, 1);
2629 } else {
2630 panic!("Expected PartialSuccess outcome");
2631 }
2632 }
2633
2634 #[test]
2637 fn startup_hook_error_builder() {
2638 let err = StartupHookError::new("test error")
2639 .with_hook_name("database_init")
2640 .with_abort(false);
2641
2642 assert_eq!(err.hook_name.as_deref(), Some("database_init"));
2643 assert_eq!(err.message, "test error");
2644 assert!(!err.abort);
2645 }
2646
2647 #[test]
2648 fn startup_hook_error_display() {
2649 let err = StartupHookError::new("connection failed").with_hook_name("redis_init");
2650
2651 let display = format!("{}", err);
2652 assert!(display.contains("redis_init"));
2653 assert!(display.contains("connection failed"));
2654 }
2655
2656 #[test]
2657 fn startup_hook_error_non_fatal() {
2658 let err = StartupHookError::non_fatal("optional feature");
2659 assert!(!err.abort);
2660 }
2661
2662 #[test]
2665 fn transfer_shutdown_hooks_to_controller() {
2666 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2667
2668 let order1 = Arc::clone(&order);
2669 let order2 = Arc::clone(&order);
2670
2671 let app = App::builder()
2672 .on_shutdown(move || {
2673 order1.lock().push(1);
2674 })
2675 .on_shutdown(move || {
2676 order2.lock().push(2);
2677 })
2678 .get("/", test_handler)
2679 .build();
2680
2681 let controller = ShutdownController::new();
2682 app.transfer_shutdown_hooks(&controller);
2683
2684 assert_eq!(app.pending_shutdown_hooks(), 0);
2686
2687 assert_eq!(controller.hook_count(), 2);
2689
2690 while let Some(hook) = controller.pop_hook() {
2692 hook.run();
2693 }
2694
2695 assert_eq!(*order.lock(), vec![2, 1]);
2697 }
2698
2699 #[test]
2702 fn app_debug_includes_hooks() {
2703 let app = App::builder()
2704 .on_startup(|| Ok(()))
2705 .on_shutdown(|| {})
2706 .get("/", test_handler)
2707 .build();
2708
2709 let debug = format!("{:?}", app);
2710 assert!(debug.contains("startup_hooks"));
2711 assert!(debug.contains("shutdown_hooks"));
2712 }
2713
2714 #[test]
2715 fn app_builder_debug_includes_hooks() {
2716 let builder = App::builder().on_startup(|| Ok(())).on_shutdown(|| {});
2717
2718 let debug = format!("{:?}", builder);
2719 assert!(debug.contains("startup_hooks"));
2720 assert!(debug.contains("shutdown_hooks"));
2721 }
2722
2723 #[test]
2726 fn startup_outcome_success() {
2727 let outcome = StartupOutcome::Success;
2728 assert!(outcome.can_proceed());
2729 assert!(outcome.into_error().is_none());
2730 }
2731
2732 #[test]
2733 fn startup_outcome_partial_success() {
2734 let outcome = StartupOutcome::PartialSuccess { warnings: 2 };
2735 assert!(outcome.can_proceed());
2736 assert!(outcome.into_error().is_none());
2737 }
2738
2739 #[test]
2740 fn startup_outcome_aborted() {
2741 let err = StartupHookError::new("fatal");
2742 let outcome = StartupOutcome::Aborted(err);
2743 assert!(!outcome.can_proceed());
2744
2745 let err = outcome.into_error();
2746 assert!(err.is_some());
2747 assert_eq!(err.unwrap().message, "fatal");
2748 }
2749
2750 #[test]
2753 fn startup_hooks_multiple_non_fatal_errors() {
2754 let app = App::builder()
2755 .on_startup(|| Err(StartupHookError::non_fatal("warning 1")))
2756 .on_startup(|| Ok(()))
2757 .on_startup(|| Err(StartupHookError::non_fatal("warning 2")))
2758 .on_startup(|| Err(StartupHookError::non_fatal("warning 3")))
2759 .get("/", test_handler)
2760 .build();
2761
2762 let outcome = futures_executor::block_on(app.run_startup_hooks());
2763 assert!(outcome.can_proceed());
2764
2765 if let StartupOutcome::PartialSuccess { warnings } = outcome {
2766 assert_eq!(warnings, 3);
2767 } else {
2768 panic!("Expected PartialSuccess");
2769 }
2770 }
2771
2772 #[test]
2775 fn empty_startup_hooks() {
2776 let app = App::builder().get("/", test_handler).build();
2777
2778 let outcome = futures_executor::block_on(app.run_startup_hooks());
2779 assert!(matches!(outcome, StartupOutcome::Success));
2780 }
2781
2782 #[test]
2783 fn empty_shutdown_hooks() {
2784 let app = App::builder().get("/", test_handler).build();
2785
2786 futures_executor::block_on(app.run_shutdown_hooks());
2788 }
2789
2790 #[test]
2793 fn startup_hooks_consumed_after_run() {
2794 let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2795 let counter_clone = Arc::clone(&counter);
2796
2797 let app = App::builder()
2798 .on_startup(move || {
2799 counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2800 Ok(())
2801 })
2802 .get("/", test_handler)
2803 .build();
2804
2805 futures_executor::block_on(app.run_startup_hooks());
2807 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2808
2809 futures_executor::block_on(app.run_startup_hooks());
2811 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2812 }
2813
2814 #[test]
2815 fn shutdown_hooks_consumed_after_run() {
2816 let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2817 let counter_clone = Arc::clone(&counter);
2818
2819 let app = App::builder()
2820 .on_shutdown(move || {
2821 counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2822 })
2823 .get("/", test_handler)
2824 .build();
2825
2826 futures_executor::block_on(app.run_shutdown_hooks());
2828 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2829
2830 futures_executor::block_on(app.run_shutdown_hooks());
2832 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2833 }
2834
2835 #[test]
2836 fn root_path_strips_trailing_slashes() {
2837 let config = AppConfig::new().root_path("/api/");
2838 assert_eq!(config.root_path, "/api");
2839
2840 let config = AppConfig::new().root_path("/api///");
2841 assert_eq!(config.root_path, "/api");
2842
2843 let config = AppConfig::new().root_path("/api");
2844 assert_eq!(config.root_path, "/api");
2845
2846 let config = AppConfig::new().root_path("");
2848 assert_eq!(config.root_path, "");
2849 }
2850}