1use std::any::{Any, TypeId};
36use std::collections::HashMap;
37use std::env;
38use std::future::Future;
39use std::marker::PhantomData;
40use std::path::{Path, PathBuf};
41use std::pin::Pin;
42use std::sync::Arc;
43use std::{fs, io};
44
45use crate::context::RequestContext;
46use crate::dependency::{DependencyOverrides, FromDependency};
47use crate::extract::PathParams;
48use crate::middleware::{BoxFuture, Handler, Middleware, MiddlewareStack};
49use crate::request::{Method, Request};
50use crate::response::{Response, StatusCode};
51use crate::routing::{RouteLookup, RouteTable, format_allow_header, method_order};
52use crate::shutdown::ShutdownController;
53use serde::Deserialize;
54
55pub trait StateRegistry: Send + Sync + 'static {}
68
69impl StateRegistry for () {}
70impl<T: Send + Sync + 'static, S: StateRegistry> StateRegistry for (T, S) {}
71
72pub trait HasState<T>: StateRegistry {}
90
91impl<T: Send + Sync + 'static, S: StateRegistry> HasState<T> for (T, S) {}
93
94pub trait RequiresState<S: StateRegistry> {}
112
113impl<S: StateRegistry> RequiresState<S> for () {}
115
116pub enum StartupHook {
122 Sync(Box<dyn FnOnce() -> Result<(), StartupHookError> + Send>),
124 AsyncFactory(
126 Box<
127 dyn FnOnce() -> Pin<Box<dyn Future<Output = Result<(), StartupHookError>> + Send>>
128 + Send,
129 >,
130 ),
131}
132
133impl StartupHook {
134 pub fn sync<F>(f: F) -> Self
136 where
137 F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,
138 {
139 Self::Sync(Box::new(f))
140 }
141
142 pub fn async_fn<F, Fut>(f: F) -> Self
144 where
145 F: FnOnce() -> Fut + Send + 'static,
146 Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,
147 {
148 Self::AsyncFactory(Box::new(move || Box::pin(f())))
149 }
150
151 pub fn run(
155 self,
156 ) -> Result<
157 Option<Pin<Box<dyn Future<Output = Result<(), StartupHookError>> + Send>>>,
158 StartupHookError,
159 > {
160 match self {
161 Self::Sync(f) => f().map(|()| None),
162 Self::AsyncFactory(f) => Ok(Some(f())),
163 }
164 }
165}
166
167#[derive(Debug)]
169pub struct StartupHookError {
170 pub hook_name: Option<String>,
172 pub message: String,
174 pub abort: bool,
176}
177
178impl StartupHookError {
179 pub fn new(message: impl Into<String>) -> Self {
181 Self {
182 hook_name: None,
183 message: message.into(),
184 abort: true,
185 }
186 }
187
188 #[must_use]
190 pub fn with_hook_name(mut self, name: impl Into<String>) -> Self {
191 self.hook_name = Some(name.into());
192 self
193 }
194
195 #[must_use]
197 pub fn with_abort(mut self, abort: bool) -> Self {
198 self.abort = abort;
199 self
200 }
201
202 pub fn non_fatal(message: impl Into<String>) -> Self {
204 Self {
205 hook_name: None,
206 message: message.into(),
207 abort: false,
208 }
209 }
210}
211
212impl std::fmt::Display for StartupHookError {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 if let Some(name) = &self.hook_name {
215 write!(f, "Startup hook '{}' failed: {}", name, self.message)
216 } else {
217 write!(f, "Startup hook failed: {}", self.message)
218 }
219 }
220}
221
222impl std::error::Error for StartupHookError {}
223
224#[derive(Debug)]
226pub enum StartupOutcome {
227 Success,
229 PartialSuccess {
231 warnings: usize,
233 },
234 Aborted(StartupHookError),
236}
237
238impl StartupOutcome {
239 #[must_use]
241 pub fn can_proceed(&self) -> bool {
242 !matches!(self, Self::Aborted(_))
243 }
244
245 pub fn into_error(self) -> Option<StartupHookError> {
247 match self {
248 Self::Aborted(e) => Some(e),
249 _ => None,
250 }
251 }
252}
253
254#[derive(Debug)]
263pub struct LifespanError {
264 pub message: String,
266 pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
268}
269
270impl LifespanError {
271 pub fn new(message: impl Into<String>) -> Self {
273 Self {
274 message: message.into(),
275 source: None,
276 }
277 }
278
279 pub fn with_source<E: std::error::Error + Send + Sync + 'static>(
281 message: impl Into<String>,
282 source: E,
283 ) -> Self {
284 Self {
285 message: message.into(),
286 source: Some(Box::new(source)),
287 }
288 }
289}
290
291impl std::fmt::Display for LifespanError {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 write!(f, "lifespan error: {}", self.message)?;
294 if let Some(ref source) = self.source {
295 write!(f, " (caused by: {})", source)?;
296 }
297 Ok(())
298 }
299}
300
301impl std::error::Error for LifespanError {
302 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
303 self.source
304 .as_ref()
305 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
306 }
307}
308
309impl From<LifespanError> for StartupHookError {
310 fn from(err: LifespanError) -> Self {
311 StartupHookError::new(err.to_string())
312 }
313}
314
315pub struct LifespanScope<T: Send + Sync + 'static> {
352 pub state: T,
357
358 cleanup: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
360}
361
362impl<T: Send + Sync + 'static> LifespanScope<T> {
363 pub fn new(state: T) -> Self {
374 Self {
375 state,
376 cleanup: None,
377 }
378 }
379
380 #[must_use]
395 pub fn on_shutdown<F>(mut self, cleanup: F) -> Self
396 where
397 F: Future<Output = ()> + Send + 'static,
398 {
399 self.cleanup = Some(Box::pin(cleanup));
400 self
401 }
402
403 pub fn take_cleanup(&mut self) -> Option<Pin<Box<dyn Future<Output = ()> + Send>>> {
407 self.cleanup.take()
408 }
409}
410
411impl<T: Send + Sync + std::fmt::Debug + 'static> std::fmt::Debug for LifespanScope<T> {
412 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
413 f.debug_struct("LifespanScope")
414 .field("state", &self.state)
415 .field("has_cleanup", &self.cleanup.is_some())
416 .finish()
417 }
418}
419
420pub type BoxLifespanFn = Box<
425 dyn FnOnce() -> Pin<
426 Box<
427 dyn Future<
428 Output = Result<
429 (
430 Box<dyn std::any::Any + Send + Sync>,
431 Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
432 ),
433 LifespanError,
434 >,
435 > + Send,
436 >,
437 > + Send,
438>;
439
440pub type BoxHandler = Box<
445 dyn for<'a> Fn(&'a RequestContext, &'a mut Request) -> BoxFuture<'a, Response> + Send + Sync,
446>;
447
448#[derive(Clone)]
450pub struct RouteEntry {
451 pub method: Method,
453 pub path: String,
455 handler: Arc<BoxHandler>,
457}
458
459impl RouteEntry {
460 pub fn new<H>(method: Method, path: impl Into<String>, handler: H) -> Self
465 where
466 H: for<'a> Fn(&'a RequestContext, &'a mut Request) -> BoxFuture<'a, Response>
467 + Send
468 + Sync
469 + 'static,
470 {
471 let handler: BoxHandler = Box::new(move |ctx, req| handler(ctx, req));
472 Self {
473 method,
474 path: path.into(),
475 handler: Arc::new(handler),
476 }
477 }
478
479 pub async fn call(&self, ctx: &RequestContext, req: &mut Request) -> Response {
481 (self.handler)(ctx, req).await
482 }
483}
484
485impl std::fmt::Debug for RouteEntry {
486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
487 f.debug_struct("RouteEntry")
488 .field("method", &self.method)
489 .field("path", &self.path)
490 .finish_non_exhaustive()
491 }
492}
493
494#[derive(Default)]
499pub struct StateContainer {
500 state: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
501}
502
503impl StateContainer {
504 #[must_use]
506 pub fn new() -> Self {
507 Self {
508 state: HashMap::new(),
509 }
510 }
511
512 pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
516 self.state.insert(TypeId::of::<T>(), Arc::new(value));
517 }
518
519 pub fn insert_any(&mut self, value: Box<dyn Any + Send + Sync>) {
524 let type_id = (*value).type_id();
525 self.state.insert(type_id, Arc::from(value));
526 }
527
528 pub fn get<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
530 self.state
531 .get(&TypeId::of::<T>())
532 .and_then(|v| Arc::clone(v).downcast::<T>().ok())
533 }
534
535 pub fn contains<T: 'static>(&self) -> bool {
537 self.state.contains_key(&TypeId::of::<T>())
538 }
539
540 pub fn len(&self) -> usize {
542 self.state.len()
543 }
544
545 pub fn is_empty(&self) -> bool {
547 self.state.is_empty()
548 }
549}
550
551impl std::fmt::Debug for StateContainer {
552 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 f.debug_struct("StateContainer")
554 .field("count", &self.state.len())
555 .finish()
556 }
557}
558
559pub type BoxExceptionHandler = Box<
567 dyn Fn(&RequestContext, Box<dyn std::error::Error + Send + Sync>) -> Response + Send + Sync,
568>;
569
570pub type BoxPanicHandler = Box<dyn Fn(Option<&RequestContext>, &str) -> Response + Send + Sync>;
576
577pub struct ExceptionHandlers {
620 handlers: HashMap<TypeId, BoxExceptionHandler>,
621 panic_handler: Option<BoxPanicHandler>,
622}
623
624impl Default for ExceptionHandlers {
625 fn default() -> Self {
626 Self::new()
627 }
628}
629
630impl ExceptionHandlers {
631 #[must_use]
633 pub fn new() -> Self {
634 Self {
635 handlers: HashMap::new(),
636 panic_handler: None,
637 }
638 }
639
640 #[must_use]
642 pub fn with_defaults() -> Self {
643 let mut handlers = Self::new();
644
645 handlers.register::<crate::HttpError>(|_ctx, err| {
647 use crate::IntoResponse;
648 err.into_response()
649 });
650
651 handlers.register::<crate::ValidationErrors>(|_ctx, err| {
653 use crate::IntoResponse;
654 err.into_response()
655 });
656
657 handlers.register::<crate::CancelledError>(|_ctx, _err| {
659 Response::with_status(StatusCode::CLIENT_CLOSED_REQUEST)
660 });
661
662 handlers
663 }
664
665 pub fn register<E>(
670 &mut self,
671 handler: impl Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
672 ) where
673 E: std::error::Error + Send + Sync + 'static,
674 {
675 let boxed_handler: BoxExceptionHandler = Box::new(move |ctx, err| {
676 match err.downcast::<E>() {
678 Ok(typed_err) => handler(ctx, *typed_err),
679 Err(_) => {
680 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
682 }
683 }
684 });
685 self.handlers.insert(TypeId::of::<E>(), boxed_handler);
686 }
687
688 #[must_use]
690 pub fn handler<E>(
691 mut self,
692 handler: impl Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
693 ) -> Self
694 where
695 E: std::error::Error + Send + Sync + 'static,
696 {
697 self.register::<E>(handler);
698 self
699 }
700
701 pub fn handle<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
706 where
707 E: std::error::Error + Send + Sync + 'static,
708 {
709 let type_id = TypeId::of::<E>();
710 self.handlers
711 .get(&type_id)
712 .map(|handler| handler(ctx, Box::new(err)))
713 }
714
715 pub fn handle_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
717 where
718 E: std::error::Error + Send + Sync + 'static,
719 {
720 self.handle(ctx, err)
721 .unwrap_or_else(|| Response::with_status(StatusCode::INTERNAL_SERVER_ERROR))
722 }
723
724 pub fn has_handler<E: 'static>(&self) -> bool {
726 self.handlers.contains_key(&TypeId::of::<E>())
727 }
728
729 pub fn len(&self) -> usize {
731 self.handlers.len()
732 }
733
734 pub fn is_empty(&self) -> bool {
736 self.handlers.is_empty()
737 }
738
739 pub fn merge(&mut self, other: ExceptionHandlers) {
743 self.handlers.extend(other.handlers);
744 if other.panic_handler.is_some() {
746 self.panic_handler = other.panic_handler;
747 }
748 }
749
750 #[must_use]
777 pub fn panic_handler<F>(mut self, handler: F) -> Self
778 where
779 F: Fn(Option<&RequestContext>, &str) -> Response + Send + Sync + 'static,
780 {
781 self.panic_handler = Some(Box::new(handler));
782 self
783 }
784
785 pub fn set_panic_handler<F>(&mut self, handler: F)
787 where
788 F: Fn(Option<&RequestContext>, &str) -> Response + Send + Sync + 'static,
789 {
790 self.panic_handler = Some(Box::new(handler));
791 }
792
793 pub fn handle_panic(&self, ctx: Option<&RequestContext>, panic_info: &str) -> Response {
805 if let Some(handler) = &self.panic_handler {
806 handler(ctx, panic_info)
807 } else {
808 Self::default_panic_response()
809 }
810 }
811
812 #[must_use]
814 pub fn default_panic_response() -> Response {
815 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
816 }
817
818 #[must_use]
820 pub fn has_panic_handler(&self) -> bool {
821 self.panic_handler.is_some()
822 }
823
824 #[must_use]
828 pub fn extract_panic_message(payload: &(dyn std::any::Any + Send)) -> String {
829 if let Some(s) = payload.downcast_ref::<&str>() {
830 (*s).to_string()
831 } else if let Some(s) = payload.downcast_ref::<String>() {
832 s.clone()
833 } else {
834 "unknown panic".to_string()
835 }
836 }
837}
838
839impl std::fmt::Debug for ExceptionHandlers {
840 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
841 f.debug_struct("ExceptionHandlers")
842 .field("count", &self.handlers.len())
843 .field("has_panic_handler", &self.panic_handler.is_some())
844 .finish()
845 }
846}
847
848#[derive(Debug, Clone)]
877pub struct AppConfig {
878 pub name: String,
880 pub version: String,
882 pub debug: bool,
884 pub max_body_size: usize,
886 pub request_timeout_ms: u64,
888 pub root_path: String,
898 pub root_path_in_servers: bool,
903 pub trailing_slash_mode: crate::routing::TrailingSlashMode,
911 pub debug_config: crate::error::DebugConfig,
918}
919
920#[derive(Debug)]
922pub enum ConfigError {
923 Io(io::Error),
925 Json(serde_json::Error),
927 UnsupportedFormat { path: PathBuf },
929 InvalidEnvVar {
931 key: String,
933 value: String,
935 expected: String,
937 },
938 Validation(String),
940}
941
942impl std::fmt::Display for ConfigError {
943 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
944 match self {
945 Self::Io(err) => write!(f, "config I/O error: {err}"),
946 Self::Json(err) => write!(f, "config JSON error: {err}"),
947 Self::UnsupportedFormat { path } => {
948 write!(f, "unsupported config format: {}", path.display())
949 }
950 Self::InvalidEnvVar {
951 key,
952 value,
953 expected,
954 } => write!(f, "invalid env var {key}='{value}' (expected {expected})"),
955 Self::Validation(message) => write!(f, "invalid config: {message}"),
956 }
957 }
958}
959
960impl std::error::Error for ConfigError {
961 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
962 match self {
963 Self::Io(err) => Some(err),
964 Self::Json(err) => Some(err),
965 _ => None,
966 }
967 }
968}
969
970impl From<io::Error> for ConfigError {
971 fn from(err: io::Error) -> Self {
972 Self::Io(err)
973 }
974}
975
976impl From<serde_json::Error> for ConfigError {
977 fn from(err: serde_json::Error) -> Self {
978 Self::Json(err)
979 }
980}
981
982#[derive(Debug, Deserialize, Default)]
983struct AppConfigFile {
984 name: Option<String>,
985 version: Option<String>,
986 debug: Option<bool>,
987 max_body_size: Option<usize>,
988 request_timeout_ms: Option<u64>,
989 root_path: Option<String>,
990 root_path_in_servers: Option<bool>,
991}
992
993impl Default for AppConfig {
994 fn default() -> Self {
995 Self {
996 name: String::from("fastapi_rust"),
997 version: String::from("0.1.0"),
998 debug: false,
999 max_body_size: 1024 * 1024, request_timeout_ms: 30_000, root_path: String::new(),
1002 root_path_in_servers: true,
1003 trailing_slash_mode: crate::routing::TrailingSlashMode::Strict,
1004 debug_config: crate::error::DebugConfig::default(),
1005 }
1006 }
1007}
1008
1009impl AppConfig {
1010 const DEFAULT_ENV_PREFIX: &'static str = "FASTAPI_";
1011 const ENV_NAME: &'static str = "NAME";
1012 const ENV_VERSION: &'static str = "VERSION";
1013 const ENV_DEBUG: &'static str = "DEBUG";
1014 const ENV_MAX_BODY_SIZE: &'static str = "MAX_BODY_SIZE";
1015 const ENV_REQUEST_TIMEOUT_MS: &'static str = "REQUEST_TIMEOUT_MS";
1016 const ENV_ROOT_PATH: &'static str = "ROOT_PATH";
1017 const ENV_ROOT_PATH_IN_SERVERS: &'static str = "ROOT_PATH_IN_SERVERS";
1018
1019 #[must_use]
1021 pub fn new() -> Self {
1022 Self::default()
1023 }
1024
1025 #[must_use]
1027 pub fn name(mut self, name: impl Into<String>) -> Self {
1028 self.name = name.into();
1029 self
1030 }
1031
1032 #[must_use]
1034 pub fn version(mut self, version: impl Into<String>) -> Self {
1035 self.version = version.into();
1036 self
1037 }
1038
1039 #[must_use]
1041 pub fn debug(mut self, debug: bool) -> Self {
1042 self.debug = debug;
1043 self
1044 }
1045
1046 #[must_use]
1048 pub fn max_body_size(mut self, size: usize) -> Self {
1049 self.max_body_size = size;
1050 self
1051 }
1052
1053 #[must_use]
1055 pub fn request_timeout_ms(mut self, timeout: u64) -> Self {
1056 self.request_timeout_ms = timeout;
1057 self
1058 }
1059
1060 #[must_use]
1072 pub fn root_path(mut self, path: impl Into<String>) -> Self {
1073 let mut path = path.into();
1074 while path.ends_with('/') && path.len() > 1 {
1076 path.pop();
1077 }
1078 self.root_path = path;
1079 self
1080 }
1081
1082 #[must_use]
1087 pub fn root_path_in_servers(mut self, include: bool) -> Self {
1088 self.root_path_in_servers = include;
1089 self
1090 }
1091
1092 #[must_use]
1105 pub fn trailing_slash_mode(mut self, mode: crate::routing::TrailingSlashMode) -> Self {
1106 self.trailing_slash_mode = mode;
1107 self
1108 }
1109
1110 #[must_use]
1126 pub fn debug_config(mut self, config: crate::error::DebugConfig) -> Self {
1127 self.debug_config = config;
1128 self
1129 }
1130
1131 pub fn from_env() -> Result<Self, ConfigError> {
1142 Self::from_env_with_prefix(Self::DEFAULT_ENV_PREFIX)
1143 }
1144
1145 pub fn from_env_with_prefix(prefix: &str) -> Result<Self, ConfigError> {
1147 let mut config = Self::default();
1148 config.apply_env(prefix)?;
1149 config.validate()?;
1150 Ok(config)
1151 }
1152
1153 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
1157 let path = path.as_ref();
1158 if !matches!(path.extension().and_then(|ext| ext.to_str()), Some("json")) {
1159 return Err(ConfigError::UnsupportedFormat {
1160 path: path.to_path_buf(),
1161 });
1162 }
1163 let contents = fs::read_to_string(path)?;
1164 let parsed: AppConfigFile = serde_json::from_str(&contents)?;
1165 let mut config = Self::default();
1166 config.apply_file(parsed);
1167 config.validate()?;
1168 Ok(config)
1169 }
1170
1171 pub fn from_env_and_file(path: impl AsRef<Path>) -> Result<Self, ConfigError> {
1173 let mut config = Self::from_file(path)?;
1174 config.apply_env(Self::DEFAULT_ENV_PREFIX)?;
1175 config.validate()?;
1176 Ok(config)
1177 }
1178
1179 #[must_use]
1196 pub fn openapi_server(&self) -> Option<(String, Option<String>)> {
1197 if !self.root_path.is_empty() && self.root_path_in_servers {
1198 Some((
1199 self.root_path.clone(),
1200 Some("Application root path".to_string()),
1201 ))
1202 } else {
1203 None
1204 }
1205 }
1206
1207 pub fn validate(&self) -> Result<(), ConfigError> {
1209 if self.name.trim().is_empty() {
1210 return Err(ConfigError::Validation(
1211 "name must not be empty".to_string(),
1212 ));
1213 }
1214 if self.version.trim().is_empty() {
1215 return Err(ConfigError::Validation(
1216 "version must not be empty".to_string(),
1217 ));
1218 }
1219 if self.max_body_size == 0 {
1220 return Err(ConfigError::Validation(
1221 "max_body_size must be greater than 0".to_string(),
1222 ));
1223 }
1224 if self.request_timeout_ms == 0 {
1225 return Err(ConfigError::Validation(
1226 "request_timeout_ms must be greater than 0".to_string(),
1227 ));
1228 }
1229 Ok(())
1230 }
1231
1232 fn apply_file(&mut self, file: AppConfigFile) {
1233 if let Some(name) = file.name {
1234 self.name = name;
1235 }
1236 if let Some(version) = file.version {
1237 self.version = version;
1238 }
1239 if let Some(debug) = file.debug {
1240 self.debug = debug;
1241 }
1242 if let Some(max_body_size) = file.max_body_size {
1243 self.max_body_size = max_body_size;
1244 }
1245 if let Some(request_timeout_ms) = file.request_timeout_ms {
1246 self.request_timeout_ms = request_timeout_ms;
1247 }
1248 if let Some(root_path) = file.root_path {
1249 self.root_path = root_path;
1250 }
1251 if let Some(root_path_in_servers) = file.root_path_in_servers {
1252 self.root_path_in_servers = root_path_in_servers;
1253 }
1254 }
1255
1256 fn apply_env(&mut self, prefix: &str) -> Result<(), ConfigError> {
1257 self.apply_env_with(prefix, fetch_env)
1258 }
1259
1260 fn apply_env_with<F>(&mut self, prefix: &str, mut fetch: F) -> Result<(), ConfigError>
1261 where
1262 F: FnMut(&str) -> Result<Option<String>, ConfigError>,
1263 {
1264 let name_key = env_key(prefix, Self::ENV_NAME);
1265 let version_key = env_key(prefix, Self::ENV_VERSION);
1266 let debug_key = env_key(prefix, Self::ENV_DEBUG);
1267 let max_body_key = env_key(prefix, Self::ENV_MAX_BODY_SIZE);
1268 let timeout_key = env_key(prefix, Self::ENV_REQUEST_TIMEOUT_MS);
1269 let root_path_key = env_key(prefix, Self::ENV_ROOT_PATH);
1270 let root_path_in_servers_key = env_key(prefix, Self::ENV_ROOT_PATH_IN_SERVERS);
1271
1272 if let Some(value) = fetch(&name_key)? {
1273 self.name = value;
1274 }
1275 if let Some(value) = fetch(&version_key)? {
1276 self.version = value;
1277 }
1278 if let Some(value) = fetch(&debug_key)? {
1279 self.debug = parse_bool(&debug_key, &value)?;
1280 }
1281 if let Some(value) = fetch(&max_body_key)? {
1282 self.max_body_size = parse_usize(&max_body_key, &value)?;
1283 }
1284 if let Some(value) = fetch(&timeout_key)? {
1285 self.request_timeout_ms = parse_u64(&timeout_key, &value)?;
1286 }
1287 if let Some(value) = fetch(&root_path_key)? {
1288 self.root_path = value;
1289 }
1290 if let Some(value) = fetch(&root_path_in_servers_key)? {
1291 self.root_path_in_servers = parse_bool(&root_path_in_servers_key, &value)?;
1292 }
1293
1294 Ok(())
1295 }
1296}
1297
1298fn env_key(prefix: &str, key: &str) -> String {
1299 if prefix.ends_with('_') {
1300 format!("{prefix}{key}")
1301 } else {
1302 format!("{prefix}_{key}")
1303 }
1304}
1305
1306fn fetch_env(key: &str) -> Result<Option<String>, ConfigError> {
1307 match env::var(key) {
1308 Ok(value) => Ok(Some(value)),
1309 Err(env::VarError::NotPresent) => Ok(None),
1310 Err(env::VarError::NotUnicode(_)) => Err(ConfigError::InvalidEnvVar {
1311 key: key.to_string(),
1312 value: "<non-utf8>".to_string(),
1313 expected: "valid UTF-8 string".to_string(),
1314 }),
1315 }
1316}
1317
1318fn parse_bool(key: &str, value: &str) -> Result<bool, ConfigError> {
1319 let normalized = value.trim().to_ascii_lowercase();
1320 match normalized.as_str() {
1321 "true" | "1" | "yes" | "on" => Ok(true),
1322 "false" | "0" | "no" | "off" => Ok(false),
1323 _ => Err(ConfigError::InvalidEnvVar {
1324 key: key.to_string(),
1325 value: value.to_string(),
1326 expected: "boolean (true/false/1/0/yes/no/on/off)".to_string(),
1327 }),
1328 }
1329}
1330
1331fn parse_usize(key: &str, value: &str) -> Result<usize, ConfigError> {
1332 value
1333 .parse::<usize>()
1334 .map_err(|_| ConfigError::InvalidEnvVar {
1335 key: key.to_string(),
1336 value: value.to_string(),
1337 expected: "usize".to_string(),
1338 })
1339}
1340
1341fn parse_u64(key: &str, value: &str) -> Result<u64, ConfigError> {
1342 value
1343 .parse::<u64>()
1344 .map_err(|_| ConfigError::InvalidEnvVar {
1345 key: key.to_string(),
1346 value: value.to_string(),
1347 expected: "u64".to_string(),
1348 })
1349}
1350
1351pub struct AppBuilder<S: StateRegistry = ()> {
1397 config: AppConfig,
1398 routes: Vec<RouteEntry>,
1399 middleware: Vec<Arc<dyn Middleware>>,
1400 state: StateContainer,
1401 dependency_overrides: Arc<DependencyOverrides>,
1402 exception_handlers: ExceptionHandlers,
1403 startup_hooks: Vec<StartupHook>,
1404 shutdown_hooks: Vec<Box<dyn FnOnce() + Send>>,
1405 async_shutdown_hooks: Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
1406 lifespan: Option<BoxLifespanFn>,
1408 mounted_apps: Vec<MountedApp>,
1410 _state_marker: PhantomData<S>,
1411}
1412
1413impl Default for AppBuilder<()> {
1414 fn default() -> Self {
1415 Self {
1416 config: AppConfig::default(),
1417 routes: Vec::new(),
1418 middleware: Vec::new(),
1419 state: StateContainer::default(),
1420 dependency_overrides: Arc::new(DependencyOverrides::new()),
1421 exception_handlers: ExceptionHandlers::default(),
1422 startup_hooks: Vec::new(),
1423 shutdown_hooks: Vec::new(),
1424 async_shutdown_hooks: Vec::new(),
1425 lifespan: None,
1426 mounted_apps: Vec::new(),
1427 _state_marker: PhantomData,
1428 }
1429 }
1430}
1431
1432impl AppBuilder<()> {
1433 #[must_use]
1435 pub fn new() -> Self {
1436 Self::default()
1437 }
1438}
1439
1440impl<S: StateRegistry> AppBuilder<S> {
1441 #[must_use]
1443 pub fn config(mut self, config: AppConfig) -> Self {
1444 self.config = config;
1445 self
1446 }
1447
1448 #[must_use]
1452 pub fn route<H, Fut>(mut self, path: impl Into<String>, method: Method, handler: H) -> Self
1453 where
1454 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1455 Fut: Future<Output = Response> + Send + 'static,
1456 {
1457 self.routes
1458 .push(RouteEntry::new(method, path, move |ctx, req| {
1459 Box::pin(handler(ctx, req))
1460 }));
1461 self
1462 }
1463
1464 #[must_use]
1466 pub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1467 where
1468 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1469 Fut: Future<Output = Response> + Send + 'static,
1470 {
1471 self.route(path, Method::Get, handler)
1472 }
1473
1474 #[must_use]
1476 pub fn post<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1477 where
1478 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1479 Fut: Future<Output = Response> + Send + 'static,
1480 {
1481 self.route(path, Method::Post, handler)
1482 }
1483
1484 #[must_use]
1486 pub fn put<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1487 where
1488 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1489 Fut: Future<Output = Response> + Send + 'static,
1490 {
1491 self.route(path, Method::Put, handler)
1492 }
1493
1494 #[must_use]
1496 pub fn delete<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1497 where
1498 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1499 Fut: Future<Output = Response> + Send + 'static,
1500 {
1501 self.route(path, Method::Delete, handler)
1502 }
1503
1504 #[must_use]
1506 pub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1507 where
1508 H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1509 Fut: Future<Output = Response> + Send + 'static,
1510 {
1511 self.route(path, Method::Patch, handler)
1512 }
1513
1514 #[must_use]
1520 pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
1521 self.middleware.push(Arc::new(middleware));
1522 self
1523 }
1524
1525 #[must_use]
1545 pub fn include_router(mut self, router: crate::api_router::APIRouter) -> Self {
1546 for entry in router.into_route_entries() {
1547 self.routes.push(entry);
1548 }
1549 self
1550 }
1551
1552 #[must_use]
1575 pub fn include_router_with_config(
1576 mut self,
1577 router: crate::api_router::APIRouter,
1578 config: crate::api_router::IncludeConfig,
1579 ) -> Self {
1580 let merged_router =
1582 crate::api_router::APIRouter::new().include_router_with_config(router, config);
1583 for entry in merged_router.into_route_entries() {
1584 self.routes.push(entry);
1585 }
1586 self
1587 }
1588
1589 #[must_use]
1636 pub fn mount(mut self, prefix: impl Into<String>, app: App) -> Self {
1637 self.mounted_apps.push(MountedApp::new(prefix, app));
1638 self
1639 }
1640
1641 #[must_use]
1643 pub fn mounted_app_count(&self) -> usize {
1644 self.mounted_apps.len()
1645 }
1646
1647 #[must_use]
1654 #[deprecated(
1655 since = "0.2.0",
1656 note = "Use `with_state` for compile-time state type verification"
1657 )]
1658 pub fn state<T: Send + Sync + 'static>(mut self, value: T) -> Self {
1659 self.state.insert(value);
1660 self
1661 }
1662
1663 #[must_use]
1697 pub fn with_state<T: Send + Sync + 'static>(mut self, value: T) -> AppBuilder<(T, S)> {
1698 self.state.insert(value);
1699 AppBuilder {
1700 config: self.config,
1701 routes: self.routes,
1702 middleware: self.middleware,
1703 state: self.state,
1704 dependency_overrides: self.dependency_overrides,
1705 exception_handlers: self.exception_handlers,
1706 startup_hooks: self.startup_hooks,
1707 shutdown_hooks: self.shutdown_hooks,
1708 async_shutdown_hooks: self.async_shutdown_hooks,
1709 lifespan: self.lifespan,
1710 mounted_apps: self.mounted_apps,
1711 _state_marker: PhantomData,
1712 }
1713 }
1714
1715 #[must_use]
1717 pub fn override_dependency<T, F, Fut>(self, f: F) -> Self
1718 where
1719 T: FromDependency,
1720 F: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1721 Fut: Future<Output = Result<T, T::Error>> + Send + 'static,
1722 {
1723 self.dependency_overrides.insert::<T, F, Fut>(f);
1724 self
1725 }
1726
1727 #[must_use]
1729 pub fn override_dependency_value<T>(self, value: T) -> Self
1730 where
1731 T: FromDependency,
1732 {
1733 self.dependency_overrides.insert_value(value);
1734 self
1735 }
1736
1737 #[must_use]
1739 pub fn clear_dependency_overrides(self) -> Self {
1740 self.dependency_overrides.clear();
1741 self
1742 }
1743
1744 #[must_use]
1772 pub fn exception_handler<E, H>(mut self, handler: H) -> Self
1773 where
1774 E: std::error::Error + Send + Sync + 'static,
1775 H: Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
1776 {
1777 self.exception_handlers.register::<E>(handler);
1778 self
1779 }
1780
1781 #[must_use]
1785 pub fn exception_handlers(mut self, handlers: ExceptionHandlers) -> Self {
1786 self.exception_handlers = handlers;
1787 self
1788 }
1789
1790 #[must_use]
1796 pub fn with_default_exception_handlers(mut self) -> Self {
1797 self.exception_handlers = ExceptionHandlers::with_defaults();
1798 self
1799 }
1800
1801 #[must_use]
1825 pub fn on_startup<F>(mut self, hook: F) -> Self
1826 where
1827 F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,
1828 {
1829 self.startup_hooks.push(StartupHook::Sync(Box::new(hook)));
1830 self
1831 }
1832
1833 #[must_use]
1848 pub fn on_startup_async<F, Fut>(mut self, hook: F) -> Self
1849 where
1850 F: FnOnce() -> Fut + Send + 'static,
1851 Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,
1852 {
1853 self.startup_hooks.push(StartupHook::AsyncFactory(Box::new(
1854 move || Box::pin(hook()),
1855 )));
1856 self
1857 }
1858
1859 #[must_use]
1877 pub fn on_shutdown<F>(mut self, hook: F) -> Self
1878 where
1879 F: FnOnce() + Send + 'static,
1880 {
1881 self.shutdown_hooks.push(Box::new(hook));
1882 self
1883 }
1884
1885 #[must_use]
1899 pub fn on_shutdown_async<F, Fut>(mut self, hook: F) -> Self
1900 where
1901 F: FnOnce() -> Fut + Send + 'static,
1902 Fut: Future<Output = ()> + Send + 'static,
1903 {
1904 self.async_shutdown_hooks
1905 .push(Box::new(move || Box::pin(hook())));
1906 self
1907 }
1908
1909 #[must_use]
1993 pub fn lifespan<F, Fut, T>(mut self, lifespan_fn: F) -> Self
1994 where
1995 F: FnOnce() -> Fut + Send + 'static,
1996 Fut: Future<Output = Result<LifespanScope<T>, LifespanError>> + Send + 'static,
1997 T: Send + Sync + 'static,
1998 {
1999 self.lifespan = Some(Box::new(move || {
2000 Box::pin(async move {
2001 let mut scope = lifespan_fn().await?;
2003
2004 let cleanup = scope.take_cleanup();
2006
2007 let state: Box<dyn std::any::Any + Send + Sync> = Box::new(scope.state);
2009
2010 Ok((state, cleanup))
2012 })
2013 }));
2014 self
2015 }
2016
2017 #[must_use]
2019 pub fn has_lifespan(&self) -> bool {
2020 self.lifespan.is_some()
2021 }
2022
2023 #[must_use]
2025 pub fn startup_hook_count(&self) -> usize {
2026 self.startup_hooks.len()
2027 }
2028
2029 #[must_use]
2031 pub fn shutdown_hook_count(&self) -> usize {
2032 self.shutdown_hooks.len() + self.async_shutdown_hooks.len()
2033 }
2034
2035 #[must_use]
2039 pub fn build(self) -> App {
2040 let mut middleware_stack = MiddlewareStack::with_capacity(self.middleware.len());
2041 for mw in self.middleware {
2042 middleware_stack.push_arc(mw);
2043 }
2044
2045 let mut route_table = RouteTable::new();
2048 for (idx, route) in self.routes.iter().enumerate() {
2049 route_table.add(route.method, &route.path, idx);
2050 }
2051
2052 App {
2053 config: self.config,
2054 routes: self.routes,
2055 route_table,
2056 middleware: middleware_stack,
2057 state: Arc::new(parking_lot::RwLock::new(self.state)),
2058 dependency_overrides: Arc::clone(&self.dependency_overrides),
2059 exception_handlers: Arc::new(self.exception_handlers),
2060 startup_hooks: parking_lot::Mutex::new(self.startup_hooks),
2061 shutdown_hooks: parking_lot::Mutex::new(self.shutdown_hooks),
2062 async_shutdown_hooks: parking_lot::Mutex::new(self.async_shutdown_hooks),
2063 lifespan: parking_lot::Mutex::new(self.lifespan),
2064 lifespan_cleanup: parking_lot::Mutex::new(None),
2065 mounted_apps: self.mounted_apps,
2066 }
2067 }
2068}
2069
2070impl<S: StateRegistry> std::fmt::Debug for AppBuilder<S> {
2071 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2072 f.debug_struct("AppBuilder")
2073 .field("config", &self.config)
2074 .field("routes", &self.routes.len())
2075 .field("middleware", &self.middleware.len())
2076 .field("state", &self.state)
2077 .field("dependency_overrides", &self.dependency_overrides)
2078 .field("exception_handlers", &self.exception_handlers)
2079 .field("startup_hooks", &self.startup_hooks.len())
2080 .field("shutdown_hooks", &self.shutdown_hook_count())
2081 .field("mounted_apps", &self.mounted_apps.len())
2082 .finish()
2083 }
2084}
2085
2086#[derive(Debug)]
2096pub struct MountedApp {
2097 prefix: String,
2099 app: Arc<App>,
2101}
2102
2103impl MountedApp {
2104 #[must_use]
2108 pub fn new(prefix: impl Into<String>, app: App) -> Self {
2109 let mut prefix = prefix.into();
2110 if !prefix.starts_with('/') {
2112 prefix = format!("/{}", prefix);
2113 }
2114 while prefix.ends_with('/') && prefix.len() > 1 {
2115 prefix.pop();
2116 }
2117 Self {
2118 prefix,
2119 app: Arc::new(app),
2120 }
2121 }
2122
2123 #[must_use]
2125 pub fn prefix(&self) -> &str {
2126 &self.prefix
2127 }
2128
2129 #[must_use]
2131 pub fn app(&self) -> &App {
2132 &self.app
2133 }
2134
2135 #[must_use]
2139 pub fn match_prefix<'a>(&self, path: &'a str) -> Option<&'a str> {
2140 if path == self.prefix {
2141 Some("/")
2143 } else if path.starts_with(&self.prefix) {
2144 let remaining = &path[self.prefix.len()..];
2145 if remaining.starts_with('/') {
2147 Some(remaining)
2148 } else {
2149 None
2150 }
2151 } else {
2152 None
2153 }
2154 }
2155}
2156
2157pub struct App {
2162 config: AppConfig,
2163 routes: Vec<RouteEntry>,
2164 route_table: RouteTable<usize>,
2166 middleware: MiddlewareStack,
2167 state: Arc<parking_lot::RwLock<StateContainer>>,
2169 dependency_overrides: Arc<DependencyOverrides>,
2170 exception_handlers: Arc<ExceptionHandlers>,
2171 startup_hooks: parking_lot::Mutex<Vec<StartupHook>>,
2172 shutdown_hooks: parking_lot::Mutex<Vec<Box<dyn FnOnce() + Send>>>,
2173 async_shutdown_hooks: parking_lot::Mutex<
2174 Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
2175 >,
2176 lifespan: parking_lot::Mutex<Option<BoxLifespanFn>>,
2178 lifespan_cleanup: parking_lot::Mutex<Option<Pin<Box<dyn Future<Output = ()> + Send>>>>,
2180 mounted_apps: Vec<MountedApp>,
2182}
2183
2184impl App {
2185 #[must_use]
2187 pub fn builder() -> AppBuilder<()> {
2188 AppBuilder::new()
2189 }
2190
2191 #[must_use]
2193 pub fn config(&self) -> &AppConfig {
2194 &self.config
2195 }
2196
2197 #[must_use]
2199 pub fn route_count(&self) -> usize {
2200 self.routes.len()
2201 }
2202
2203 #[must_use]
2205 pub fn state(&self) -> &Arc<parking_lot::RwLock<StateContainer>> {
2206 &self.state
2207 }
2208
2209 pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
2211 self.state.read().get::<T>()
2212 }
2213
2214 #[must_use]
2216 pub fn dependency_overrides(&self) -> &Arc<DependencyOverrides> {
2217 &self.dependency_overrides
2218 }
2219
2220 pub fn override_dependency<T, F, Fut>(&self, f: F)
2222 where
2223 T: FromDependency,
2224 F: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
2225 Fut: Future<Output = Result<T, T::Error>> + Send + 'static,
2226 {
2227 self.dependency_overrides.insert::<T, F, Fut>(f);
2228 }
2229
2230 pub fn override_dependency_value<T>(&self, value: T)
2232 where
2233 T: FromDependency,
2234 {
2235 self.dependency_overrides.insert_value(value);
2236 }
2237
2238 pub fn clear_dependency_overrides(&self) {
2240 self.dependency_overrides.clear();
2241 }
2242
2243 #[must_use]
2245 pub fn exception_handlers(&self) -> &Arc<ExceptionHandlers> {
2246 &self.exception_handlers
2247 }
2248
2249 pub fn handle_error<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
2254 where
2255 E: std::error::Error + Send + Sync + 'static,
2256 {
2257 self.exception_handlers.handle(ctx, err)
2258 }
2259
2260 pub fn handle_error_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
2262 where
2263 E: std::error::Error + Send + Sync + 'static,
2264 {
2265 self.exception_handlers.handle_or_default(ctx, err)
2266 }
2267
2268 #[must_use]
2270 pub fn mounted_apps(&self) -> &[MountedApp] {
2271 &self.mounted_apps
2272 }
2273
2274 pub async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response {
2295 for mounted in &self.mounted_apps {
2297 if let Some(stripped_path) = mounted.match_prefix(req.path()) {
2298 let original_path = req.path().to_string();
2300 req.set_path(stripped_path.to_string());
2301
2302 let response = Box::pin(mounted.app.handle(ctx, req)).await;
2305
2306 req.set_path(original_path);
2308
2309 return response;
2310 }
2311 }
2312
2313 match self.route_table.lookup(req.path(), req.method()) {
2315 RouteLookup::Match { route: idx, params } => {
2316 req.insert_extension(PathParams::from_pairs(params));
2318
2319 req.insert_extension(crate::extract::ResponseMutations::new());
2321
2322 req.insert_extension(crate::extract::BackgroundTasksInner::new());
2324
2325 let entry = &self.routes[*idx];
2327 let handler = RouteHandler { entry };
2328 let response = self.middleware.execute(&handler, ctx, req).await;
2329
2330 ctx.cleanup_stack().run_cleanups().await;
2332
2333 let response = if let Some(mutations) =
2335 req.get_extension::<crate::extract::ResponseMutations>()
2336 {
2337 mutations.clone().apply(response)
2338 } else {
2339 response
2340 };
2341
2342 if req.method() == Method::Head {
2345 strip_body_for_head(response)
2346 } else {
2347 response
2348 }
2349 }
2350 RouteLookup::MethodNotAllowed { mut allowed } => {
2351 if req.method() == Method::Options {
2354 if !allowed.contains(&Method::Options) {
2356 allowed.push(Method::Options);
2357 allowed.sort_by_key(|m| method_order(*m));
2359 }
2360 return Response::with_status(StatusCode::NO_CONTENT)
2361 .header("Allow", format_allow_header(&allowed).into_bytes());
2362 }
2363 Response::with_status(StatusCode::METHOD_NOT_ALLOWED)
2364 .header("Allow", format_allow_header(&allowed).into_bytes())
2365 }
2366 RouteLookup::NotFound => Response::with_status(StatusCode::NOT_FOUND),
2367 RouteLookup::Redirect { target } => {
2368 Response::with_status(StatusCode::PERMANENT_REDIRECT)
2370 .header("Location", target.into_bytes())
2371 }
2372 }
2373 }
2374
2375 #[must_use]
2391 pub fn take_background_tasks(req: &mut Request) -> Option<crate::extract::BackgroundTasks> {
2392 req.get_extension::<crate::extract::BackgroundTasksInner>()
2393 .map(|inner| crate::extract::BackgroundTasks::from_inner(inner.clone()))
2394 }
2395
2396 pub async fn run_startup_hooks(&self) -> StartupOutcome {
2413 let mut warnings = 0;
2414
2415 let lifespan_fn = self.lifespan.lock().take();
2417 if let Some(lifespan) = lifespan_fn {
2418 let result = lifespan().await;
2420
2421 match result {
2422 Ok((state, cleanup)) => {
2423 self.state.write().insert_any(state);
2425 *self.lifespan_cleanup.lock() = cleanup;
2427 }
2428 Err(e) => {
2429 return StartupOutcome::Aborted(e.into());
2431 }
2432 }
2433 }
2434
2435 let hooks: Vec<StartupHook> = std::mem::take(&mut *self.startup_hooks.lock());
2437
2438 for hook in hooks {
2439 match hook.run() {
2440 Ok(None) => {
2441 }
2443 Ok(Some(fut)) => {
2444 match fut.await {
2446 Ok(()) => {}
2447 Err(e) if e.abort => {
2448 return StartupOutcome::Aborted(e);
2449 }
2450 Err(_) => {
2451 warnings += 1;
2452 }
2453 }
2454 }
2455 Err(e) if e.abort => {
2456 return StartupOutcome::Aborted(e);
2457 }
2458 Err(_) => {
2459 warnings += 1;
2460 }
2461 }
2462 }
2463
2464 if warnings > 0 {
2465 StartupOutcome::PartialSuccess { warnings }
2466 } else {
2467 StartupOutcome::Success
2468 }
2469 }
2470
2471 pub async fn run_shutdown_hooks(&self) {
2478 let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
2480 for hook in async_hooks.into_iter().rev() {
2481 let fut = hook();
2482 fut.await;
2483 }
2484
2485 let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
2487 for hook in sync_hooks.into_iter().rev() {
2488 hook();
2489 }
2490
2491 let lifespan_cleanup = self.lifespan_cleanup.lock().take();
2493 if let Some(cleanup) = lifespan_cleanup {
2494 cleanup.await;
2495 }
2496 }
2497
2498 pub fn transfer_shutdown_hooks(&self, controller: &ShutdownController) {
2505 let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
2507 for hook in sync_hooks {
2508 controller.register_hook(hook);
2509 }
2510
2511 let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
2513 for hook in async_hooks {
2514 controller.register_async_hook(move || hook());
2515 }
2516 }
2517
2518 #[must_use]
2520 pub fn pending_startup_hooks(&self) -> usize {
2521 self.startup_hooks.lock().len()
2522 }
2523
2524 #[must_use]
2526 pub fn pending_shutdown_hooks(&self) -> usize {
2527 self.shutdown_hooks.lock().len() + self.async_shutdown_hooks.lock().len()
2528 }
2529
2530 #[must_use]
2562 pub fn test_client(self: Arc<Self>) -> crate::testing::TestClient<Arc<App>> {
2563 crate::testing::TestClient::new(self)
2564 }
2565
2566 #[must_use]
2579 pub fn test_client_with_seed(
2580 self: Arc<Self>,
2581 seed: u64,
2582 ) -> crate::testing::TestClient<Arc<App>> {
2583 crate::testing::TestClient::with_seed(self, seed)
2584 }
2585}
2586
2587impl std::fmt::Debug for App {
2588 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2589 f.debug_struct("App")
2590 .field("config", &self.config)
2591 .field("routes", &self.routes.len())
2592 .field("middleware", &self.middleware.len())
2593 .field("state", &self.state)
2594 .field("dependency_overrides", &self.dependency_overrides)
2595 .field("exception_handlers", &self.exception_handlers)
2596 .field("startup_hooks", &self.startup_hooks.lock().len())
2597 .field("shutdown_hooks", &self.pending_shutdown_hooks())
2598 .finish()
2599 }
2600}
2601
2602impl Handler for App {
2603 fn call<'a>(
2604 &'a self,
2605 ctx: &'a RequestContext,
2606 req: &'a mut Request,
2607 ) -> BoxFuture<'a, Response> {
2608 Box::pin(async move { self.handle(ctx, req).await })
2609 }
2610
2611 fn dependency_overrides(&self) -> Option<Arc<DependencyOverrides>> {
2612 Some(Arc::clone(&self.dependency_overrides))
2613 }
2614}
2615
2616impl Handler for Arc<App> {
2617 fn call<'a>(
2618 &'a self,
2619 ctx: &'a RequestContext,
2620 req: &'a mut Request,
2621 ) -> BoxFuture<'a, Response> {
2622 Box::pin(async move { self.handle(ctx, req).await })
2623 }
2624
2625 fn dependency_overrides(&self) -> Option<Arc<DependencyOverrides>> {
2626 Some(Arc::clone(&self.dependency_overrides))
2627 }
2628}
2629
2630struct RouteHandler<'a> {
2632 entry: &'a RouteEntry,
2633}
2634
2635impl<'a> Handler for RouteHandler<'a> {
2636 fn call<'b>(
2637 &'b self,
2638 ctx: &'b RequestContext,
2639 req: &'b mut Request,
2640 ) -> BoxFuture<'b, Response> {
2641 let handler = self.entry.handler.clone();
2642 Box::pin(async move {
2643 let _ = ctx.checkpoint();
2644 handler(ctx, req).await
2645 })
2646 }
2647}
2648
2649fn strip_body_for_head(response: Response) -> Response {
2661 use crate::response::ResponseBody;
2662
2663 let has_content_length = response
2665 .headers()
2666 .iter()
2667 .any(|(name, _): &(String, Vec<u8>)| name.eq_ignore_ascii_case("content-length"));
2668
2669 let (status, headers, body) = response.into_parts();
2671
2672 let content_length_to_add: Option<usize> = if has_content_length {
2675 None
2676 } else {
2677 match &body {
2678 ResponseBody::Bytes(b) => Some(b.len()),
2679 ResponseBody::Empty => Some(0),
2680 ResponseBody::Stream(_) => None, }
2682 };
2683
2684 let mut new_response = Response::with_status(status)
2686 .body(ResponseBody::Empty)
2687 .rebuild_with_headers(headers);
2688
2689 if let Some(len) = content_length_to_add {
2690 new_response = new_response.header("content-length", len.to_string().as_bytes().to_vec());
2691 }
2692
2693 new_response
2694}
2695
2696#[cfg(test)]
2697mod tests {
2698 use super::*;
2699
2700 use crate::response::ResponseBody;
2701 use std::collections::HashMap;
2702 use std::sync::Mutex;
2703 use std::time::{SystemTime, UNIX_EPOCH};
2704
2705 fn test_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
2707 std::future::ready(Response::ok().body(ResponseBody::Bytes(b"Hello, World!".to_vec())))
2708 }
2709
2710 fn health_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
2711 std::future::ready(Response::ok().body(ResponseBody::Bytes(b"OK".to_vec())))
2712 }
2713
2714 fn test_context() -> RequestContext {
2715 let cx = asupersync::Cx::for_testing();
2716 RequestContext::new(cx, 1)
2717 }
2718
2719 #[test]
2720 fn app_builder_creates_app() {
2721 let app = App::builder()
2722 .config(AppConfig::new().name("Test App"))
2723 .get("/", test_handler)
2724 .get("/health", health_handler)
2725 .build();
2726
2727 assert_eq!(app.route_count(), 2);
2728 assert_eq!(app.config().name, "Test App");
2729 }
2730
2731 #[test]
2732 fn app_config_builder() {
2733 let config = AppConfig::new()
2734 .name("My API")
2735 .version("1.0.0")
2736 .debug(true)
2737 .max_body_size(2 * 1024 * 1024)
2738 .request_timeout_ms(60_000);
2739
2740 assert_eq!(config.name, "My API");
2741 assert_eq!(config.version, "1.0.0");
2742 assert!(config.debug);
2743 assert_eq!(config.max_body_size, 2 * 1024 * 1024);
2744 assert_eq!(config.request_timeout_ms, 60_000);
2745 }
2746
2747 #[test]
2748 fn app_config_defaults() {
2749 let config = AppConfig::default();
2750 assert_eq!(config.name, "fastapi_rust");
2751 assert_eq!(config.version, "0.1.0");
2752 assert!(!config.debug);
2753 assert_eq!(config.max_body_size, 1024 * 1024);
2754 assert_eq!(config.request_timeout_ms, 30_000);
2755 }
2756
2757 #[test]
2758 fn app_config_from_env_parses_values() {
2759 let mut env = HashMap::new();
2760 env.insert("FASTAPI_NAME".to_string(), "Env API".to_string());
2761 env.insert("FASTAPI_VERSION".to_string(), "9.9.9".to_string());
2762 env.insert("FASTAPI_DEBUG".to_string(), "true".to_string());
2763 env.insert("FASTAPI_MAX_BODY_SIZE".to_string(), "4096".to_string());
2764 env.insert(
2765 "FASTAPI_REQUEST_TIMEOUT_MS".to_string(),
2766 "15000".to_string(),
2767 );
2768
2769 let mut config = AppConfig::default();
2770 config
2771 .apply_env_with(AppConfig::DEFAULT_ENV_PREFIX, |key| {
2772 Ok(env.get(key).cloned())
2773 })
2774 .expect("env config");
2775 config.validate().expect("env config");
2776
2777 assert_eq!(config.name, "Env API");
2778 assert_eq!(config.version, "9.9.9");
2779 assert!(config.debug);
2780 assert_eq!(config.max_body_size, 4096);
2781 assert_eq!(config.request_timeout_ms, 15_000);
2782 }
2783
2784 #[test]
2785 fn app_config_from_env_invalid_value() {
2786 let mut env = HashMap::new();
2787 env.insert(
2788 "FASTAPI_MAX_BODY_SIZE".to_string(),
2789 "not-a-number".to_string(),
2790 );
2791
2792 let mut config = AppConfig::default();
2793 let err = config
2794 .apply_env_with(AppConfig::DEFAULT_ENV_PREFIX, |key| {
2795 Ok(env.get(key).cloned())
2796 })
2797 .expect_err("invalid env should error");
2798 match err {
2799 ConfigError::InvalidEnvVar { key, .. } => {
2800 assert_eq!(key, "FASTAPI_MAX_BODY_SIZE");
2801 }
2802 _ => panic!("expected invalid env var error"),
2803 }
2804 }
2805
2806 #[test]
2807 fn app_config_validation_rejects_empty_name() {
2808 let config = AppConfig {
2809 name: String::new(),
2810 ..Default::default()
2811 };
2812 let err = config.validate().expect_err("empty name invalid");
2813 assert!(matches!(err, ConfigError::Validation(_)));
2814 }
2815
2816 #[test]
2817 fn app_config_from_file_json() {
2818 let stamp = SystemTime::now()
2819 .duration_since(UNIX_EPOCH)
2820 .expect("time")
2821 .as_nanos();
2822 let mut path = std::env::temp_dir();
2823 path.push(format!("fastapi_config_{stamp}.json"));
2824
2825 let json = r#"{
2826 "name": "File API",
2827 "version": "2.1.0",
2828 "debug": true,
2829 "max_body_size": 2048,
2830 "request_timeout_ms": 8000
2831}"#;
2832 std::fs::write(&path, json).expect("write temp config");
2833
2834 let config = AppConfig::from_file(&path).expect("file config");
2835 assert_eq!(config.name, "File API");
2836 assert_eq!(config.version, "2.1.0");
2837 assert!(config.debug);
2838 assert_eq!(config.max_body_size, 2048);
2839 assert_eq!(config.request_timeout_ms, 8000);
2840 }
2841
2842 #[test]
2843 fn app_config_env_overrides_file() {
2844 let stamp = SystemTime::now()
2845 .duration_since(UNIX_EPOCH)
2846 .expect("time")
2847 .as_nanos();
2848 let mut path = std::env::temp_dir();
2849 path.push(format!("fastapi_config_override_{stamp}.json"));
2850
2851 let json = r#"{
2852 "name": "File API",
2853 "version": "1.0.0",
2854 "debug": false,
2855 "max_body_size": 1024,
2856 "request_timeout_ms": 1000
2857}"#;
2858 std::fs::write(&path, json).expect("write temp config");
2859
2860 let mut env = HashMap::new();
2861 env.insert("FASTAPI_NAME".to_string(), "Env API".to_string());
2862 env.insert("FASTAPI_DEBUG".to_string(), "1".to_string());
2863
2864 let mut config = AppConfig::from_file(&path).expect("file config");
2865 config
2866 .apply_env_with(AppConfig::DEFAULT_ENV_PREFIX, |key| {
2867 Ok(env.get(key).cloned())
2868 })
2869 .expect("env+file config");
2870 config.validate().expect("env+file config");
2871
2872 assert_eq!(config.name, "Env API");
2873 assert!(config.debug);
2874 assert_eq!(config.version, "1.0.0");
2875 assert_eq!(config.max_body_size, 1024);
2876 }
2877
2878 #[test]
2879 fn app_config_default_root_path() {
2880 let config = AppConfig::default();
2881 assert!(config.root_path.is_empty());
2882 assert!(config.root_path_in_servers);
2883 }
2884
2885 #[test]
2886 fn app_config_builder_root_path() {
2887 let config = AppConfig::new()
2888 .root_path("/api/v1")
2889 .root_path_in_servers(false);
2890
2891 assert_eq!(config.root_path, "/api/v1");
2892 assert!(!config.root_path_in_servers);
2893 }
2894
2895 #[test]
2896 fn app_config_from_env_root_path() {
2897 let mut env = HashMap::new();
2898 env.insert("FASTAPI_ROOT_PATH".to_string(), "/proxy".to_string());
2899 env.insert(
2900 "FASTAPI_ROOT_PATH_IN_SERVERS".to_string(),
2901 "false".to_string(),
2902 );
2903
2904 let mut config = AppConfig::default();
2905 config
2906 .apply_env_with(AppConfig::DEFAULT_ENV_PREFIX, |key| {
2907 Ok(env.get(key).cloned())
2908 })
2909 .expect("env config");
2910
2911 assert_eq!(config.root_path, "/proxy");
2912 assert!(!config.root_path_in_servers);
2913 }
2914
2915 #[test]
2916 fn app_config_from_env_root_path_in_servers_truthy() {
2917 for truthy in &["true", "1", "yes", "on"] {
2918 let mut env = HashMap::new();
2919 env.insert(
2920 "FASTAPI_ROOT_PATH_IN_SERVERS".to_string(),
2921 (*truthy).to_string(),
2922 );
2923
2924 let mut config = AppConfig::new().root_path_in_servers(false);
2925 config
2926 .apply_env_with(AppConfig::DEFAULT_ENV_PREFIX, |key| {
2927 Ok(env.get(key).cloned())
2928 })
2929 .expect("env config");
2930
2931 assert!(
2932 config.root_path_in_servers,
2933 "expected true for value: {}",
2934 truthy
2935 );
2936 }
2937 }
2938
2939 #[test]
2940 fn app_config_from_env_root_path_in_servers_falsy() {
2941 for falsy in &["false", "0", "no", "off"] {
2942 let mut env = HashMap::new();
2943 env.insert(
2944 "FASTAPI_ROOT_PATH_IN_SERVERS".to_string(),
2945 (*falsy).to_string(),
2946 );
2947
2948 let mut config = AppConfig::default();
2949 config
2950 .apply_env_with(AppConfig::DEFAULT_ENV_PREFIX, |key| {
2951 Ok(env.get(key).cloned())
2952 })
2953 .expect("env config");
2954
2955 assert!(
2956 !config.root_path_in_servers,
2957 "expected false for value: {}",
2958 falsy
2959 );
2960 }
2961 }
2962
2963 #[test]
2964 fn app_config_from_file_root_path() {
2965 let stamp = SystemTime::now()
2966 .duration_since(UNIX_EPOCH)
2967 .expect("time")
2968 .as_nanos();
2969 let mut path = std::env::temp_dir();
2970 path.push(format!("fastapi_config_root_{stamp}.json"));
2971
2972 let json = r#"{
2973 "name": "Proxied API",
2974 "root_path": "/api/v2",
2975 "root_path_in_servers": false
2976}"#;
2977 std::fs::write(&path, json).expect("write temp config");
2978
2979 let config = AppConfig::from_file(&path).expect("file config");
2980 assert_eq!(config.name, "Proxied API");
2981 assert_eq!(config.root_path, "/api/v2");
2982 assert!(!config.root_path_in_servers);
2983 }
2984
2985 #[test]
2986 fn app_config_openapi_server_returns_some_when_configured() {
2987 let config = AppConfig::new()
2988 .root_path("/api/v1")
2989 .root_path_in_servers(true);
2990
2991 let server = config.openapi_server();
2992 assert!(server.is_some());
2993
2994 let (url, description) = server.unwrap();
2995 assert_eq!(url, "/api/v1");
2996 assert!(description.is_some());
2997 }
2998
2999 #[test]
3000 fn app_config_openapi_server_returns_none_when_disabled() {
3001 let config = AppConfig::new()
3002 .root_path("/api/v1")
3003 .root_path_in_servers(false);
3004
3005 assert!(config.openapi_server().is_none());
3006 }
3007
3008 #[test]
3009 fn app_config_openapi_server_returns_none_when_empty_root_path() {
3010 let config = AppConfig::new().root_path_in_servers(true);
3011
3012 assert!(config.openapi_server().is_none());
3013 }
3014
3015 #[test]
3016 fn state_container_insert_and_get() {
3017 #[derive(Debug, PartialEq)]
3018 struct MyState {
3019 value: i32,
3020 }
3021
3022 let mut container = StateContainer::new();
3023 container.insert(MyState { value: 42 });
3024
3025 let state = container.get::<MyState>();
3026 assert!(state.is_some());
3027 assert_eq!(state.unwrap().value, 42);
3028 }
3029
3030 #[test]
3031 fn state_container_multiple_types() {
3032 struct TypeA(i32);
3033 struct TypeB(String);
3034
3035 let mut container = StateContainer::new();
3036 container.insert(TypeA(1));
3037 container.insert(TypeB("hello".to_string()));
3038
3039 assert!(container.contains::<TypeA>());
3040 assert!(container.contains::<TypeB>());
3041 assert!(!container.contains::<i64>());
3042
3043 assert_eq!(container.get::<TypeA>().unwrap().0, 1);
3044 assert_eq!(container.get::<TypeB>().unwrap().0, "hello");
3045 }
3046
3047 #[test]
3048 fn app_builder_with_state() {
3049 struct DbPool {
3050 connection_count: usize,
3051 }
3052
3053 let app = App::builder()
3054 .with_state(DbPool {
3055 connection_count: 10,
3056 })
3057 .get("/", test_handler)
3058 .build();
3059
3060 let pool = app.get_state::<DbPool>();
3061 assert!(pool.is_some());
3062 assert_eq!(pool.unwrap().connection_count, 10);
3063 }
3064
3065 #[test]
3066 fn app_handles_get_request() {
3067 let app = App::builder().get("/", test_handler).build();
3068
3069 let ctx = test_context();
3070 let mut req = Request::new(Method::Get, "/");
3071
3072 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3073 assert_eq!(response.status().as_u16(), 200);
3074 }
3075
3076 #[test]
3077 fn app_returns_404_for_unknown_path() {
3078 let app = App::builder().get("/", test_handler).build();
3079
3080 let ctx = test_context();
3081 let mut req = Request::new(Method::Get, "/unknown");
3082
3083 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3084 assert_eq!(response.status().as_u16(), 404);
3085 }
3086
3087 #[test]
3088 fn app_returns_405_for_wrong_method() {
3089 let app = App::builder().get("/", test_handler).build();
3090
3091 let ctx = test_context();
3092 let mut req = Request::new(Method::Post, "/");
3093
3094 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3095 assert_eq!(response.status().as_u16(), 405);
3096 }
3097
3098 #[test]
3103 fn head_request_returns_empty_body() {
3104 let app = App::builder().get("/", test_handler).build();
3106
3107 let ctx = test_context();
3108 let mut req = Request::new(Method::Head, "/");
3109
3110 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3111 assert_eq!(response.status().as_u16(), 200);
3112 assert!(response.body_ref().is_empty());
3114 }
3115
3116 #[test]
3117 fn head_request_preserves_content_type() {
3118 fn json_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
3120 std::future::ready(
3121 Response::ok()
3122 .header("content-type", b"application/json".to_vec())
3123 .body(ResponseBody::Bytes(br#"{"status":"ok"}"#.to_vec())),
3124 )
3125 }
3126
3127 let app = App::builder().get("/api", json_handler).build();
3128
3129 let ctx = test_context();
3130 let mut req = Request::new(Method::Head, "/api");
3131
3132 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3133 assert_eq!(response.status().as_u16(), 200);
3134 assert!(response.body_ref().is_empty());
3135
3136 let content_type = response
3138 .headers()
3139 .iter()
3140 .find(|(name, _)| name.eq_ignore_ascii_case("content-type"));
3141 assert!(content_type.is_some());
3142 assert_eq!(content_type.unwrap().1, b"application/json");
3143 }
3144
3145 #[test]
3146 fn head_request_adds_content_length_for_known_body() {
3147 let app = App::builder().get("/", test_handler).build();
3148
3149 let ctx = test_context();
3150 let mut req = Request::new(Method::Head, "/");
3151
3152 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3153 assert_eq!(response.status().as_u16(), 200);
3154 assert!(response.body_ref().is_empty());
3155
3156 let content_length = response
3158 .headers()
3159 .iter()
3160 .find(|(name, _)| name.eq_ignore_ascii_case("content-length"));
3161 assert!(content_length.is_some());
3162 assert_eq!(content_length.unwrap().1, b"13");
3163 }
3164
3165 #[test]
3166 fn head_request_preserves_existing_content_length() {
3167 fn explicit_length_handler(
3169 _ctx: &RequestContext,
3170 _req: &mut Request,
3171 ) -> std::future::Ready<Response> {
3172 std::future::ready(
3173 Response::ok()
3174 .header("content-length", b"999".to_vec()) .body(ResponseBody::Bytes(b"short".to_vec())),
3176 )
3177 }
3178
3179 let app = App::builder().get("/", explicit_length_handler).build();
3180
3181 let ctx = test_context();
3182 let mut req = Request::new(Method::Head, "/");
3183
3184 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3185 assert_eq!(response.status().as_u16(), 200);
3186 assert!(response.body_ref().is_empty());
3187
3188 let content_length = response
3190 .headers()
3191 .iter()
3192 .find(|(name, _)| name.eq_ignore_ascii_case("content-length"));
3193 assert!(content_length.is_some());
3194 assert_eq!(content_length.unwrap().1, b"999");
3195 }
3196
3197 #[test]
3198 fn head_request_for_empty_body_adds_zero_content_length() {
3199 fn empty_handler(
3200 _ctx: &RequestContext,
3201 _req: &mut Request,
3202 ) -> std::future::Ready<Response> {
3203 std::future::ready(Response::ok().body(ResponseBody::Empty))
3204 }
3205
3206 let app = App::builder().get("/", empty_handler).build();
3207
3208 let ctx = test_context();
3209 let mut req = Request::new(Method::Head, "/");
3210
3211 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3212 assert_eq!(response.status().as_u16(), 200);
3213 assert!(response.body_ref().is_empty());
3214
3215 let content_length = response
3217 .headers()
3218 .iter()
3219 .find(|(name, _)| name.eq_ignore_ascii_case("content-length"));
3220 assert!(content_length.is_some());
3221 assert_eq!(content_length.unwrap().1, b"0");
3222 }
3223
3224 #[test]
3225 fn head_falls_through_to_get_route() {
3226 let app = App::builder()
3228 .get("/resource", test_handler)
3229 .post("/resource", test_handler) .build();
3231
3232 let ctx = test_context();
3233
3234 let mut head_req = Request::new(Method::Head, "/resource");
3236 let response = futures_executor::block_on(app.handle(&ctx, &mut head_req));
3237 assert_eq!(response.status().as_u16(), 200);
3238 assert!(response.body_ref().is_empty());
3239 }
3240
3241 #[test]
3242 fn head_to_unknown_path_returns_404() {
3243 let app = App::builder().get("/", test_handler).build();
3244
3245 let ctx = test_context();
3246 let mut req = Request::new(Method::Head, "/unknown");
3247
3248 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3249 assert_eq!(response.status().as_u16(), 404);
3250 }
3251
3252 #[test]
3253 fn app_builder_all_methods() {
3254 let app = App::builder()
3255 .get("/get", test_handler)
3256 .post("/post", test_handler)
3257 .put("/put", test_handler)
3258 .delete("/delete", test_handler)
3259 .patch("/patch", test_handler)
3260 .build();
3261
3262 assert_eq!(app.route_count(), 5);
3263 }
3264
3265 #[test]
3266 fn route_entry_debug() {
3267 let entry = RouteEntry::new(Method::Get, "/test", |ctx, req| {
3268 Box::pin(test_handler(ctx, req))
3269 });
3270 let debug = format!("{:?}", entry);
3271 assert!(debug.contains("RouteEntry"));
3272 assert!(debug.contains("Get"));
3273 assert!(debug.contains("/test"));
3274 }
3275
3276 #[test]
3277 fn app_with_middleware() {
3278 use crate::middleware::NoopMiddleware;
3279
3280 let app = App::builder()
3281 .middleware(NoopMiddleware)
3282 .middleware(NoopMiddleware)
3283 .get("/", test_handler)
3284 .build();
3285
3286 let ctx = test_context();
3287 let mut req = Request::new(Method::Get, "/");
3288
3289 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
3290 assert_eq!(response.status().as_u16(), 200);
3291 }
3292
3293 #[derive(Debug)]
3299 struct TestError {
3300 message: String,
3301 code: u32,
3302 }
3303
3304 impl std::fmt::Display for TestError {
3305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3306 write!(f, "TestError({}): {}", self.code, self.message)
3307 }
3308 }
3309
3310 impl std::error::Error for TestError {}
3311
3312 #[derive(Debug)]
3314 struct AnotherError(String);
3315
3316 impl std::fmt::Display for AnotherError {
3317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3318 write!(f, "AnotherError: {}", self.0)
3319 }
3320 }
3321
3322 impl std::error::Error for AnotherError {}
3323
3324 #[test]
3327 fn exception_handlers_new_is_empty() {
3328 let handlers = ExceptionHandlers::new();
3329 assert!(handlers.is_empty());
3330 assert_eq!(handlers.len(), 0);
3331 }
3332
3333 #[test]
3334 fn exception_handlers_register_single() {
3335 let mut handlers = ExceptionHandlers::new();
3336 handlers.register::<TestError>(|_ctx, err| {
3337 Response::with_status(StatusCode::BAD_REQUEST)
3338 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
3339 });
3340
3341 assert!(handlers.has_handler::<TestError>());
3342 assert!(!handlers.has_handler::<AnotherError>());
3343 assert_eq!(handlers.len(), 1);
3344 }
3345
3346 #[test]
3347 fn exception_handlers_register_multiple() {
3348 let mut handlers = ExceptionHandlers::new();
3349 handlers.register::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
3350 handlers.register::<AnotherError>(|_ctx, _err| {
3351 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
3352 });
3353
3354 assert!(handlers.has_handler::<TestError>());
3355 assert!(handlers.has_handler::<AnotherError>());
3356 assert_eq!(handlers.len(), 2);
3357 }
3358
3359 #[test]
3360 fn exception_handlers_builder_pattern() {
3361 let handlers = ExceptionHandlers::new()
3362 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
3363 .handler::<AnotherError>(|_ctx, _err| {
3364 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
3365 });
3366
3367 assert!(handlers.has_handler::<TestError>());
3368 assert!(handlers.has_handler::<AnotherError>());
3369 assert_eq!(handlers.len(), 2);
3370 }
3371
3372 #[test]
3373 fn exception_handlers_with_defaults() {
3374 let handlers = ExceptionHandlers::with_defaults();
3375
3376 assert!(handlers.has_handler::<crate::HttpError>());
3377 assert!(handlers.has_handler::<crate::ValidationErrors>());
3378 assert!(handlers.has_handler::<crate::CancelledError>());
3379 assert_eq!(handlers.len(), 3);
3380 }
3381
3382 #[test]
3383 fn exception_handlers_merge() {
3384 let mut handlers1 = ExceptionHandlers::new()
3385 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
3386
3387 let handlers2 = ExceptionHandlers::new().handler::<AnotherError>(|_ctx, _err| {
3388 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
3389 });
3390
3391 handlers1.merge(handlers2);
3392
3393 assert!(handlers1.has_handler::<TestError>());
3394 assert!(handlers1.has_handler::<AnotherError>());
3395 assert_eq!(handlers1.len(), 2);
3396 }
3397
3398 #[test]
3401 fn exception_handlers_handle_registered_error() {
3402 let handlers = ExceptionHandlers::new().handler::<TestError>(|_ctx, err| {
3403 Response::with_status(StatusCode::BAD_REQUEST)
3404 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
3405 });
3406
3407 let ctx = test_context();
3408 let err = TestError {
3409 message: "test error".into(),
3410 code: 42,
3411 };
3412
3413 let response = handlers.handle(&ctx, err);
3414 assert!(response.is_some());
3415
3416 let response = response.unwrap();
3417 assert_eq!(response.status().as_u16(), 400);
3418 }
3419
3420 #[test]
3421 fn exception_handlers_handle_unregistered_error() {
3422 let handlers = ExceptionHandlers::new()
3423 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
3424
3425 let ctx = test_context();
3426 let err = AnotherError("unhandled".into());
3427
3428 let response = handlers.handle(&ctx, err);
3429 assert!(response.is_none());
3430 }
3431
3432 #[test]
3433 fn exception_handlers_handle_or_default_registered() {
3434 let handlers = ExceptionHandlers::new()
3435 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
3436
3437 let ctx = test_context();
3438 let err = TestError {
3439 message: "test".into(),
3440 code: 1,
3441 };
3442
3443 let response = handlers.handle_or_default(&ctx, err);
3444 assert_eq!(response.status().as_u16(), 400);
3445 }
3446
3447 #[test]
3448 fn exception_handlers_handle_or_default_unregistered() {
3449 let handlers = ExceptionHandlers::new();
3450
3451 let ctx = test_context();
3452 let err = TestError {
3453 message: "test".into(),
3454 code: 1,
3455 };
3456
3457 let response = handlers.handle_or_default(&ctx, err);
3458 assert_eq!(response.status().as_u16(), 500);
3459 }
3460
3461 #[test]
3462 fn exception_handlers_error_values_passed_to_handler() {
3463 use std::sync::atomic::{AtomicU32, Ordering};
3464
3465 let captured_code = Arc::new(AtomicU32::new(0));
3466 let captured_code_clone = captured_code.clone();
3467
3468 let handlers = ExceptionHandlers::new().handler::<TestError>(move |_ctx, err| {
3469 captured_code_clone.store(err.code, Ordering::SeqCst);
3470 Response::with_status(StatusCode::BAD_REQUEST)
3471 });
3472
3473 let ctx = test_context();
3474 let err = TestError {
3475 message: "test".into(),
3476 code: 12345,
3477 };
3478
3479 let _ = handlers.handle(&ctx, err);
3480 assert_eq!(captured_code.load(Ordering::SeqCst), 12345);
3481 }
3482
3483 #[test]
3486 fn app_builder_exception_handler_single() {
3487 let app = App::builder()
3488 .exception_handler::<TestError, _>(|_ctx, err| {
3489 Response::with_status(StatusCode::BAD_REQUEST)
3490 .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
3491 })
3492 .get("/", test_handler)
3493 .build();
3494
3495 assert!(app.exception_handlers().has_handler::<TestError>());
3496 }
3497
3498 #[test]
3499 fn app_builder_exception_handler_multiple() {
3500 let app = App::builder()
3501 .exception_handler::<TestError, _>(|_ctx, _err| {
3502 Response::with_status(StatusCode::BAD_REQUEST)
3503 })
3504 .exception_handler::<AnotherError, _>(|_ctx, _err| {
3505 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
3506 })
3507 .get("/", test_handler)
3508 .build();
3509
3510 assert!(app.exception_handlers().has_handler::<TestError>());
3511 assert!(app.exception_handlers().has_handler::<AnotherError>());
3512 }
3513
3514 #[test]
3515 fn app_builder_with_default_exception_handlers() {
3516 let app = App::builder()
3517 .with_default_exception_handlers()
3518 .get("/", test_handler)
3519 .build();
3520
3521 assert!(app.exception_handlers().has_handler::<crate::HttpError>());
3522 assert!(
3523 app.exception_handlers()
3524 .has_handler::<crate::ValidationErrors>()
3525 );
3526 assert!(
3527 app.exception_handlers()
3528 .has_handler::<crate::CancelledError>()
3529 );
3530 }
3531
3532 #[test]
3533 fn app_builder_exception_handlers_registry() {
3534 let handlers = ExceptionHandlers::new()
3535 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
3536 .handler::<AnotherError>(|_ctx, _err| {
3537 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
3538 });
3539
3540 let app = App::builder()
3541 .exception_handlers(handlers)
3542 .get("/", test_handler)
3543 .build();
3544
3545 assert!(app.exception_handlers().has_handler::<TestError>());
3546 assert!(app.exception_handlers().has_handler::<AnotherError>());
3547 }
3548
3549 #[test]
3550 fn app_handle_error_registered() {
3551 let app = App::builder()
3552 .exception_handler::<TestError, _>(|_ctx, _err| {
3553 Response::with_status(StatusCode::BAD_REQUEST)
3554 })
3555 .get("/", test_handler)
3556 .build();
3557
3558 let ctx = test_context();
3559 let err = TestError {
3560 message: "test".into(),
3561 code: 1,
3562 };
3563
3564 let response = app.handle_error(&ctx, err);
3565 assert!(response.is_some());
3566 assert_eq!(response.unwrap().status().as_u16(), 400);
3567 }
3568
3569 #[test]
3570 fn app_handle_error_unregistered() {
3571 let app = App::builder().get("/", test_handler).build();
3572
3573 let ctx = test_context();
3574 let err = TestError {
3575 message: "test".into(),
3576 code: 1,
3577 };
3578
3579 let response = app.handle_error(&ctx, err);
3580 assert!(response.is_none());
3581 }
3582
3583 #[test]
3584 fn app_handle_error_or_default() {
3585 let app = App::builder().get("/", test_handler).build();
3586
3587 let ctx = test_context();
3588 let err = TestError {
3589 message: "test".into(),
3590 code: 1,
3591 };
3592
3593 let response = app.handle_error_or_default(&ctx, err);
3594 assert_eq!(response.status().as_u16(), 500);
3595 }
3596
3597 #[test]
3600 fn exception_handlers_override_on_register() {
3601 let handlers = ExceptionHandlers::new()
3602 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
3603 .handler::<TestError>(|_ctx, _err| {
3604 Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
3605 });
3606
3607 assert_eq!(handlers.len(), 1);
3609
3610 let ctx = test_context();
3611 let err = TestError {
3612 message: "test".into(),
3613 code: 1,
3614 };
3615
3616 let response = handlers.handle(&ctx, err);
3618 assert!(response.is_some());
3619 assert_eq!(response.unwrap().status().as_u16(), 422);
3620 }
3621
3622 #[test]
3623 fn exception_handlers_merge_overrides() {
3624 let mut handlers1 = ExceptionHandlers::new()
3625 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
3626
3627 let handlers2 = ExceptionHandlers::new().handler::<TestError>(|_ctx, _err| {
3628 Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
3629 });
3630
3631 handlers1.merge(handlers2);
3632
3633 assert_eq!(handlers1.len(), 1);
3635
3636 let ctx = test_context();
3637 let err = TestError {
3638 message: "test".into(),
3639 code: 1,
3640 };
3641
3642 let response = handlers1.handle(&ctx, err);
3644 assert!(response.is_some());
3645 assert_eq!(response.unwrap().status().as_u16(), 422);
3646 }
3647
3648 #[test]
3649 fn exception_handlers_override_default_http_error() {
3650 let mut handlers = ExceptionHandlers::with_defaults();
3652
3653 handlers.register::<crate::HttpError>(|_ctx, err| {
3655 let detail = err.detail.as_deref().unwrap_or("Unknown error");
3657 Response::with_status(err.status)
3658 .header("x-custom-error", b"true".to_vec())
3659 .body(ResponseBody::Bytes(detail.as_bytes().to_vec()))
3660 });
3661
3662 assert_eq!(handlers.len(), 3);
3664
3665 let ctx = test_context();
3666 let err = crate::HttpError::bad_request().with_detail("test error");
3667
3668 let response = handlers.handle(&ctx, err);
3669 assert!(response.is_some());
3670
3671 let response = response.unwrap();
3672 assert_eq!(response.status().as_u16(), 400);
3673
3674 let custom_header = response
3676 .headers()
3677 .iter()
3678 .find(|(name, _)| name.eq_ignore_ascii_case("x-custom-error"))
3679 .map(|(_, v)| v.as_slice());
3680 assert_eq!(custom_header, Some(b"true".as_slice()));
3681 }
3682
3683 #[test]
3684 fn exception_handlers_override_default_validation_errors() {
3685 let mut handlers = ExceptionHandlers::with_defaults();
3687
3688 handlers.register::<crate::ValidationErrors>(|_ctx, errs| {
3690 Response::with_status(StatusCode::BAD_REQUEST)
3692 .header("x-error-count", errs.len().to_string().as_bytes().to_vec())
3693 });
3694
3695 let ctx = test_context();
3696 let mut errs = crate::ValidationErrors::new();
3697 errs.push(crate::ValidationError::missing(
3698 crate::error::loc::body_field("name"),
3699 ));
3700 errs.push(crate::ValidationError::missing(
3701 crate::error::loc::body_field("email"),
3702 ));
3703
3704 let response = handlers.handle(&ctx, errs);
3705 assert!(response.is_some());
3706
3707 let response = response.unwrap();
3708 assert_eq!(response.status().as_u16(), 400);
3710
3711 let count_header = response
3713 .headers()
3714 .iter()
3715 .find(|(name, _)| name.eq_ignore_ascii_case("x-error-count"))
3716 .map(|(_, v)| v.as_slice());
3717 assert_eq!(count_header, Some(b"2".as_slice()));
3718 }
3719
3720 #[test]
3721 fn exception_handlers_default_cancelled_error() {
3722 let handlers = ExceptionHandlers::with_defaults();
3723
3724 let ctx = test_context();
3725 let err = crate::CancelledError;
3726
3727 let response = handlers.handle(&ctx, err);
3728 assert!(response.is_some());
3729
3730 let response = response.unwrap();
3731 assert_eq!(response.status().as_u16(), 499);
3733 }
3734
3735 #[test]
3736 fn exception_handlers_override_cancelled_error() {
3737 let mut handlers = ExceptionHandlers::with_defaults();
3738
3739 handlers.register::<crate::CancelledError>(|_ctx, _err| {
3741 Response::with_status(StatusCode::GATEWAY_TIMEOUT)
3742 .header("x-cancelled", b"true".to_vec())
3743 });
3744
3745 let ctx = test_context();
3746 let err = crate::CancelledError;
3747
3748 let response = handlers.handle(&ctx, err);
3749 assert!(response.is_some());
3750
3751 let response = response.unwrap();
3752 assert_eq!(response.status().as_u16(), 504);
3754
3755 let cancelled_header = response
3757 .headers()
3758 .iter()
3759 .find(|(name, _)| name.eq_ignore_ascii_case("x-cancelled"))
3760 .map(|(_, v)| v.as_slice());
3761 assert_eq!(cancelled_header, Some(b"true".as_slice()));
3762 }
3763
3764 #[test]
3765 fn exception_handlers_debug_format() {
3766 let handlers = ExceptionHandlers::new()
3767 .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
3768
3769 let debug = format!("{:?}", handlers);
3770 assert!(debug.contains("ExceptionHandlers"));
3771 assert!(debug.contains("count"));
3772 assert!(debug.contains("1"));
3773 assert!(debug.contains("has_panic_handler"));
3774 }
3775
3776 #[test]
3781 fn panic_handler_default_response() {
3782 let handlers = ExceptionHandlers::new();
3783 assert!(!handlers.has_panic_handler());
3784
3785 let response = handlers.handle_panic(None, "test panic");
3786 assert_eq!(response.status().as_u16(), 500);
3787 }
3788
3789 #[test]
3790 fn panic_handler_custom_handler() {
3791 let handlers = ExceptionHandlers::new().panic_handler(|_ctx, msg| {
3792 Response::with_status(StatusCode::SERVICE_UNAVAILABLE)
3793 .header("x-panic", msg.as_bytes().to_vec())
3794 });
3795
3796 assert!(handlers.has_panic_handler());
3797
3798 let response = handlers.handle_panic(None, "custom panic");
3799 assert_eq!(response.status().as_u16(), 503);
3800
3801 let panic_header = response
3802 .headers()
3803 .iter()
3804 .find(|(name, _)| name.eq_ignore_ascii_case("x-panic"))
3805 .map(|(_, v)| String::from_utf8_lossy(v).to_string());
3806 assert_eq!(panic_header, Some("custom panic".to_string()));
3807 }
3808
3809 #[test]
3810 fn panic_handler_with_context() {
3811 use std::sync::atomic::{AtomicBool, Ordering};
3812
3813 let ctx_received = Arc::new(AtomicBool::new(false));
3814 let ctx_received_clone = ctx_received.clone();
3815
3816 let handlers = ExceptionHandlers::new().panic_handler(move |ctx, _msg| {
3817 ctx_received_clone.store(ctx.is_some(), Ordering::SeqCst);
3818 Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
3819 });
3820
3821 let ctx = test_context();
3822 let _ = handlers.handle_panic(Some(&ctx), "panic with context");
3823
3824 assert!(ctx_received.load(Ordering::SeqCst));
3825 }
3826
3827 #[test]
3828 fn panic_handler_set_panic_handler() {
3829 let mut handlers = ExceptionHandlers::new();
3830 assert!(!handlers.has_panic_handler());
3831
3832 handlers.set_panic_handler(|_ctx, _msg| Response::with_status(StatusCode::GATEWAY_TIMEOUT));
3833
3834 assert!(handlers.has_panic_handler());
3835
3836 let response = handlers.handle_panic(None, "test");
3837 assert_eq!(response.status().as_u16(), 504);
3838 }
3839
3840 #[test]
3841 fn panic_handler_extract_panic_message_str() {
3842 let payload: Box<dyn std::any::Any + Send> = Box::new("test panic");
3844 let msg = ExceptionHandlers::extract_panic_message(&*payload);
3845 assert_eq!(msg, "test panic");
3846 }
3847
3848 #[test]
3849 fn panic_handler_extract_panic_message_string() {
3850 let payload: Box<dyn std::any::Any + Send> = Box::new("test string panic".to_string());
3852 let msg = ExceptionHandlers::extract_panic_message(&*payload);
3853 assert_eq!(msg, "test string panic");
3854 }
3855
3856 #[test]
3857 fn panic_handler_extract_panic_message_unknown() {
3858 let payload: Box<dyn std::any::Any + Send> = Box::new(42i32);
3860 let msg = ExceptionHandlers::extract_panic_message(&*payload);
3861 assert_eq!(msg, "unknown panic");
3862 }
3863
3864 #[test]
3865 fn panic_handler_merge_prefers_other() {
3866 let mut handlers1 = ExceptionHandlers::new()
3867 .panic_handler(|_ctx, _msg| Response::with_status(StatusCode::BAD_REQUEST));
3868
3869 let handlers2 = ExceptionHandlers::new()
3870 .panic_handler(|_ctx, _msg| Response::with_status(StatusCode::SERVICE_UNAVAILABLE));
3871
3872 handlers1.merge(handlers2);
3873
3874 let response = handlers1.handle_panic(None, "test");
3875 assert_eq!(response.status().as_u16(), 503);
3877 }
3878
3879 #[test]
3880 fn panic_handler_merge_keeps_existing_if_other_empty() {
3881 let mut handlers1 = ExceptionHandlers::new()
3882 .panic_handler(|_ctx, _msg| Response::with_status(StatusCode::BAD_REQUEST));
3883
3884 let handlers2 = ExceptionHandlers::new(); handlers1.merge(handlers2);
3887
3888 let response = handlers1.handle_panic(None, "test");
3889 assert_eq!(response.status().as_u16(), 400);
3891 }
3892
3893 #[test]
3894 fn panic_handler_default_panic_response() {
3895 let response = ExceptionHandlers::default_panic_response();
3896 assert_eq!(response.status().as_u16(), 500);
3897 }
3898
3899 #[test]
3900 fn app_debug_includes_exception_handlers() {
3901 let app = App::builder()
3902 .exception_handler::<TestError, _>(|_ctx, _err| {
3903 Response::with_status(StatusCode::BAD_REQUEST)
3904 })
3905 .get("/", test_handler)
3906 .build();
3907
3908 let debug = format!("{:?}", app);
3909 assert!(debug.contains("exception_handlers"));
3910 }
3911
3912 #[test]
3913 fn app_builder_debug_includes_exception_handlers() {
3914 let builder = App::builder().exception_handler::<TestError, _>(|_ctx, _err| {
3915 Response::with_status(StatusCode::BAD_REQUEST)
3916 });
3917
3918 let debug = format!("{:?}", builder);
3919 assert!(debug.contains("exception_handlers"));
3920 }
3921
3922 #[test]
3929 fn app_builder_startup_hook_registration() {
3930 let builder = App::builder().on_startup(|| Ok(())).on_startup(|| Ok(()));
3931
3932 assert_eq!(builder.startup_hook_count(), 2);
3933 }
3934
3935 #[test]
3936 fn app_builder_shutdown_hook_registration() {
3937 let builder = App::builder().on_shutdown(|| {}).on_shutdown(|| {});
3938
3939 assert_eq!(builder.shutdown_hook_count(), 2);
3940 }
3941
3942 #[test]
3943 fn app_builder_mixed_hooks() {
3944 let builder = App::builder()
3945 .on_startup(|| Ok(()))
3946 .on_shutdown(|| {})
3947 .on_startup(|| Ok(()))
3948 .on_shutdown(|| {});
3949
3950 assert_eq!(builder.startup_hook_count(), 2);
3951 assert_eq!(builder.shutdown_hook_count(), 2);
3952 }
3953
3954 #[test]
3955 fn app_pending_hooks_count() {
3956 let app = App::builder()
3957 .on_startup(|| Ok(()))
3958 .on_startup(|| Ok(()))
3959 .on_shutdown(|| {})
3960 .get("/", test_handler)
3961 .build();
3962
3963 assert_eq!(app.pending_startup_hooks(), 2);
3964 assert_eq!(app.pending_shutdown_hooks(), 1);
3965 }
3966
3967 #[test]
3970 fn startup_hooks_run_in_fifo_order() {
3971 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
3972
3973 let order1 = Arc::clone(&order);
3974 let order2 = Arc::clone(&order);
3975 let order3 = Arc::clone(&order);
3976
3977 let app = App::builder()
3978 .on_startup(move || {
3979 order1.lock().push(1);
3980 Ok(())
3981 })
3982 .on_startup(move || {
3983 order2.lock().push(2);
3984 Ok(())
3985 })
3986 .on_startup(move || {
3987 order3.lock().push(3);
3988 Ok(())
3989 })
3990 .get("/", test_handler)
3991 .build();
3992
3993 let outcome = futures_executor::block_on(app.run_startup_hooks());
3994 assert!(outcome.can_proceed());
3995
3996 assert_eq!(*order.lock(), vec![1, 2, 3]);
3998
3999 assert_eq!(app.pending_startup_hooks(), 0);
4001 }
4002
4003 #[test]
4006 fn shutdown_hooks_run_in_lifo_order() {
4007 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
4008
4009 let order1 = Arc::clone(&order);
4010 let order2 = Arc::clone(&order);
4011 let order3 = Arc::clone(&order);
4012
4013 let app = App::builder()
4014 .on_shutdown(move || {
4015 order1.lock().push(1);
4016 })
4017 .on_shutdown(move || {
4018 order2.lock().push(2);
4019 })
4020 .on_shutdown(move || {
4021 order3.lock().push(3);
4022 })
4023 .get("/", test_handler)
4024 .build();
4025
4026 futures_executor::block_on(app.run_shutdown_hooks());
4027
4028 assert_eq!(*order.lock(), vec![3, 2, 1]);
4030
4031 assert_eq!(app.pending_shutdown_hooks(), 0);
4033 }
4034
4035 #[test]
4038 fn startup_hooks_success_outcome() {
4039 let app = App::builder()
4040 .on_startup(|| Ok(()))
4041 .on_startup(|| Ok(()))
4042 .get("/", test_handler)
4043 .build();
4044
4045 let outcome = futures_executor::block_on(app.run_startup_hooks());
4046 assert!(matches!(outcome, StartupOutcome::Success));
4047 assert!(outcome.can_proceed());
4048 }
4049
4050 #[test]
4053 fn startup_hooks_fatal_error_aborts() {
4054 let app = App::builder()
4055 .on_startup(|| Ok(()))
4056 .on_startup(|| Err(StartupHookError::new("database connection failed")))
4057 .on_startup(|| Ok(())) .get("/", test_handler)
4059 .build();
4060
4061 let outcome = futures_executor::block_on(app.run_startup_hooks());
4062 assert!(!outcome.can_proceed());
4063
4064 if let StartupOutcome::Aborted(err) = outcome {
4065 assert!(err.message.contains("database connection failed"));
4066 assert!(err.abort);
4067 } else {
4068 panic!("Expected Aborted outcome");
4069 }
4070 }
4071
4072 #[test]
4075 fn startup_hooks_non_fatal_error_continues() {
4076 let app = App::builder()
4077 .on_startup(|| Ok(()))
4078 .on_startup(|| Err(StartupHookError::non_fatal("optional feature unavailable")))
4079 .on_startup(|| Ok(())) .get("/", test_handler)
4081 .build();
4082
4083 let outcome = futures_executor::block_on(app.run_startup_hooks());
4084 assert!(outcome.can_proceed());
4085
4086 if let StartupOutcome::PartialSuccess { warnings } = outcome {
4087 assert_eq!(warnings, 1);
4088 } else {
4089 panic!("Expected PartialSuccess outcome");
4090 }
4091 }
4092
4093 #[test]
4096 fn startup_hook_error_builder() {
4097 let err = StartupHookError::new("test error")
4098 .with_hook_name("database_init")
4099 .with_abort(false);
4100
4101 assert_eq!(err.hook_name.as_deref(), Some("database_init"));
4102 assert_eq!(err.message, "test error");
4103 assert!(!err.abort);
4104 }
4105
4106 #[test]
4107 fn startup_hook_error_display() {
4108 let err = StartupHookError::new("connection failed").with_hook_name("redis_init");
4109
4110 let display = format!("{}", err);
4111 assert!(display.contains("redis_init"));
4112 assert!(display.contains("connection failed"));
4113 }
4114
4115 #[test]
4116 fn startup_hook_error_non_fatal() {
4117 let err = StartupHookError::non_fatal("optional feature");
4118 assert!(!err.abort);
4119 }
4120
4121 #[test]
4124 fn transfer_shutdown_hooks_to_controller() {
4125 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
4126
4127 let order1 = Arc::clone(&order);
4128 let order2 = Arc::clone(&order);
4129
4130 let app = App::builder()
4131 .on_shutdown(move || {
4132 order1.lock().push(1);
4133 })
4134 .on_shutdown(move || {
4135 order2.lock().push(2);
4136 })
4137 .get("/", test_handler)
4138 .build();
4139
4140 let controller = ShutdownController::new();
4141 app.transfer_shutdown_hooks(&controller);
4142
4143 assert_eq!(app.pending_shutdown_hooks(), 0);
4145
4146 assert_eq!(controller.hook_count(), 2);
4148
4149 while let Some(hook) = controller.pop_hook() {
4151 hook.run();
4152 }
4153
4154 assert_eq!(*order.lock(), vec![2, 1]);
4156 }
4157
4158 #[test]
4161 fn app_debug_includes_hooks() {
4162 let app = App::builder()
4163 .on_startup(|| Ok(()))
4164 .on_shutdown(|| {})
4165 .get("/", test_handler)
4166 .build();
4167
4168 let debug = format!("{:?}", app);
4169 assert!(debug.contains("startup_hooks"));
4170 assert!(debug.contains("shutdown_hooks"));
4171 }
4172
4173 #[test]
4174 fn app_builder_debug_includes_hooks() {
4175 let builder = App::builder().on_startup(|| Ok(())).on_shutdown(|| {});
4176
4177 let debug = format!("{:?}", builder);
4178 assert!(debug.contains("startup_hooks"));
4179 assert!(debug.contains("shutdown_hooks"));
4180 }
4181
4182 #[test]
4185 fn startup_outcome_success() {
4186 let outcome = StartupOutcome::Success;
4187 assert!(outcome.can_proceed());
4188 assert!(outcome.into_error().is_none());
4189 }
4190
4191 #[test]
4192 fn startup_outcome_partial_success() {
4193 let outcome = StartupOutcome::PartialSuccess { warnings: 2 };
4194 assert!(outcome.can_proceed());
4195 assert!(outcome.into_error().is_none());
4196 }
4197
4198 #[test]
4199 fn startup_outcome_aborted() {
4200 let err = StartupHookError::new("fatal");
4201 let outcome = StartupOutcome::Aborted(err);
4202 assert!(!outcome.can_proceed());
4203
4204 let err = outcome.into_error();
4205 assert!(err.is_some());
4206 assert_eq!(err.unwrap().message, "fatal");
4207 }
4208
4209 #[test]
4212 fn startup_hooks_multiple_non_fatal_errors() {
4213 let app = App::builder()
4214 .on_startup(|| Err(StartupHookError::non_fatal("warning 1")))
4215 .on_startup(|| Ok(()))
4216 .on_startup(|| Err(StartupHookError::non_fatal("warning 2")))
4217 .on_startup(|| Err(StartupHookError::non_fatal("warning 3")))
4218 .get("/", test_handler)
4219 .build();
4220
4221 let outcome = futures_executor::block_on(app.run_startup_hooks());
4222 assert!(outcome.can_proceed());
4223
4224 if let StartupOutcome::PartialSuccess { warnings } = outcome {
4225 assert_eq!(warnings, 3);
4226 } else {
4227 panic!("Expected PartialSuccess");
4228 }
4229 }
4230
4231 #[test]
4234 fn empty_startup_hooks() {
4235 let app = App::builder().get("/", test_handler).build();
4236
4237 let outcome = futures_executor::block_on(app.run_startup_hooks());
4238 assert!(matches!(outcome, StartupOutcome::Success));
4239 }
4240
4241 #[test]
4242 fn empty_shutdown_hooks() {
4243 let app = App::builder().get("/", test_handler).build();
4244
4245 futures_executor::block_on(app.run_shutdown_hooks());
4247 }
4248
4249 #[test]
4252 fn startup_hooks_consumed_after_run() {
4253 let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
4254 let counter_clone = Arc::clone(&counter);
4255
4256 let app = App::builder()
4257 .on_startup(move || {
4258 counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4259 Ok(())
4260 })
4261 .get("/", test_handler)
4262 .build();
4263
4264 futures_executor::block_on(app.run_startup_hooks());
4266 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4267
4268 futures_executor::block_on(app.run_startup_hooks());
4270 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4271 }
4272
4273 #[test]
4274 fn shutdown_hooks_consumed_after_run() {
4275 let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
4276 let counter_clone = Arc::clone(&counter);
4277
4278 let app = App::builder()
4279 .on_shutdown(move || {
4280 counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4281 })
4282 .get("/", test_handler)
4283 .build();
4284
4285 futures_executor::block_on(app.run_shutdown_hooks());
4287 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4288
4289 futures_executor::block_on(app.run_shutdown_hooks());
4291 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4292 }
4293
4294 #[test]
4299 fn async_startup_hook_runs() {
4300 let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
4301 let counter_clone = Arc::clone(&counter);
4302
4303 let app = App::builder()
4304 .on_startup_async(move || {
4305 let counter = Arc::clone(&counter_clone);
4306 async move {
4307 counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4308 Ok(())
4309 }
4310 })
4311 .get("/", test_handler)
4312 .build();
4313
4314 let outcome = futures_executor::block_on(app.run_startup_hooks());
4315 assert!(outcome.can_proceed());
4316 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4317 }
4318
4319 #[test]
4320 fn async_startup_hook_error_aborts() {
4321 let app = App::builder()
4322 .on_startup_async(|| async { Err(StartupHookError::new("async connection failed")) })
4323 .get("/", test_handler)
4324 .build();
4325
4326 let outcome = futures_executor::block_on(app.run_startup_hooks());
4327 assert!(!outcome.can_proceed());
4328
4329 if let StartupOutcome::Aborted(err) = outcome {
4330 assert!(err.message.contains("async connection failed"));
4331 } else {
4332 panic!("Expected Aborted outcome");
4333 }
4334 }
4335
4336 #[test]
4337 fn async_shutdown_hook_runs() {
4338 let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
4339 let counter_clone = Arc::clone(&counter);
4340
4341 let app = App::builder()
4342 .on_shutdown_async(move || {
4343 let counter = Arc::clone(&counter_clone);
4344 async move {
4345 counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4346 }
4347 })
4348 .get("/", test_handler)
4349 .build();
4350
4351 futures_executor::block_on(app.run_shutdown_hooks());
4352 assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4353 }
4354
4355 #[test]
4356 fn mixed_sync_and_async_startup_hooks() {
4357 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
4358
4359 let order1 = Arc::clone(&order);
4360 let order2 = Arc::clone(&order);
4361 let order3 = Arc::clone(&order);
4362
4363 let app = App::builder()
4364 .on_startup(move || {
4365 order1.lock().push(1);
4366 Ok(())
4367 })
4368 .on_startup_async(move || {
4369 let order = Arc::clone(&order2);
4370 async move {
4371 order.lock().push(2);
4372 Ok(())
4373 }
4374 })
4375 .on_startup(move || {
4376 order3.lock().push(3);
4377 Ok(())
4378 })
4379 .get("/", test_handler)
4380 .build();
4381
4382 let outcome = futures_executor::block_on(app.run_startup_hooks());
4383 assert!(outcome.can_proceed());
4384
4385 assert_eq!(*order.lock(), vec![1, 2, 3]);
4387 }
4388
4389 #[test]
4390 fn mixed_sync_and_async_shutdown_hooks() {
4391 let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
4392
4393 let order1 = Arc::clone(&order);
4394 let order2 = Arc::clone(&order);
4395 let order3 = Arc::clone(&order);
4396
4397 let app = App::builder()
4398 .on_shutdown(move || {
4399 order1.lock().push(1);
4400 })
4401 .on_shutdown_async(move || {
4402 let order = Arc::clone(&order2);
4403 async move {
4404 order.lock().push(2);
4405 }
4406 })
4407 .on_shutdown(move || {
4408 order3.lock().push(3);
4409 })
4410 .get("/", test_handler)
4411 .build();
4412
4413 futures_executor::block_on(app.run_shutdown_hooks());
4414
4415 assert_eq!(*order.lock(), vec![2, 3, 1]);
4418 }
4419
4420 #[test]
4425 fn state_accessible_via_app_get_state() {
4426 #[derive(Debug, Clone)]
4427 struct DatabasePool {
4428 connection_count: usize,
4429 }
4430
4431 #[derive(Debug, Clone)]
4432 struct CacheClient {
4433 max_entries: usize,
4434 }
4435
4436 let app = App::builder()
4437 .with_state(DatabasePool {
4438 connection_count: 10,
4439 })
4440 .with_state(CacheClient { max_entries: 1000 })
4441 .get("/", test_handler)
4442 .build();
4443
4444 let db = app.get_state::<DatabasePool>();
4446 assert!(db.is_some());
4447 assert_eq!(db.unwrap().connection_count, 10);
4448
4449 let cache = app.get_state::<CacheClient>();
4450 assert!(cache.is_some());
4451 assert_eq!(cache.unwrap().max_entries, 1000);
4452
4453 let missing = app.get_state::<String>();
4455 assert!(missing.is_none());
4456 }
4457
4458 #[test]
4459 fn state_container_replace_on_duplicate_type() {
4460 struct Counter(u32);
4461
4462 let mut container = StateContainer::new();
4463 container.insert(Counter(1));
4464 assert_eq!(container.get::<Counter>().unwrap().0, 1);
4465
4466 container.insert(Counter(42));
4468 assert_eq!(container.get::<Counter>().unwrap().0, 42);
4469
4470 assert_eq!(container.len(), 1);
4472 }
4473
4474 #[test]
4475 fn state_container_empty_checks() {
4476 let container = StateContainer::new();
4477 assert!(container.is_empty());
4478 assert_eq!(container.len(), 0);
4479
4480 let mut container = StateContainer::new();
4481 container.insert(42i32);
4482 assert!(!container.is_empty());
4483 assert_eq!(container.len(), 1);
4484 }
4485
4486 #[test]
4491 fn app_config_validation_rejects_empty_version() {
4492 let config = AppConfig {
4493 version: String::new(),
4494 ..Default::default()
4495 };
4496 let err = config.validate().expect_err("empty version invalid");
4497 assert!(matches!(err, ConfigError::Validation(_)));
4498 }
4499
4500 #[test]
4501 fn app_config_validation_rejects_zero_body_size() {
4502 let config = AppConfig {
4503 max_body_size: 0,
4504 ..Default::default()
4505 };
4506 let err = config.validate().expect_err("zero body size invalid");
4507 assert!(matches!(err, ConfigError::Validation(_)));
4508 }
4509
4510 #[test]
4511 fn app_config_validation_rejects_zero_timeout() {
4512 let config = AppConfig {
4513 request_timeout_ms: 0,
4514 ..Default::default()
4515 };
4516 let err = config.validate().expect_err("zero timeout invalid");
4517 assert!(matches!(err, ConfigError::Validation(_)));
4518 }
4519
4520 #[test]
4521 fn app_config_debug_bool_parsing() {
4522 assert!(parse_bool("test", "true").unwrap());
4524 assert!(parse_bool("test", "TRUE").unwrap());
4525 assert!(parse_bool("test", "1").unwrap());
4526 assert!(parse_bool("test", "yes").unwrap());
4527 assert!(parse_bool("test", "YES").unwrap());
4528 assert!(parse_bool("test", "on").unwrap());
4529 assert!(parse_bool("test", "ON").unwrap());
4530
4531 assert!(!parse_bool("test", "false").unwrap());
4532 assert!(!parse_bool("test", "FALSE").unwrap());
4533 assert!(!parse_bool("test", "0").unwrap());
4534 assert!(!parse_bool("test", "no").unwrap());
4535 assert!(!parse_bool("test", "NO").unwrap());
4536 assert!(!parse_bool("test", "off").unwrap());
4537 assert!(!parse_bool("test", "OFF").unwrap());
4538
4539 assert!(parse_bool("test", "maybe").is_err());
4541 assert!(parse_bool("test", "2").is_err());
4542 }
4543
4544 #[test]
4545 fn app_config_unsupported_format() {
4546 let err = AppConfig::from_file("/tmp/config.yaml");
4547 assert!(matches!(err, Err(ConfigError::UnsupportedFormat { .. })));
4548 }
4549
4550 #[test]
4555 fn full_lifecycle_startup_serve_shutdown() {
4556 let lifecycle_log = Arc::new(parking_lot::Mutex::new(Vec::new()));
4557
4558 let log1 = Arc::clone(&lifecycle_log);
4559 let log2 = Arc::clone(&lifecycle_log);
4560 let log3 = Arc::clone(&lifecycle_log);
4561 let log4 = Arc::clone(&lifecycle_log);
4562
4563 let app = App::builder()
4564 .on_startup(move || {
4565 log1.lock().push("startup_1");
4566 Ok(())
4567 })
4568 .on_startup(move || {
4569 log2.lock().push("startup_2");
4570 Ok(())
4571 })
4572 .on_shutdown(move || {
4573 log3.lock().push("shutdown_1");
4574 })
4575 .on_shutdown(move || {
4576 log4.lock().push("shutdown_2");
4577 })
4578 .get("/", test_handler)
4579 .build();
4580
4581 let outcome = futures_executor::block_on(app.run_startup_hooks());
4583 assert!(outcome.can_proceed());
4584
4585 let ctx = test_context();
4587 let mut req = Request::new(Method::Get, "/");
4588 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
4589 assert_eq!(response.status().as_u16(), 200);
4590
4591 futures_executor::block_on(app.run_shutdown_hooks());
4593
4594 let log = lifecycle_log.lock();
4596 assert_eq!(
4597 *log,
4598 vec!["startup_1", "startup_2", "shutdown_2", "shutdown_1"]
4599 );
4600 }
4601
4602 #[test]
4603 fn lifecycle_startup_failure_prevents_serving() {
4604 let serve_attempted = Arc::new(std::sync::atomic::AtomicBool::new(false));
4605
4606 let app = App::builder()
4607 .on_startup(|| Err(StartupHookError::new("database unavailable")))
4608 .get("/", test_handler)
4609 .build();
4610
4611 let outcome = futures_executor::block_on(app.run_startup_hooks());
4612
4613 if outcome.can_proceed() {
4615 serve_attempted.store(true, std::sync::atomic::Ordering::SeqCst);
4616 }
4617
4618 assert!(!serve_attempted.load(std::sync::atomic::Ordering::SeqCst));
4619 assert!(matches!(outcome, StartupOutcome::Aborted(_)));
4620 }
4621
4622 #[test]
4623 fn lifecycle_with_state_initialization() {
4624 #[derive(Debug)]
4625 struct AppState {
4626 initialized: std::sync::atomic::AtomicBool,
4627 }
4628
4629 let state = Arc::new(AppState {
4630 initialized: std::sync::atomic::AtomicBool::new(false),
4631 });
4632 let state_for_hook = Arc::clone(&state);
4633
4634 let app = App::builder()
4635 .on_startup(move || {
4636 state_for_hook
4637 .initialized
4638 .store(true, std::sync::atomic::Ordering::SeqCst);
4639 Ok(())
4640 })
4641 .get("/", test_handler)
4642 .build();
4643
4644 assert!(!state.initialized.load(std::sync::atomic::Ordering::SeqCst));
4646
4647 let outcome = futures_executor::block_on(app.run_startup_hooks());
4649 assert!(outcome.can_proceed());
4650
4651 assert!(state.initialized.load(std::sync::atomic::Ordering::SeqCst));
4653 }
4654
4655 #[test]
4656 fn lifecycle_shutdown_runs_even_after_failed_startup() {
4657 let shutdown_ran = Arc::new(std::sync::atomic::AtomicBool::new(false));
4658 let shutdown_flag = Arc::clone(&shutdown_ran);
4659
4660 let app = App::builder()
4661 .on_startup(|| Err(StartupHookError::new("startup failed")))
4662 .on_shutdown(move || {
4663 shutdown_flag.store(true, std::sync::atomic::Ordering::SeqCst);
4664 })
4665 .get("/", test_handler)
4666 .build();
4667
4668 let outcome = futures_executor::block_on(app.run_startup_hooks());
4670 assert!(!outcome.can_proceed());
4671
4672 futures_executor::block_on(app.run_shutdown_hooks());
4674 assert!(shutdown_ran.load(std::sync::atomic::Ordering::SeqCst));
4675 }
4676
4677 #[test]
4678 fn multiple_lifecycle_phases_with_async_hooks() {
4679 let log = Arc::new(parking_lot::Mutex::new(Vec::<&str>::new()));
4680
4681 let log1 = Arc::clone(&log);
4682 let log2 = Arc::clone(&log);
4683 let log3 = Arc::clone(&log);
4684 let log4 = Arc::clone(&log);
4685
4686 let app = App::builder()
4687 .on_startup(move || {
4688 log1.lock().push("sync_startup");
4689 Ok(())
4690 })
4691 .on_startup_async(move || {
4692 let log = Arc::clone(&log2);
4693 async move {
4694 log.lock().push("async_startup");
4695 Ok(())
4696 }
4697 })
4698 .on_shutdown(move || {
4699 log3.lock().push("sync_shutdown");
4700 })
4701 .on_shutdown_async(move || {
4702 let log = Arc::clone(&log4);
4703 async move {
4704 log.lock().push("async_shutdown");
4705 }
4706 })
4707 .get("/", test_handler)
4708 .build();
4709
4710 let outcome = futures_executor::block_on(app.run_startup_hooks());
4712 assert!(outcome.can_proceed());
4713
4714 futures_executor::block_on(app.run_shutdown_hooks());
4715
4716 let events = log.lock();
4718 assert_eq!(
4719 *events,
4720 vec![
4721 "sync_startup",
4722 "async_startup",
4723 "async_shutdown",
4724 "sync_shutdown"
4725 ]
4726 );
4727 }
4728
4729 #[test]
4734 fn lifespan_scope_creation() {
4735 let scope = LifespanScope::new(42i32);
4736 assert_eq!(scope.state, 42);
4737 }
4738
4739 #[test]
4740 fn lifespan_scope_with_cleanup() {
4741 let cleanup_called = Arc::new(Mutex::new(false));
4742 let cleanup_called_clone = Arc::clone(&cleanup_called);
4743
4744 let mut scope = LifespanScope::new("state").on_shutdown(async move {
4745 *cleanup_called_clone.lock().unwrap() = true;
4746 });
4747
4748 assert!(!*cleanup_called.lock().unwrap());
4750
4751 let cleanup = scope.take_cleanup();
4753 assert!(cleanup.is_some());
4754
4755 futures_executor::block_on(cleanup.unwrap());
4757 assert!(*cleanup_called.lock().unwrap());
4758 }
4759
4760 #[test]
4761 fn lifespan_error_display() {
4762 let err = LifespanError::new("connection failed");
4763 assert!(err.to_string().contains("connection failed"));
4764 assert!(err.source.is_none());
4765
4766 let io_err = std::io::Error::other("disk full");
4767 let err_with_source = LifespanError::with_source("backup failed", io_err);
4768 assert!(err_with_source.to_string().contains("backup failed"));
4769 assert!(err_with_source.source.is_some());
4770 }
4771
4772 #[test]
4773 fn lifespan_error_into_startup_hook_error() {
4774 let err = LifespanError::new("startup failed");
4775 let hook_err: StartupHookError = err.into();
4776 assert!(hook_err.abort);
4777 assert!(hook_err.message.contains("startup failed"));
4778 }
4779
4780 struct TestDbPool {
4782 connection_count: i32,
4783 }
4784
4785 #[test]
4786 fn lifespan_injects_state() {
4787 let app = App::builder()
4788 .lifespan(|| async {
4789 let pool = TestDbPool {
4790 connection_count: 10,
4791 };
4792 Ok(LifespanScope::new(pool))
4793 })
4794 .get("/", test_handler)
4795 .build();
4796
4797 let outcome = futures_executor::block_on(app.run_startup_hooks());
4799 assert!(outcome.can_proceed());
4800
4801 let pool = app.get_state::<TestDbPool>();
4803 assert!(pool.is_some());
4804 assert_eq!(pool.unwrap().connection_count, 10);
4805 }
4806
4807 #[test]
4808 fn lifespan_runs_cleanup_on_shutdown() {
4809 let cleanup_log = Arc::new(Mutex::new(Vec::<&'static str>::new()));
4810 let log_clone = Arc::clone(&cleanup_log);
4811
4812 let app = App::builder()
4813 .lifespan(move || {
4814 let log = Arc::clone(&log_clone);
4815 async move {
4816 let pool = TestDbPool {
4817 connection_count: 5,
4818 };
4819 Ok(LifespanScope::new(pool).on_shutdown(async move {
4820 log.lock().unwrap().push("cleanup");
4821 }))
4822 }
4823 })
4824 .get("/", test_handler)
4825 .build();
4826
4827 let outcome = futures_executor::block_on(app.run_startup_hooks());
4829 assert!(outcome.can_proceed());
4830
4831 assert!(cleanup_log.lock().unwrap().is_empty());
4833
4834 futures_executor::block_on(app.run_shutdown_hooks());
4836
4837 assert_eq!(*cleanup_log.lock().unwrap(), vec!["cleanup"]);
4839 }
4840
4841 #[test]
4842 fn lifespan_error_aborts_startup() {
4843 let app = App::builder()
4844 .lifespan(|| async {
4845 Err::<LifespanScope<()>, _>(LifespanError::new("database connection failed"))
4846 })
4847 .get("/", test_handler)
4848 .build();
4849
4850 let outcome = futures_executor::block_on(app.run_startup_hooks());
4851
4852 match outcome {
4853 StartupOutcome::Aborted(err) => {
4854 assert!(err.message.contains("database connection failed"));
4855 assert!(err.abort);
4856 }
4857 _ => panic!("expected Aborted outcome"),
4858 }
4859 }
4860
4861 #[test]
4862 fn lifespan_runs_before_other_startup_hooks() {
4863 let log = Arc::new(Mutex::new(Vec::<&'static str>::new()));
4864 let log1 = Arc::clone(&log);
4865 let log2 = Arc::clone(&log);
4866
4867 let app = App::builder()
4868 .on_startup(move || {
4869 log1.lock().unwrap().push("regular_hook");
4870 Ok(())
4871 })
4872 .lifespan(move || {
4873 let log = Arc::clone(&log2);
4874 async move {
4875 log.lock().unwrap().push("lifespan");
4876 Ok(LifespanScope::new(()))
4877 }
4878 })
4879 .get("/", test_handler)
4880 .build();
4881
4882 futures_executor::block_on(app.run_startup_hooks());
4883
4884 let events = log.lock().unwrap();
4886 assert_eq!(*events, vec!["lifespan", "regular_hook"]);
4887 }
4888
4889 #[test]
4890 fn lifespan_cleanup_runs_after_other_shutdown_hooks() {
4891 let log = Arc::new(Mutex::new(Vec::<&'static str>::new()));
4892 let log1 = Arc::clone(&log);
4893 let log2 = Arc::clone(&log);
4894
4895 let app = App::builder()
4896 .on_shutdown(move || {
4897 log1.lock().unwrap().push("regular_hook");
4898 })
4899 .lifespan(move || {
4900 let log = Arc::clone(&log2);
4901 async move {
4902 Ok(LifespanScope::new(()).on_shutdown(async move {
4903 log.lock().unwrap().push("lifespan_cleanup");
4904 }))
4905 }
4906 })
4907 .get("/", test_handler)
4908 .build();
4909
4910 futures_executor::block_on(app.run_startup_hooks());
4911 futures_executor::block_on(app.run_shutdown_hooks());
4912
4913 let events = log.lock().unwrap();
4915 assert_eq!(*events, vec!["regular_hook", "lifespan_cleanup"]);
4916 }
4917
4918 #[test]
4923 fn mounted_app_prefix_matching() {
4924 let mounted = MountedApp::new("/admin", App::builder().build());
4925
4926 assert_eq!(mounted.match_prefix("/admin"), Some("/"));
4928
4929 assert_eq!(mounted.match_prefix("/admin/users"), Some("/users"));
4931 assert_eq!(mounted.match_prefix("/admin/users/123"), Some("/users/123"));
4932
4933 assert_eq!(mounted.match_prefix("/api"), None);
4935 assert_eq!(mounted.match_prefix("/"), None);
4936
4937 assert_eq!(mounted.match_prefix("/administrator"), None);
4939 }
4940
4941 #[test]
4942 fn mounted_app_prefix_normalization() {
4943 let mounted = MountedApp::new("admin", App::builder().build());
4945 assert_eq!(mounted.prefix(), "/admin");
4946
4947 let mounted = MountedApp::new("/admin/", App::builder().build());
4949 assert_eq!(mounted.prefix(), "/admin");
4950
4951 let mounted = MountedApp::new("/admin///", App::builder().build());
4953 assert_eq!(mounted.prefix(), "/admin");
4954 }
4955
4956 #[test]
4957 fn app_builder_mount_adds_mounted_app() {
4958 let sub_app = App::builder().get("/", test_handler).build();
4959
4960 let app = App::builder()
4961 .get("/", test_handler)
4962 .mount("/admin", sub_app)
4963 .build();
4964
4965 assert_eq!(app.mounted_apps().len(), 1);
4966 assert_eq!(app.mounted_apps()[0].prefix(), "/admin");
4967 }
4968
4969 #[test]
4970 fn app_builder_multiple_mounts() {
4971 let admin_app = App::builder().get("/", test_handler).build();
4972 let api_app = App::builder().get("/", test_handler).build();
4973 let docs_app = App::builder().get("/", test_handler).build();
4974
4975 let app = App::builder()
4976 .get("/", test_handler)
4977 .mount("/admin", admin_app)
4978 .mount("/api", api_app)
4979 .mount("/docs", docs_app)
4980 .build();
4981
4982 assert_eq!(app.mounted_apps().len(), 3);
4983 assert_eq!(app.mounted_apps()[0].prefix(), "/admin");
4984 assert_eq!(app.mounted_apps()[1].prefix(), "/api");
4985 assert_eq!(app.mounted_apps()[2].prefix(), "/docs");
4986 }
4987
4988 fn admin_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
4989 std::future::ready(Response::ok().body(ResponseBody::Bytes(b"Admin Panel".to_vec())))
4990 }
4991
4992 #[test]
4993 fn app_routes_to_mounted_app() {
4994 let admin_app = App::builder()
4995 .get("/", admin_handler)
4996 .get("/users", admin_handler)
4997 .build();
4998
4999 let app = App::builder()
5000 .get("/", test_handler)
5001 .mount("/admin", admin_app)
5002 .build();
5003
5004 let ctx = test_context();
5005
5006 let mut req = Request::new(Method::Get, "/");
5008 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
5009 assert_eq!(response.status().as_u16(), 200);
5010 if let ResponseBody::Bytes(body) = response.body_ref() {
5011 assert_eq!(body.as_slice(), b"Hello, World!");
5012 }
5013
5014 let mut req = Request::new(Method::Get, "/admin");
5016 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
5017 assert_eq!(response.status().as_u16(), 200);
5018 if let ResponseBody::Bytes(body) = response.body_ref() {
5019 assert_eq!(body.as_slice(), b"Admin Panel");
5020 }
5021
5022 let mut req = Request::new(Method::Get, "/admin/users");
5024 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
5025 assert_eq!(response.status().as_u16(), 200);
5026 if let ResponseBody::Bytes(body) = response.body_ref() {
5027 assert_eq!(body.as_slice(), b"Admin Panel");
5028 }
5029 }
5030
5031 #[test]
5032 fn mounted_app_404_for_unknown_routes() {
5033 let admin_app = App::builder().get("/users", admin_handler).build();
5034
5035 let app = App::builder()
5036 .get("/", test_handler)
5037 .mount("/admin", admin_app)
5038 .build();
5039
5040 let ctx = test_context();
5041
5042 let mut req = Request::new(Method::Get, "/admin/unknown");
5044 let response = futures_executor::block_on(app.handle(&ctx, &mut req));
5045 assert_eq!(response.status().as_u16(), 404);
5046 }
5047
5048 #[test]
5053 fn app_config_debug_config_default() {
5054 let config = AppConfig::default();
5055 assert!(!config.debug_config.enabled);
5056 assert!(config.debug_config.debug_header.is_none());
5057 assert!(config.debug_config.debug_token.is_none());
5058 assert!(!config.debug_config.allow_unauthenticated);
5059 }
5060
5061 #[test]
5062 fn app_config_debug_config_builder() {
5063 let config = AppConfig::new().debug_config(
5064 crate::error::DebugConfig::new()
5065 .enable()
5066 .with_debug_header("X-Debug-Token", "secret-abc"),
5067 );
5068
5069 assert!(config.debug_config.enabled);
5070 assert_eq!(
5071 config.debug_config.debug_header,
5072 Some("X-Debug-Token".to_owned())
5073 );
5074 assert_eq!(
5075 config.debug_config.debug_token,
5076 Some("secret-abc".to_owned())
5077 );
5078 }
5079
5080 #[test]
5081 fn app_config_debug_config_unauthenticated() {
5082 let config = AppConfig::new().debug_config(
5083 crate::error::DebugConfig::new()
5084 .enable()
5085 .allow_unauthenticated(),
5086 );
5087
5088 assert!(config.debug_config.enabled);
5089 assert!(config.debug_config.allow_unauthenticated);
5090 }
5091
5092 #[test]
5093 fn app_debug_config_accessible_from_app() {
5094 let app = App::builder()
5095 .config(
5096 AppConfig::new().debug_config(
5097 crate::error::DebugConfig::new()
5098 .enable()
5099 .with_debug_header("X-Debug", "tok123"),
5100 ),
5101 )
5102 .get("/", test_handler)
5103 .build();
5104
5105 assert!(app.config().debug_config.enabled);
5106 assert_eq!(
5107 app.config().debug_config.debug_header,
5108 Some("X-Debug".to_owned())
5109 );
5110 }
5111
5112 #[test]
5113 fn app_debug_config_is_authorized_integration() {
5114 let config = AppConfig::new().debug_config(
5115 crate::error::DebugConfig::new()
5116 .enable()
5117 .with_debug_header("X-Debug-Token", "my-secret"),
5118 );
5119
5120 let headers = vec![("X-Debug-Token".to_owned(), b"my-secret".to_vec())];
5122 assert!(config.debug_config.is_authorized(&headers));
5123
5124 let headers = vec![("X-Debug-Token".to_owned(), b"wrong".to_vec())];
5126 assert!(!config.debug_config.is_authorized(&headers));
5127
5128 let headers: Vec<(String, Vec<u8>)> = vec![];
5130 assert!(!config.debug_config.is_authorized(&headers));
5131 }
5132}