Skip to main content

fastapi_core/
app.rs

1//! Application builder and runtime for fastapi_rust.
2//!
3//! This module provides a fluent API for building web applications with
4//! type-safe route registration, middleware ordering, and shared state.
5//!
6//! # Design Principles
7//!
8//! - **Fluent Builder API**: Chain methods to configure the application
9//! - **Type-Safe State**: Shared state is type-checked at compile time
10//! - **Explicit Middleware Order**: Middleware runs in registration order
11//! - **Compile-Time Validation**: Invalid configurations fail at compile time
12//!
13//! # Example
14//!
15//! ```ignore
16//! use fastapi_core::app::{App, AppBuilder};
17//! use fastapi_core::{Request, Response, RequestContext};
18//!
19//! async fn hello(ctx: &RequestContext, req: &mut Request) -> Response {
20//!     Response::ok().body_text("Hello, World!")
21//! }
22//!
23//! async fn health(ctx: &RequestContext, req: &mut Request) -> Response {
24//!     Response::ok().body_json(&serde_json::json!({"status": "healthy"}))
25//! }
26//!
27//! let app = App::builder()
28//!     .route("/", Method::Get, hello)
29//!     .route("/health", Method::Get, health)
30//!     .middleware(RequestIdMiddleware::new())
31//!     .middleware(LoggingMiddleware::new())
32//!     .build();
33//! ```
34
35use 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
55// ============================================================================
56// Type-Safe State Registry (Compile-Time State Tracking)
57// ============================================================================
58
59/// Marker trait for the type-level state registry.
60///
61/// The state registry is a type-level set represented as nested tuples:
62/// - `()` represents the empty set
63/// - `(T, S)` represents the set containing T plus all types in S
64///
65/// This enables compile-time verification that state types are registered
66/// before they are used by handlers.
67pub trait StateRegistry: Send + Sync + 'static {}
68
69impl StateRegistry for () {}
70impl<T: Send + Sync + 'static, S: StateRegistry> StateRegistry for (T, S) {}
71
72/// Marker trait indicating that type T is present in state registry S.
73///
74/// This trait is automatically implemented for any type T that appears
75/// in the nested tuple structure of S.
76///
77/// # Example
78///
79/// ```ignore
80/// // (DbPool, (Config, ())) contains both DbPool and Config
81/// fn requires_db<S: HasState<DbPool>>() {}
82/// fn requires_config<S: HasState<Config>>() {}
83///
84/// // Both work with (DbPool, (Config, ()))
85/// type MyState = (DbPool, (Config, ()));
86/// requires_db::<MyState>();   // compiles
87/// requires_config::<MyState>(); // compiles
88/// ```
89pub trait HasState<T>: StateRegistry {}
90
91// T is in (T, S) - direct match (at head of tuple)
92impl<T: Send + Sync + 'static, S: StateRegistry> HasState<T> for (T, S) {}
93
94// Note: A recursive impl like "T is in (U, S) if T is in S" would conflict
95// with the direct impl when T == U. Without negative bounds or specialization,
96// we can only support checking for types at specific positions in the tuple.
97// For multiple state types, users can define impls manually for their specific
98// tuple structure, or use a different state organization.
99
100/// Trait for types that require specific state to be registered.
101///
102/// This is implemented by extractors like `State<T>` to declare their
103/// state dependencies at the type level.
104///
105/// # Example
106///
107/// ```ignore
108/// // State<DbPool> requires DbPool to be in the registry
109/// impl<S: HasState<DbPool>> RequiresState<S> for State<DbPool> {}
110/// ```
111pub trait RequiresState<S: StateRegistry> {}
112
113// All types that don't need state trivially satisfy RequiresState
114impl<S: StateRegistry> RequiresState<S> for () {}
115
116// ============================================================================
117// Lifecycle Hook Types
118// ============================================================================
119
120/// A startup hook that runs before the server starts accepting connections.
121pub enum StartupHook {
122    /// Synchronous startup function.
123    Sync(Box<dyn FnOnce() -> Result<(), StartupHookError> + Send>),
124    /// Factory for async startup future.
125    AsyncFactory(
126        Box<
127            dyn FnOnce() -> Pin<Box<dyn Future<Output = Result<(), StartupHookError>> + Send>>
128                + Send,
129        >,
130    ),
131}
132
133impl StartupHook {
134    /// Create a synchronous startup hook.
135    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    /// Create an async startup hook.
143    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    /// Run the hook synchronously.
152    ///
153    /// For async hooks, this returns the future to await.
154    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/// Error returned when a startup hook fails.
168#[derive(Debug)]
169pub struct StartupHookError {
170    /// Name of the hook that failed (if provided).
171    pub hook_name: Option<String>,
172    /// The underlying error message.
173    pub message: String,
174    /// Whether the application should abort startup.
175    pub abort: bool,
176}
177
178impl StartupHookError {
179    /// Create a new startup hook error.
180    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    /// Set the hook name.
189    #[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    /// Set whether to abort startup.
196    #[must_use]
197    pub fn with_abort(mut self, abort: bool) -> Self {
198        self.abort = abort;
199        self
200    }
201
202    /// Create an error that doesn't abort startup (just logs warning).
203    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/// Outcome of running all startup hooks.
225#[derive(Debug)]
226pub enum StartupOutcome {
227    /// All hooks succeeded.
228    Success,
229    /// Some hooks had non-fatal errors (logged but continued).
230    PartialSuccess {
231        /// Number of hooks that failed with non-fatal errors.
232        warnings: usize,
233    },
234    /// A fatal hook error aborted startup.
235    Aborted(StartupHookError),
236}
237
238impl StartupOutcome {
239    /// Returns true if startup can proceed (Success or PartialSuccess).
240    #[must_use]
241    pub fn can_proceed(&self) -> bool {
242        !matches!(self, Self::Aborted(_))
243    }
244
245    /// Returns the abort error, if any.
246    pub fn into_error(self) -> Option<StartupHookError> {
247        match self {
248            Self::Aborted(e) => Some(e),
249            _ => None,
250        }
251    }
252}
253
254// ============================================================================
255// Lifespan Context Manager
256// ============================================================================
257
258/// Error during lifespan startup.
259///
260/// This error is returned when the lifespan function fails during the startup phase.
261/// It will abort the application startup, preventing the server from accepting connections.
262#[derive(Debug)]
263pub struct LifespanError {
264    /// Description of what went wrong.
265    pub message: String,
266    /// Optional underlying error source.
267    pub source: Option<Box<dyn std::error::Error + Send + Sync>>,
268}
269
270impl LifespanError {
271    /// Creates a new lifespan error with a message.
272    pub fn new(message: impl Into<String>) -> Self {
273        Self {
274            message: message.into(),
275            source: None,
276        }
277    }
278
279    /// Creates a lifespan error wrapping another error.
280    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
315/// Scope returned by a lifespan function containing state and cleanup logic.
316///
317/// The lifespan pattern allows sharing state between startup and shutdown phases,
318/// which is particularly useful for resources like database connections that need
319/// coordinated initialization and cleanup.
320///
321/// # Example
322///
323/// ```ignore
324/// use fastapi_core::app::{App, LifespanScope, LifespanError};
325///
326/// struct DatabasePool { /* ... */ }
327///
328/// impl DatabasePool {
329///     async fn connect(url: &str) -> Result<Self, Error> { /* ... */ }
330///     async fn close(&self) { /* ... */ }
331/// }
332///
333/// let app = App::builder()
334///     .lifespan(|| async {
335///         // Startup: connect to database
336///         let pool = DatabasePool::connect("postgres://localhost/mydb")
337///             .await
338///             .map_err(|e| LifespanError::with_source("failed to connect to database", e))?;
339///
340///         // Clone for the cleanup closure
341///         let pool_for_cleanup = pool.clone();
342///
343///         // Return state + cleanup
344///         Ok(LifespanScope::new(pool)
345///             .on_shutdown(async move {
346///                 pool_for_cleanup.close().await;
347///             }))
348///     })
349///     .build();
350/// ```
351pub struct LifespanScope<T: Send + Sync + 'static> {
352    /// State produced by the lifespan function.
353    ///
354    /// This state is automatically added to the application's state container
355    /// and can be accessed by handlers via the `State<T>` extractor.
356    pub state: T,
357
358    /// Optional cleanup future to run during shutdown.
359    cleanup: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
360}
361
362impl<T: Send + Sync + 'static> LifespanScope<T> {
363    /// Creates a new lifespan scope with the given state.
364    ///
365    /// The state will be added to the application's state container after
366    /// successful startup, accessible via the `State<T>` extractor.
367    ///
368    /// # Example
369    ///
370    /// ```ignore
371    /// let scope = LifespanScope::new(MyState { value: 42 });
372    /// ```
373    pub fn new(state: T) -> Self {
374        Self {
375            state,
376            cleanup: None,
377        }
378    }
379
380    /// Sets the cleanup future to run during application shutdown.
381    ///
382    /// The cleanup runs in reverse order relative to other lifespan/shutdown hooks,
383    /// after all in-flight requests have completed or been cancelled.
384    ///
385    /// # Example
386    ///
387    /// ```ignore
388    /// let scope = LifespanScope::new(pool.clone())
389    ///     .on_shutdown(async move {
390    ///         pool.close().await;
391    ///         println!("Database pool closed");
392    ///     });
393    /// ```
394    #[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    /// Takes the cleanup future, leaving `None` in its place.
404    ///
405    /// This is used internally to transfer the cleanup to the shutdown system.
406    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
420/// Boxed lifespan function type.
421///
422/// The lifespan function runs during startup and returns state (as Any) along with
423/// an optional cleanup future for shutdown. The state is then inserted into the container.
424pub 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
440/// A boxed handler function.
441///
442/// The handler may return a future that borrows from the context/request for
443/// the duration of the call.
444pub type BoxHandler = Box<
445    dyn for<'a> Fn(&'a RequestContext, &'a mut Request) -> BoxFuture<'a, Response> + Send + Sync,
446>;
447
448/// A registered route with its handler.
449#[derive(Clone)]
450pub struct RouteEntry {
451    /// The HTTP method for this route.
452    pub method: Method,
453    /// The path pattern for this route.
454    pub path: String,
455    /// The handler function.
456    handler: Arc<BoxHandler>,
457}
458
459impl RouteEntry {
460    /// Creates a new route entry.
461    ///
462    /// Note: The handler's future must not outlive the borrow of the
463    /// context/request passed to it.
464    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    /// Calls the handler with the given context and request.
480    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/// Type-safe application state container.
495///
496/// State is stored by type and can be accessed by handlers through
497/// the `State<T>` extractor.
498#[derive(Default)]
499pub struct StateContainer {
500    state: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
501}
502
503impl StateContainer {
504    /// Creates a new empty state container.
505    #[must_use]
506    pub fn new() -> Self {
507        Self {
508            state: HashMap::new(),
509        }
510    }
511
512    /// Inserts a value into the state container.
513    ///
514    /// If a value of the same type already exists, it is replaced.
515    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
516        self.state.insert(TypeId::of::<T>(), Arc::new(value));
517    }
518
519    /// Inserts a boxed Any value into the state container.
520    ///
521    /// This is used by the lifespan system to insert type-erased state.
522    /// The TypeId is obtained from the actual type inside the box.
523    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    /// Gets a reference to a value in the state container.
529    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    /// Returns true if the state container contains a value of type T.
536    pub fn contains<T: 'static>(&self) -> bool {
537        self.state.contains_key(&TypeId::of::<T>())
538    }
539
540    /// Returns the number of values in the state container.
541    pub fn len(&self) -> usize {
542        self.state.len()
543    }
544
545    /// Returns true if the state container is empty.
546    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
559// ============================================================================
560// Exception Handler Registry
561// ============================================================================
562
563/// A boxed exception handler function.
564///
565/// The handler receives the RequestContext and a boxed error, and returns a Response.
566pub type BoxExceptionHandler = Box<
567    dyn Fn(&RequestContext, Box<dyn std::error::Error + Send + Sync>) -> Response + Send + Sync,
568>;
569
570/// A boxed panic handler function.
571///
572/// The handler receives the RequestContext (if available) and panic info string,
573/// and returns a Response. This is called by the HTTP server layer when a panic
574/// is caught via `catch_unwind`.
575pub type BoxPanicHandler = Box<dyn Fn(Option<&RequestContext>, &str) -> Response + Send + Sync>;
576
577/// Registry for custom exception handlers.
578///
579/// This allows applications to register handlers for specific error types,
580/// converting errors into HTTP responses in a customizable way.
581///
582/// # Default Handlers
583///
584/// The registry comes with default handlers for common error types:
585/// - [`HttpError`](crate::HttpError) → JSON response with status/detail
586/// - [`ValidationErrors`](crate::ValidationErrors) → 422 with error list
587/// - [`CancelledError`](crate::CancelledError) → 499 Client Closed Request
588///
589/// # Panic Handler
590///
591/// The registry also supports a panic handler that is invoked when a panic
592/// is caught during request handling. This is typically used by the HTTP
593/// server layer via `catch_unwind`. The default panic handler returns a
594/// 500 Internal Server Error.
595///
596/// # Example
597///
598/// ```ignore
599/// use fastapi_core::app::ExceptionHandlers;
600/// use fastapi_core::{RequestContext, Response, HttpError};
601///
602/// #[derive(Debug)]
603/// struct MyCustomError(String);
604///
605/// impl std::fmt::Display for MyCustomError {
606///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
607///         write!(f, "Custom error: {}", self.0)
608///     }
609/// }
610///
611/// impl std::error::Error for MyCustomError {}
612///
613/// let handlers = ExceptionHandlers::new()
614///     .handler(|_ctx, err: MyCustomError| {
615///         Response::with_status(StatusCode::BAD_REQUEST)
616///             .body_json(&serde_json::json!({"error": err.0}))
617///     });
618/// ```
619pub 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    /// Creates a new empty exception handler registry.
632    #[must_use]
633    pub fn new() -> Self {
634        Self {
635            handlers: HashMap::new(),
636            panic_handler: None,
637        }
638    }
639
640    /// Creates a registry with default handlers for common error types.
641    #[must_use]
642    pub fn with_defaults() -> Self {
643        let mut handlers = Self::new();
644
645        // Default handler for HttpError
646        handlers.register::<crate::HttpError>(|_ctx, err| {
647            use crate::IntoResponse;
648            err.into_response()
649        });
650
651        // Default handler for ValidationErrors
652        handlers.register::<crate::ValidationErrors>(|_ctx, err| {
653            use crate::IntoResponse;
654            err.into_response()
655        });
656
657        // Default handler for CancelledError -> 499 Client Closed Request
658        handlers.register::<crate::CancelledError>(|_ctx, _err| {
659            Response::with_status(StatusCode::CLIENT_CLOSED_REQUEST)
660        });
661
662        handlers
663    }
664
665    /// Registers a handler for a specific error type.
666    ///
667    /// The handler receives the error value directly (not boxed) for type safety.
668    /// If a handler for the same type already exists, it is replaced.
669    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            // Try to downcast the error to the expected type
677            match err.downcast::<E>() {
678                Ok(typed_err) => handler(ctx, *typed_err),
679                Err(_) => {
680                    // This shouldn't happen if the registry is used correctly
681                    Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
682                }
683            }
684        });
685        self.handlers.insert(TypeId::of::<E>(), boxed_handler);
686    }
687
688    /// Registers a handler for a specific error type (builder pattern).
689    #[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    /// Handles an error by finding and invoking the appropriate handler.
702    ///
703    /// Returns `Some(Response)` if a handler was found for the error type,
704    /// or `None` if no handler is registered.
705    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    /// Handles an error, falling back to a default 500 response if no handler is found.
716    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    /// Returns true if a handler is registered for the given error type.
725    pub fn has_handler<E: 'static>(&self) -> bool {
726        self.handlers.contains_key(&TypeId::of::<E>())
727    }
728
729    /// Returns the number of registered handlers.
730    pub fn len(&self) -> usize {
731        self.handlers.len()
732    }
733
734    /// Returns true if no handlers are registered.
735    pub fn is_empty(&self) -> bool {
736        self.handlers.is_empty()
737    }
738
739    /// Merges another handler registry into this one.
740    ///
741    /// Handlers from `other` will override handlers in `self` for the same error types.
742    pub fn merge(&mut self, other: ExceptionHandlers) {
743        self.handlers.extend(other.handlers);
744        // Prefer other's panic handler if set
745        if other.panic_handler.is_some() {
746            self.panic_handler = other.panic_handler;
747        }
748    }
749
750    // =========================================================================
751    // Panic Handler
752    // =========================================================================
753
754    /// Sets a custom panic handler.
755    ///
756    /// The panic handler is called by the HTTP server layer when a panic is caught
757    /// during request handling via `catch_unwind`. The handler receives the
758    /// `RequestContext` (if available) and a panic message string.
759    ///
760    /// # Example
761    ///
762    /// ```ignore
763    /// let handlers = ExceptionHandlers::with_defaults()
764    ///     .panic_handler(|ctx, panic_msg| {
765    ///         // Log the panic
766    ///         eprintln!("Request panicked: {}", panic_msg);
767    ///
768    ///         // Return a custom error response
769    ///         Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
770    ///             .body_json(&serde_json::json!({
771    ///                 "error": "internal_server_error",
772    ///                 "message": "An unexpected error occurred"
773    ///             }))
774    ///     });
775    /// ```
776    #[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    /// Sets a custom panic handler (mutable reference version).
786    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    /// Handles a panic by invoking the configured panic handler.
794    ///
795    /// If no panic handler is configured, returns a default 500 Internal Server Error.
796    ///
797    /// This method is intended to be called by the HTTP server layer after catching
798    /// a panic via `catch_unwind`.
799    ///
800    /// # Arguments
801    ///
802    /// * `ctx` - The request context, if available when the panic occurred
803    /// * `panic_info` - A string describing the panic (extracted from the panic payload)
804    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    /// Returns the default response for panics: 500 Internal Server Error.
813    #[must_use]
814    pub fn default_panic_response() -> Response {
815        Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
816    }
817
818    /// Returns true if a custom panic handler is registered.
819    #[must_use]
820    pub fn has_panic_handler(&self) -> bool {
821        self.panic_handler.is_some()
822    }
823
824    /// Extracts a message string from a panic payload.
825    ///
826    /// This is a helper for use with `catch_unwind` results.
827    #[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/// Application configuration for the FastAPI Rust framework.
849///
850/// Controls application-level settings including naming, debug mode,
851/// body size limits, timeouts, and routing behavior.
852///
853/// # Defaults
854///
855/// | Setting | Default |
856/// |---------|---------|
857/// | `name` | `"FastAPI"` |
858/// | `version` | `"0.1.0"` |
859/// | `debug` | `false` |
860/// | `max_body_size` | 1 MB (1,048,576 bytes) |
861/// | `request_timeout_ms` | 30,000 ms |
862/// | `root_path` | `""` |
863/// | `trailing_slash_mode` | `Strict` |
864///
865/// # Example
866///
867/// ```ignore
868/// use fastapi_core::AppConfig;
869///
870/// let config = AppConfig::default()
871///     .with_name("my-api")
872///     .with_version("2.0.0")
873///     .with_debug(true)
874///     .with_max_body_size(10 * 1024 * 1024); // 10 MB
875/// ```
876#[derive(Debug, Clone)]
877pub struct AppConfig {
878    /// Application name (used in logging and OpenAPI).
879    pub name: String,
880    /// Application version.
881    pub version: String,
882    /// Enable debug mode.
883    pub debug: bool,
884    /// Maximum request body size in bytes.
885    pub max_body_size: usize,
886    /// Default request timeout in milliseconds.
887    pub request_timeout_ms: u64,
888    /// Root path prefix for apps behind a reverse proxy.
889    ///
890    /// When the application is served behind a reverse proxy at a sub-path,
891    /// this should be set to that sub-path. For example, if the app is
892    /// proxied at `/api/v1`, set `root_path = "/api/v1"`.
893    ///
894    /// This affects:
895    /// - URL generation via `url_for()`
896    /// - OpenAPI servers list (if `root_path_in_servers` is true)
897    pub root_path: String,
898    /// Whether to include the root_path in OpenAPI servers list.
899    ///
900    /// When true and `root_path` is set, a server entry with the root_path
901    /// will be added to the OpenAPI specification's servers array.
902    pub root_path_in_servers: bool,
903    /// Trailing slash handling mode.
904    ///
905    /// Controls how the router handles trailing slashes in URLs:
906    /// - `Strict` (default): `/users` and `/users/` are different routes
907    /// - `Redirect`: 308 redirect `/users/` to `/users`
908    /// - `RedirectWithSlash`: 308 redirect `/users` to `/users/`
909    /// - `MatchBoth`: accept both forms without redirect
910    pub trailing_slash_mode: crate::routing::TrailingSlashMode,
911    /// Debug mode configuration.
912    ///
913    /// Controls whether error responses include additional diagnostic
914    /// information such as source location, handler name, and route pattern.
915    /// When a debug header and token are configured, debug info is only
916    /// included for requests that present the correct token.
917    pub debug_config: crate::error::DebugConfig,
918}
919
920/// Configuration loading errors.
921#[derive(Debug)]
922pub enum ConfigError {
923    /// Failed to read configuration file.
924    Io(io::Error),
925    /// Failed to parse JSON configuration.
926    Json(serde_json::Error),
927    /// Unsupported configuration format.
928    UnsupportedFormat { path: PathBuf },
929    /// Invalid environment variable value.
930    InvalidEnvVar {
931        /// Environment variable name.
932        key: String,
933        /// Raw value.
934        value: String,
935        /// Expected format.
936        expected: String,
937    },
938    /// Validation failure.
939    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, // 1MB
1000            request_timeout_ms: 30_000, // 30 seconds
1001            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    /// Creates a new configuration with defaults.
1020    #[must_use]
1021    pub fn new() -> Self {
1022        Self::default()
1023    }
1024
1025    /// Sets the application name.
1026    #[must_use]
1027    pub fn name(mut self, name: impl Into<String>) -> Self {
1028        self.name = name.into();
1029        self
1030    }
1031
1032    /// Sets the application version.
1033    #[must_use]
1034    pub fn version(mut self, version: impl Into<String>) -> Self {
1035        self.version = version.into();
1036        self
1037    }
1038
1039    /// Enables or disables debug mode.
1040    #[must_use]
1041    pub fn debug(mut self, debug: bool) -> Self {
1042        self.debug = debug;
1043        self
1044    }
1045
1046    /// Sets the maximum request body size.
1047    #[must_use]
1048    pub fn max_body_size(mut self, size: usize) -> Self {
1049        self.max_body_size = size;
1050        self
1051    }
1052
1053    /// Sets the default request timeout in milliseconds.
1054    #[must_use]
1055    pub fn request_timeout_ms(mut self, timeout: u64) -> Self {
1056        self.request_timeout_ms = timeout;
1057        self
1058    }
1059
1060    /// Sets the root path for apps behind a reverse proxy.
1061    ///
1062    /// When the application is served behind a reverse proxy at a sub-path,
1063    /// set this to that sub-path. For example, if the app is proxied at
1064    /// `/api/v1`, set `root_path("/api/v1")`.
1065    ///
1066    /// # Example
1067    ///
1068    /// ```ignore
1069    /// let config = AppConfig::new().root_path("/api/v1");
1070    /// ```
1071    #[must_use]
1072    pub fn root_path(mut self, path: impl Into<String>) -> Self {
1073        let mut path = path.into();
1074        // Normalize: remove trailing slashes
1075        while path.ends_with('/') && path.len() > 1 {
1076            path.pop();
1077        }
1078        self.root_path = path;
1079        self
1080    }
1081
1082    /// Sets whether to include root_path in OpenAPI servers list.
1083    ///
1084    /// When true and `root_path` is set, a server entry with the root_path
1085    /// will be added to the OpenAPI specification's servers array.
1086    #[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    /// Sets the trailing slash handling mode.
1093    ///
1094    /// Controls how the router handles trailing slashes in URLs.
1095    ///
1096    /// # Example
1097    ///
1098    /// ```ignore
1099    /// use fastapi_core::TrailingSlashMode;
1100    ///
1101    /// let config = AppConfig::new()
1102    ///     .trailing_slash_mode(TrailingSlashMode::Redirect);
1103    /// ```
1104    #[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    /// Sets the debug mode configuration.
1111    ///
1112    /// Controls whether error responses include additional diagnostic
1113    /// information. Use with `DebugConfig::new().enable().with_debug_header(...)`.
1114    ///
1115    /// # Example
1116    ///
1117    /// ```ignore
1118    /// use fastapi_core::{DebugConfig};
1119    ///
1120    /// let config = AppConfig::new()
1121    ///     .debug_config(DebugConfig::new()
1122    ///         .enable()
1123    ///         .with_debug_header("X-Debug-Token", "my-secret"));
1124    /// ```
1125    #[must_use]
1126    pub fn debug_config(mut self, config: crate::error::DebugConfig) -> Self {
1127        self.debug_config = config;
1128        self
1129    }
1130
1131    /// Load configuration from environment variables.
1132    ///
1133    /// Variables (prefix `FASTAPI_` by default):
1134    /// - `FASTAPI_NAME`
1135    /// - `FASTAPI_VERSION`
1136    /// - `FASTAPI_DEBUG` (true/false/1/0/yes/no/on/off)
1137    /// - `FASTAPI_MAX_BODY_SIZE` (bytes)
1138    /// - `FASTAPI_REQUEST_TIMEOUT_MS`
1139    /// - `FASTAPI_ROOT_PATH` (path prefix for reverse proxy)
1140    /// - `FASTAPI_ROOT_PATH_IN_SERVERS` (true/false/1/0/yes/no/on/off)
1141    pub fn from_env() -> Result<Self, ConfigError> {
1142        Self::from_env_with_prefix(Self::DEFAULT_ENV_PREFIX)
1143    }
1144
1145    /// Load configuration from environment variables using a custom prefix.
1146    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    /// Load configuration from a JSON file.
1154    ///
1155    /// Only JSON is supported for now to keep dependencies minimal.
1156    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    /// Load configuration from a JSON file then override with environment variables.
1172    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    /// Returns the root_path as an OpenAPI server entry if configured.
1180    ///
1181    /// Returns `Some((url, description))` if:
1182    /// - `root_path` is non-empty
1183    /// - `root_path_in_servers` is true
1184    ///
1185    /// Returns `None` otherwise.
1186    ///
1187    /// # Example
1188    ///
1189    /// ```ignore
1190    /// let config = AppConfig::new().root_path("/api/v1");
1191    /// if let Some((url, description)) = config.openapi_server() {
1192    ///     builder = builder.server(url, description);
1193    /// }
1194    /// ```
1195    #[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    /// Validate configuration values.
1208    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
1351/// Builder for constructing an [`App`].
1352///
1353/// Use this to configure routes, middleware, and shared state before
1354/// building the final application.
1355///
1356/// # Example
1357///
1358/// ```ignore
1359/// let app = App::builder()
1360///     .config(AppConfig::new().name("My API"))
1361///     .state(DatabasePool::new())
1362///     .middleware(LoggingMiddleware::new())
1363///     .on_startup(|| {
1364///         println!("Server starting...");
1365///         Ok(())
1366///     })
1367///     .on_shutdown(|| {
1368///         println!("Server stopping...");
1369///     })
1370///     .route("/", Method::Get, index_handler)
1371///     .route("/items", Method::Get, list_items)
1372///     .route("/items", Method::Post, create_item)
1373///     .route("/items/{id}", Method::Get, get_item)
1374///     .build();
1375/// ```
1376///
1377/// # Type-Safe State
1378///
1379/// The `AppBuilder` uses a type-state pattern to track registered state types
1380/// at compile time. The generic parameter `S` represents the type-level set of
1381/// registered state types.
1382///
1383/// When you call `.with_state::<T>(value)`, the builder's type changes from
1384/// `AppBuilder<S>` to `AppBuilder<(T, S)>`, recording that `T` is now available.
1385///
1386/// Handlers that use `State<T>` extractors can optionally be constrained to
1387/// require `S: HasState<T>`, ensuring the state is registered at compile time.
1388///
1389/// ```ignore
1390/// // Type changes: AppBuilder<()> -> AppBuilder<(DbPool, ())> -> AppBuilder<(Config, (DbPool, ()))>
1391/// let app = App::builder()
1392///     .with_state(DbPool::new())  // Now has DbPool
1393///     .with_state(Config::default())  // Now has DbPool + Config
1394///     .build();
1395/// ```
1396pub 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    /// Optional lifespan function for async startup/shutdown context management.
1407    lifespan: Option<BoxLifespanFn>,
1408    /// Mounted sub-applications at specific path prefixes.
1409    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    /// Creates a new application builder with no registered state.
1434    #[must_use]
1435    pub fn new() -> Self {
1436        Self::default()
1437    }
1438}
1439
1440impl<S: StateRegistry> AppBuilder<S> {
1441    /// Sets the application configuration.
1442    #[must_use]
1443    pub fn config(mut self, config: AppConfig) -> Self {
1444        self.config = config;
1445        self
1446    }
1447
1448    /// Adds a route to the application.
1449    ///
1450    /// Routes are matched in the order they are added.
1451    #[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    /// Adds a GET route.
1465    #[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    /// Adds a POST route.
1475    #[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    /// Adds a PUT route.
1485    #[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    /// Adds a DELETE route.
1495    #[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    /// Adds a PATCH route.
1505    #[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    /// Adds middleware to the application.
1515    ///
1516    /// Middleware is executed in the order it is added:
1517    /// - `before` hooks run first-to-last
1518    /// - `after` hooks run last-to-first
1519    #[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    /// Includes routes from an [`APIRouter`](crate::api_router::APIRouter).
1526    ///
1527    /// This adds all routes from the router to the application, applying
1528    /// the router's prefix, tags, and dependencies.
1529    ///
1530    /// # Example
1531    ///
1532    /// ```ignore
1533    /// use fastapi_core::api_router::APIRouter;
1534    ///
1535    /// let users_router = APIRouter::new()
1536    ///     .prefix("/users")
1537    ///     .get("", list_users)
1538    ///     .get("/{id}", get_user);
1539    ///
1540    /// let app = App::builder()
1541    ///     .include_router(users_router)
1542    ///     .build();
1543    /// ```
1544    #[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    /// Includes routes from an [`APIRouter`](crate::api_router::APIRouter) with configuration.
1553    ///
1554    /// This allows applying additional configuration when including a router,
1555    /// such as prepending a prefix, adding tags, or injecting dependencies.
1556    ///
1557    /// # Example
1558    ///
1559    /// ```ignore
1560    /// use fastapi_core::api_router::{APIRouter, IncludeConfig};
1561    ///
1562    /// let users_router = APIRouter::new()
1563    ///     .prefix("/users")
1564    ///     .get("", list_users);
1565    ///
1566    /// let config = IncludeConfig::new()
1567    ///     .prefix("/api/v1")
1568    ///     .tags(vec!["api"]);
1569    ///
1570    /// let app = App::builder()
1571    ///     .include_router_with_config(users_router, config)
1572    ///     .build();
1573    /// ```
1574    #[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        // Apply config to a temporary router, then include
1581        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    /// Mounts a sub-application at a path prefix.
1590    ///
1591    /// Mounted applications are completely independent from the parent:
1592    /// - They have their own middleware stack (parent middleware does NOT apply)
1593    /// - They have their own state and configuration
1594    /// - Their OpenAPI schemas are NOT merged with the parent
1595    /// - Request paths have the prefix stripped before being passed to the sub-app
1596    ///
1597    /// This differs from [`include_router`](Self::include_router), which merges
1598    /// routes into the parent app and applies parent middleware.
1599    ///
1600    /// # Use Cases
1601    ///
1602    /// - Mount admin panels at `/admin`
1603    /// - Mount Swagger UI at `/docs`
1604    /// - Mount static file servers
1605    /// - Integrate legacy or third-party apps
1606    ///
1607    /// # Path Stripping Behavior
1608    ///
1609    /// When a request arrives at `/admin/users`, and an app is mounted at `/admin`,
1610    /// the sub-app receives the request with path `/users`.
1611    ///
1612    /// # Example
1613    ///
1614    /// ```ignore
1615    /// use fastapi_core::app::App;
1616    ///
1617    /// // Create an admin sub-application
1618    /// let admin_app = App::builder()
1619    ///     .get("/users", admin_list_users)
1620    ///     .get("/settings", admin_settings)
1621    ///     .middleware(AdminAuthMiddleware::new())
1622    ///     .build();
1623    ///
1624    /// // Mount it at /admin
1625    /// let main_app = App::builder()
1626    ///     .get("/", home_page)
1627    ///     .mount("/admin", admin_app)
1628    ///     .build();
1629    ///
1630    /// // Now:
1631    /// // - GET /           -> home_page
1632    /// // - GET /admin/users -> admin_list_users (with AdminAuthMiddleware)
1633    /// // - GET /admin/settings -> admin_settings (with AdminAuthMiddleware)
1634    /// ```
1635    #[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    /// Returns the number of mounted sub-applications.
1642    #[must_use]
1643    pub fn mounted_app_count(&self) -> usize {
1644        self.mounted_apps.len()
1645    }
1646
1647    /// Adds shared state to the application (legacy method).
1648    ///
1649    /// State can be accessed by handlers through the `State<T>` extractor.
1650    ///
1651    /// **Note:** This method is deprecated in favor of [`with_state`](Self::with_state),
1652    /// which provides compile-time verification that state types are registered.
1653    #[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    /// Adds typed state to the application with compile-time registration.
1664    ///
1665    /// This method registers state using a type-state pattern, which enables
1666    /// compile-time verification that state types are properly registered before
1667    /// they are used by handlers.
1668    ///
1669    /// The return type changes from `AppBuilder<S>` to `AppBuilder<(T, S)>`,
1670    /// recording that type `T` is now available in the state registry.
1671    ///
1672    /// # Example
1673    ///
1674    /// ```ignore
1675    /// use fastapi_core::app::App;
1676    ///
1677    /// struct DbPool { /* ... */ }
1678    /// struct Config { api_key: String }
1679    ///
1680    /// // Type evolves: () -> (DbPool, ()) -> (Config, (DbPool, ()))
1681    /// let app = App::builder()
1682    ///     .with_state(DbPool::new())    // Now has DbPool
1683    ///     .with_state(Config::default()) // Now has DbPool + Config
1684    ///     .build();
1685    /// ```
1686    ///
1687    /// # Compile-Time Safety
1688    ///
1689    /// When used with the `RequiresState` trait, handlers can declare their
1690    /// state dependencies and the compiler will verify they are met:
1691    ///
1692    /// ```ignore
1693    /// // This handler requires DbPool to be registered
1694    /// fn handler_requiring_db<S: HasState<DbPool>>(app: AppBuilder<S>) { /* ... */ }
1695    /// ```
1696    #[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    /// Registers a dependency override for this application (useful in tests).
1716    #[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    /// Registers a fixed dependency override value.
1728    #[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    /// Clears all registered dependency overrides.
1738    #[must_use]
1739    pub fn clear_dependency_overrides(self) -> Self {
1740        self.dependency_overrides.clear();
1741        self
1742    }
1743
1744    /// Registers a custom exception handler for a specific error type.
1745    ///
1746    /// When an error of type `E` occurs during request handling, the registered
1747    /// handler will be called to convert it into a response.
1748    ///
1749    /// # Example
1750    ///
1751    /// ```ignore
1752    /// #[derive(Debug)]
1753    /// struct AuthError(String);
1754    ///
1755    /// impl std::fmt::Display for AuthError {
1756    ///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1757    ///         write!(f, "Auth error: {}", self.0)
1758    ///     }
1759    /// }
1760    ///
1761    /// impl std::error::Error for AuthError {}
1762    ///
1763    /// let app = App::builder()
1764    ///     .exception_handler(|_ctx, err: AuthError| {
1765    ///         Response::with_status(StatusCode::UNAUTHORIZED)
1766    ///             .header("www-authenticate", b"Bearer".to_vec())
1767    ///             .body_json(&json!({"error": err.0}))
1768    ///     })
1769    ///     .build();
1770    /// ```
1771    #[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    /// Sets the exception handlers registry.
1782    ///
1783    /// This replaces any previously registered handlers.
1784    #[must_use]
1785    pub fn exception_handlers(mut self, handlers: ExceptionHandlers) -> Self {
1786        self.exception_handlers = handlers;
1787        self
1788    }
1789
1790    /// Uses default exception handlers for common error types.
1791    ///
1792    /// This registers handlers for:
1793    /// - [`HttpError`](crate::HttpError) → JSON response with status/detail
1794    /// - [`ValidationErrors`](crate::ValidationErrors) → 422 with error list
1795    #[must_use]
1796    pub fn with_default_exception_handlers(mut self) -> Self {
1797        self.exception_handlers = ExceptionHandlers::with_defaults();
1798        self
1799    }
1800
1801    // =========================================================================
1802    // Lifecycle Hooks
1803    // =========================================================================
1804
1805    /// Registers a synchronous startup hook.
1806    ///
1807    /// Startup hooks run before the server starts accepting connections,
1808    /// in the order they are registered (FIFO).
1809    ///
1810    /// # Example
1811    ///
1812    /// ```ignore
1813    /// let app = App::builder()
1814    ///     .on_startup(|| {
1815    ///         println!("Connecting to database...");
1816    ///         Ok(())
1817    ///     })
1818    ///     .on_startup(|| {
1819    ///         println!("Loading configuration...");
1820    ///         Ok(())
1821    ///     })
1822    ///     .build();
1823    /// ```
1824    #[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    /// Registers an async startup hook.
1834    ///
1835    /// Async startup hooks are awaited in registration order.
1836    ///
1837    /// # Example
1838    ///
1839    /// ```ignore
1840    /// let app = App::builder()
1841    ///     .on_startup_async(|| async {
1842    ///         let pool = connect_to_database().await?;
1843    ///         Ok(())
1844    ///     })
1845    ///     .build();
1846    /// ```
1847    #[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    /// Registers a synchronous shutdown hook.
1860    ///
1861    /// Shutdown hooks run after the server stops accepting connections
1862    /// and all in-flight requests complete (or are cancelled).
1863    ///
1864    /// Shutdown hooks run in reverse registration order (LIFO), matching
1865    /// typical resource cleanup patterns (last acquired, first released).
1866    ///
1867    /// # Example
1868    ///
1869    /// ```ignore
1870    /// let app = App::builder()
1871    ///     .on_shutdown(|| {
1872    ///         println!("Closing database connections...");
1873    ///     })
1874    ///     .build();
1875    /// ```
1876    #[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    /// Registers an async shutdown hook.
1886    ///
1887    /// Async shutdown hooks are awaited in reverse registration order (LIFO).
1888    ///
1889    /// # Example
1890    ///
1891    /// ```ignore
1892    /// let app = App::builder()
1893    ///     .on_shutdown_async(|| async {
1894    ///         flush_metrics().await;
1895    ///     })
1896    ///     .build();
1897    /// ```
1898    #[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    /// Registers a lifespan context manager for async startup/shutdown.
1910    ///
1911    /// The lifespan pattern is preferred over separate `on_startup`/`on_shutdown` hooks
1912    /// because it allows sharing state between the startup and shutdown phases. This is
1913    /// especially useful for resources like database connections, HTTP clients, or
1914    /// background task managers.
1915    ///
1916    /// The lifespan function runs during application startup. It should:
1917    /// 1. Initialize resources (connect to database, start background tasks, etc.)
1918    /// 2. Return a `LifespanScope` containing:
1919    ///    - State to be added to the application (accessible via `State<T>` extractor)
1920    ///    - An optional cleanup closure to run during shutdown
1921    ///
1922    /// If the lifespan function returns an error, application startup is aborted.
1923    ///
1924    /// **Note:** When a lifespan is provided, it runs *before* any `on_startup` hooks.
1925    /// The lifespan cleanup runs *after* all `on_shutdown` hooks during shutdown.
1926    ///
1927    /// # Example
1928    ///
1929    /// ```ignore
1930    /// use fastapi_core::app::{App, LifespanScope, LifespanError};
1931    ///
1932    /// #[derive(Clone)]
1933    /// struct DatabasePool { /* ... */ }
1934    ///
1935    /// impl DatabasePool {
1936    ///     async fn connect(url: &str) -> Result<Self, Error> { /* ... */ }
1937    ///     async fn close(&self) { /* ... */ }
1938    /// }
1939    ///
1940    /// let app = App::builder()
1941    ///     .lifespan(|| async {
1942    ///         // Startup: connect to database
1943    ///         println!("Connecting to database...");
1944    ///         let pool = DatabasePool::connect("postgres://localhost/mydb")
1945    ///             .await
1946    ///             .map_err(|e| LifespanError::with_source("database connection failed", e))?;
1947    ///
1948    ///         // Clone for use in cleanup
1949    ///         let pool_for_cleanup = pool.clone();
1950    ///
1951    ///         // Return state and cleanup
1952    ///         Ok(LifespanScope::new(pool)
1953    ///             .on_shutdown(async move {
1954    ///                 println!("Closing database connections...");
1955    ///                 pool_for_cleanup.close().await;
1956    ///             }))
1957    ///     })
1958    ///     .get("/users", get_users)  // Handler can use State<DatabasePool>
1959    ///     .build();
1960    /// ```
1961    ///
1962    /// # Multiple State Types
1963    ///
1964    /// To provide multiple state types from a single lifespan, use a tuple or
1965    /// define a struct containing all your state:
1966    ///
1967    /// ```ignore
1968    /// #[derive(Clone)]
1969    /// struct AppState {
1970    ///     db: DatabasePool,
1971    ///     cache: RedisClient,
1972    ///     config: AppConfig,
1973    /// }
1974    ///
1975    /// let app = App::builder()
1976    ///     .lifespan(|| async {
1977    ///         let db = DatabasePool::connect("...").await?;
1978    ///         let cache = RedisClient::connect("...").await?;
1979    ///         let config = load_config().await?;
1980    ///
1981    ///         let state = AppState { db, cache, config };
1982    ///         let state_for_cleanup = state.clone();
1983    ///
1984    ///         Ok(LifespanScope::new(state)
1985    ///             .on_shutdown(async move {
1986    ///                 state_for_cleanup.db.close().await;
1987    ///                 state_for_cleanup.cache.close().await;
1988    ///             }))
1989    ///     })
1990    ///     .build();
1991    /// ```
1992    #[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                // Run the lifespan function
2002                let mut scope = lifespan_fn().await?;
2003
2004                // Extract cleanup first (before moving state)
2005                let cleanup = scope.take_cleanup();
2006
2007                // Extract the state as a boxed Any
2008                let state: Box<dyn std::any::Any + Send + Sync> = Box::new(scope.state);
2009
2010                // Return both the state and the cleanup future
2011                Ok((state, cleanup))
2012            })
2013        }));
2014        self
2015    }
2016
2017    /// Returns true if a lifespan function has been registered.
2018    #[must_use]
2019    pub fn has_lifespan(&self) -> bool {
2020        self.lifespan.is_some()
2021    }
2022
2023    /// Returns the number of registered startup hooks.
2024    #[must_use]
2025    pub fn startup_hook_count(&self) -> usize {
2026        self.startup_hooks.len()
2027    }
2028
2029    /// Returns the number of registered shutdown hooks.
2030    #[must_use]
2031    pub fn shutdown_hook_count(&self) -> usize {
2032        self.shutdown_hooks.len() + self.async_shutdown_hooks.len()
2033    }
2034
2035    /// Builds the application.
2036    ///
2037    /// This consumes the builder and returns the configured [`App`].
2038    #[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        // Build the route table from route entries
2046        // Each route stores its index into the routes vec
2047        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/// A mounted sub-application with its path prefix.
2087///
2088/// Mounted applications are completely independent from the parent app:
2089/// - They have their own middleware stack
2090/// - They have their own state and configuration
2091/// - Their OpenAPI schemas are NOT merged with the parent
2092/// - Request paths have the prefix stripped before being passed to the sub-app
2093///
2094/// This differs from `include_router`, which merges routes into the parent app.
2095#[derive(Debug)]
2096pub struct MountedApp {
2097    /// Path prefix at which this app is mounted (e.g., "/admin", "/docs").
2098    prefix: String,
2099    /// The mounted sub-application.
2100    app: Arc<App>,
2101}
2102
2103impl MountedApp {
2104    /// Create a new mounted app at the given prefix.
2105    ///
2106    /// The prefix should start with '/' and not end with '/'.
2107    #[must_use]
2108    pub fn new(prefix: impl Into<String>, app: App) -> Self {
2109        let mut prefix = prefix.into();
2110        // Normalize prefix: ensure starts with '/', doesn't end with '/'
2111        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    /// Returns the mount prefix.
2124    #[must_use]
2125    pub fn prefix(&self) -> &str {
2126        &self.prefix
2127    }
2128
2129    /// Returns a reference to the mounted app.
2130    #[must_use]
2131    pub fn app(&self) -> &App {
2132        &self.app
2133    }
2134
2135    /// Check if a request path matches this mount prefix.
2136    ///
2137    /// Returns the remaining path after the prefix is stripped, or None if no match.
2138    #[must_use]
2139    pub fn match_prefix<'a>(&self, path: &'a str) -> Option<&'a str> {
2140        if path == self.prefix {
2141            // Exact match - sub-app should receive "/"
2142            Some("/")
2143        } else if path.starts_with(&self.prefix) {
2144            let remaining = &path[self.prefix.len()..];
2145            // Must be followed by '/' or end of string
2146            if remaining.starts_with('/') {
2147                Some(remaining)
2148            } else {
2149                None
2150            }
2151        } else {
2152            None
2153        }
2154    }
2155}
2156
2157/// A configured web application.
2158///
2159/// The `App` holds all routes, middleware, state, and lifecycle hooks,
2160/// and provides methods to handle incoming requests.
2161pub struct App {
2162    config: AppConfig,
2163    routes: Vec<RouteEntry>,
2164    /// Route table for path matching with parameter extraction.
2165    route_table: RouteTable<usize>,
2166    middleware: MiddlewareStack,
2167    /// State container with interior mutability to support lifespan-injected state.
2168    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    /// Pending lifespan function to run during startup.
2177    lifespan: parking_lot::Mutex<Option<BoxLifespanFn>>,
2178    /// Cleanup future from lifespan function (runs during shutdown).
2179    lifespan_cleanup: parking_lot::Mutex<Option<Pin<Box<dyn Future<Output = ()> + Send>>>>,
2180    /// Mounted sub-applications.
2181    mounted_apps: Vec<MountedApp>,
2182}
2183
2184impl App {
2185    /// Creates a new application builder.
2186    #[must_use]
2187    pub fn builder() -> AppBuilder<()> {
2188        AppBuilder::new()
2189    }
2190
2191    /// Returns the application configuration.
2192    #[must_use]
2193    pub fn config(&self) -> &AppConfig {
2194        &self.config
2195    }
2196
2197    /// Returns the number of registered routes.
2198    #[must_use]
2199    pub fn route_count(&self) -> usize {
2200        self.routes.len()
2201    }
2202
2203    /// Returns the shared state container (protected by RwLock for lifespan mutation).
2204    #[must_use]
2205    pub fn state(&self) -> &Arc<parking_lot::RwLock<StateContainer>> {
2206        &self.state
2207    }
2208
2209    /// Gets a reference to shared state of type T.
2210    pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
2211        self.state.read().get::<T>()
2212    }
2213
2214    /// Returns the dependency overrides registry.
2215    #[must_use]
2216    pub fn dependency_overrides(&self) -> &Arc<DependencyOverrides> {
2217        &self.dependency_overrides
2218    }
2219
2220    /// Registers a dependency override for this application (useful in tests).
2221    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    /// Registers a fixed dependency override value.
2231    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    /// Clears all registered dependency overrides.
2239    pub fn clear_dependency_overrides(&self) {
2240        self.dependency_overrides.clear();
2241    }
2242
2243    /// Returns the exception handlers registry.
2244    #[must_use]
2245    pub fn exception_handlers(&self) -> &Arc<ExceptionHandlers> {
2246        &self.exception_handlers
2247    }
2248
2249    /// Handles an error using registered exception handlers.
2250    ///
2251    /// If a handler is registered for the error type, it will be invoked.
2252    /// Otherwise, returns `None`.
2253    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    /// Handles an error, returning a 500 response if no handler is registered.
2261    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    /// Returns the mounted sub-applications.
2269    #[must_use]
2270    pub fn mounted_apps(&self) -> &[MountedApp] {
2271        &self.mounted_apps
2272    }
2273
2274    /// Handles an incoming request.
2275    ///
2276    /// This first checks mounted sub-applications (in registration order),
2277    /// then matches against registered routes, runs middleware, and returns
2278    /// the response. Path parameters are extracted and stored in request
2279    /// extensions for use by the `Path` extractor.
2280    ///
2281    /// # Mounted App Handling
2282    ///
2283    /// When a request path matches a mounted app's prefix, the request is
2284    /// forwarded to that app with the prefix stripped from the path. The
2285    /// mounted app's middleware runs instead of the parent's middleware.
2286    ///
2287    /// # Special Parameter Injection
2288    ///
2289    /// - **ResponseMutations**: Headers and cookies set by handlers are automatically
2290    ///   applied to the final response.
2291    /// - **BackgroundTasks**: Tasks are stored in request extensions via
2292    ///   `BackgroundTasksInner`. The HTTP server should retrieve and execute these
2293    ///   after sending the response using `take_background_tasks()`.
2294    pub async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response {
2295        // First, check mounted sub-applications
2296        for mounted in &self.mounted_apps {
2297            if let Some(stripped_path) = mounted.match_prefix(req.path()) {
2298                // Clone the request with the stripped path for the sub-app
2299                let original_path = req.path().to_string();
2300                req.set_path(stripped_path.to_string());
2301
2302                // Forward to the mounted app (uses its own middleware stack)
2303                // Use Box::pin for the recursive call to avoid infinite future size
2304                let response = Box::pin(mounted.app.handle(ctx, req)).await;
2305
2306                // Restore original path (in case of error handling or logging)
2307                req.set_path(original_path);
2308
2309                return response;
2310            }
2311        }
2312
2313        // Use the route table for path matching with converter validation
2314        match self.route_table.lookup(req.path(), req.method()) {
2315            RouteLookup::Match { route: idx, params } => {
2316                // Store path parameters in request extensions for the Path extractor
2317                req.insert_extension(PathParams::from_pairs(params));
2318
2319                // Initialize response mutations container for handlers to use
2320                req.insert_extension(crate::extract::ResponseMutations::new());
2321
2322                // Initialize background tasks container for handlers to use
2323                req.insert_extension(crate::extract::BackgroundTasksInner::new());
2324
2325                // Get the route entry by index
2326                let entry = &self.routes[*idx];
2327                let handler = RouteHandler { entry };
2328                let response = self.middleware.execute(&handler, ctx, req).await;
2329
2330                // Run cleanup functions in LIFO order (even on error)
2331                ctx.cleanup_stack().run_cleanups().await;
2332
2333                // Apply any response mutations set by the handler
2334                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                // Handle HEAD requests: strip body but preserve Content-Length
2343                // HEAD should return same headers as GET but with no body
2344                if req.method() == Method::Head {
2345                    strip_body_for_head(response)
2346                } else {
2347                    response
2348                }
2349            }
2350            RouteLookup::MethodNotAllowed { mut allowed } => {
2351                // Auto-handle OPTIONS requests: return 204 No Content with Allow header
2352                // listing all methods available for this path (including OPTIONS)
2353                if req.method() == Method::Options {
2354                    // Add OPTIONS to allowed methods if not already present
2355                    if !allowed.contains(&Method::Options) {
2356                        allowed.push(Method::Options);
2357                        // Re-sort to maintain consistent ordering
2358                        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                // 308 Permanent Redirect preserves the request method
2369                Response::with_status(StatusCode::PERMANENT_REDIRECT)
2370                    .header("Location", target.into_bytes())
2371            }
2372        }
2373    }
2374
2375    /// Take background tasks from a request after handling.
2376    ///
2377    /// This should be called by the HTTP server after `handle()` returns
2378    /// and the response has been sent to the client. The returned tasks
2379    /// should be executed asynchronously.
2380    ///
2381    /// # Example
2382    ///
2383    /// ```ignore
2384    /// let response = app.handle(&ctx, &mut request).await;
2385    /// // Send response to client...
2386    /// if let Some(tasks) = App::take_background_tasks(&mut request) {
2387    ///     tasks.execute_all().await;
2388    /// }
2389    /// ```
2390    #[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    // =========================================================================
2397    // Lifecycle Hook Execution
2398    // =========================================================================
2399
2400    /// Runs all startup hooks.
2401    ///
2402    /// Hooks run in registration order (FIFO). If a hook returns an error
2403    /// with `abort: true`, execution stops and returns `StartupOutcome::Aborted`.
2404    ///
2405    /// This consumes the startup hooks - they can only be run once.
2406    ///
2407    /// # Returns
2408    ///
2409    /// - `StartupOutcome::Success` if all hooks succeeded
2410    /// - `StartupOutcome::PartialSuccess` if some hooks had non-fatal errors
2411    /// - `StartupOutcome::Aborted` if a fatal hook error occurred
2412    pub async fn run_startup_hooks(&self) -> StartupOutcome {
2413        let mut warnings = 0;
2414
2415        // Run lifespan function first (if registered)
2416        let lifespan_fn = self.lifespan.lock().take();
2417        if let Some(lifespan) = lifespan_fn {
2418            // Run the lifespan function (returns state + cleanup)
2419            let result = lifespan().await;
2420
2421            match result {
2422                Ok((state, cleanup)) => {
2423                    // Insert the state into the container
2424                    self.state.write().insert_any(state);
2425                    // Store cleanup future for shutdown
2426                    *self.lifespan_cleanup.lock() = cleanup;
2427                }
2428                Err(e) => {
2429                    // Convert LifespanError to StartupHookError and abort
2430                    return StartupOutcome::Aborted(e.into());
2431                }
2432            }
2433        }
2434
2435        // Run regular startup hooks
2436        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                    // Sync hook succeeded
2442                }
2443                Ok(Some(fut)) => {
2444                    // Async hook - await it
2445                    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    /// Runs all shutdown hooks.
2472    ///
2473    /// Hooks run in reverse registration order (LIFO). Errors are logged
2474    /// but do not stop other hooks from running.
2475    ///
2476    /// This consumes the shutdown hooks - they can only be run once.
2477    pub async fn run_shutdown_hooks(&self) {
2478        // Run async hooks first (LIFO)
2479        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        // Run sync hooks (LIFO)
2486        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        // Run lifespan cleanup last (mirrors startup order)
2492        let lifespan_cleanup = self.lifespan_cleanup.lock().take();
2493        if let Some(cleanup) = lifespan_cleanup {
2494            cleanup.await;
2495        }
2496    }
2497
2498    /// Transfers shutdown hooks to a [`ShutdownController`].
2499    ///
2500    /// This moves all registered shutdown hooks to the controller, which
2501    /// will run them during the appropriate shutdown phase.
2502    ///
2503    /// Call this when integrating with the server's shutdown mechanism.
2504    pub fn transfer_shutdown_hooks(&self, controller: &ShutdownController) {
2505        // Transfer sync hooks (they'll run in LIFO due to how pop_hook works)
2506        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        // Transfer async hooks
2512        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    /// Returns the number of pending startup hooks.
2519    #[must_use]
2520    pub fn pending_startup_hooks(&self) -> usize {
2521        self.startup_hooks.lock().len()
2522    }
2523
2524    /// Returns the number of pending shutdown hooks.
2525    #[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    // =========================================================================
2531    // Testing Support
2532    // =========================================================================
2533
2534    /// Creates a [`crate::TestClient`] for this application.
2535    ///
2536    /// The test client provides in-process testing without network overhead,
2537    /// simulating HTTP requests against the full application stack including
2538    /// middleware, routing, and handlers.
2539    ///
2540    /// # Example
2541    ///
2542    /// ```ignore
2543    /// use fastapi_core::{App, Request, Response, RequestContext, Method};
2544    ///
2545    /// async fn hello(ctx: &RequestContext, req: &mut Request) -> Response {
2546    ///     Response::ok().body_text("Hello!")
2547    /// }
2548    ///
2549    /// let app = App::builder()
2550    ///     .route("/hello", Method::Get, hello)
2551    ///     .build();
2552    ///
2553    /// // Create test client from app
2554    /// let client = app.test_client();
2555    ///
2556    /// // Make requests
2557    /// let response = client.get("/hello").send();
2558    /// assert_eq!(response.status().as_u16(), 200);
2559    /// assert_eq!(response.text(), "Hello!");
2560    /// ```
2561    #[must_use]
2562    pub fn test_client(self: Arc<Self>) -> crate::testing::TestClient<Arc<App>> {
2563        crate::testing::TestClient::new(self)
2564    }
2565
2566    /// Creates a [`crate::TestClient`] with a specific seed for deterministic testing.
2567    ///
2568    /// Using the same seed produces reproducible behavior for tests involving
2569    /// concurrent operations or random elements.
2570    ///
2571    /// # Example
2572    ///
2573    /// ```ignore
2574    /// let app = Arc::new(App::builder().build());
2575    /// let client = app.test_client_with_seed(42);
2576    /// // Tests will be deterministic with this seed
2577    /// ```
2578    #[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
2630/// Handler wrapper for a route entry.
2631struct 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
2649/// Strip the body from a response for HEAD requests.
2650///
2651/// Per HTTP spec (RFC 7231 Section 4.3.2), HEAD responses must have the
2652/// same headers as the corresponding GET response, including Content-Length
2653/// if applicable, but with an empty message body.
2654///
2655/// This function:
2656/// - Preserves Content-Length header if already present
2657/// - Adds Content-Length for known-length bodies (Bytes) if not present
2658/// - Does not add Content-Length for streaming bodies (length unknown)
2659/// - Strips the body, returning an empty response
2660fn strip_body_for_head(response: Response) -> Response {
2661    use crate::response::ResponseBody;
2662
2663    // Check if Content-Length already exists
2664    let has_content_length = response
2665        .headers()
2666        .iter()
2667        .any(|(name, _): &(String, Vec<u8>)| name.eq_ignore_ascii_case("content-length"));
2668
2669    // Decompose the response
2670    let (status, headers, body) = response.into_parts();
2671
2672    // Determine if we should add Content-Length
2673    // Only add if not present and body has known length (Bytes, not Stream)
2674    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, // Unknown length, can't add
2681        }
2682    };
2683
2684    // Build new response with empty body but preserved headers
2685    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    // Test handlers that return 'static futures (no borrowing from parameters)
2706    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    // ========================================================================
3099    // HEAD Method Tests
3100    // ========================================================================
3101
3102    #[test]
3103    fn head_request_returns_empty_body() {
3104        // GET handler returns a body
3105        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        // Body should be empty for HEAD
3113        assert!(response.body_ref().is_empty());
3114    }
3115
3116    #[test]
3117    fn head_request_preserves_content_type() {
3118        // Handler that sets Content-Type
3119        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        // Content-Type should be preserved
3137        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        // Content-Length should be set to original body length ("Hello, World!" = 13 bytes)
3157        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        // Handler that explicitly sets Content-Length
3168        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()) // Explicit, different from actual
3175                    .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        // Should preserve the explicit Content-Length, not replace it
3189        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        // Content-Length should be 0 for empty body
3216        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        // Only register GET, HEAD should still work
3227        let app = App::builder()
3228            .get("/resource", test_handler)
3229            .post("/resource", test_handler) // Different route for POST
3230            .build();
3231
3232        let ctx = test_context();
3233
3234        // HEAD to GET route should work
3235        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    // =========================================================================
3294    // Exception Handlers Tests
3295    // =========================================================================
3296
3297    // Custom error type for testing
3298    #[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    // Another custom error type
3313    #[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    // --- Unit Tests: Handler Registration ---
3325
3326    #[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    // --- Unit Tests: Handler Invocation ---
3399
3400    #[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    // --- Integration Tests: Custom Error Type Handling ---
3484
3485    #[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    // --- Integration Tests: Override Default Handler ---
3598
3599    #[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        // Only one handler for TestError
3608        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        // Should use the second (overriding) handler
3617        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        // Only one handler for TestError after merge
3634        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        // Merged handlers should override
3643        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        // Start with default handlers
3651        let mut handlers = ExceptionHandlers::with_defaults();
3652
3653        // Override HttpError handler
3654        handlers.register::<crate::HttpError>(|_ctx, err| {
3655            // Custom handler that adds extra header
3656            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        // Still has 3 handlers (HttpError, ValidationErrors, CancelledError)
3663        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        // Check custom header was added
3675        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        // Start with default handlers
3686        let mut handlers = ExceptionHandlers::with_defaults();
3687
3688        // Override ValidationErrors handler
3689        handlers.register::<crate::ValidationErrors>(|_ctx, errs| {
3690            // Custom handler that returns 400 instead of 422
3691            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        // Custom handler returns 400 instead of 422
3709        assert_eq!(response.status().as_u16(), 400);
3710
3711        // Check custom header
3712        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        // CancelledError should return 499 Client Closed Request
3732        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        // Override CancelledError handler to return 504 Gateway Timeout
3740        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        // Custom handler returns 504 instead of 499
3753        assert_eq!(response.status().as_u16(), 504);
3754
3755        // Check custom header
3756        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    // =========================================================================
3777    // Panic Handler Tests
3778    // =========================================================================
3779
3780    #[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        // Simulate a panic payload with &str
3843        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        // Simulate a panic payload with String
3851        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        // Simulate a panic payload with unknown type
3859        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        // Should use handlers2's panic handler (503) after merge
3876        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(); // No panic handler
3885
3886        handlers1.merge(handlers2);
3887
3888        let response = handlers1.handle_panic(None, "test");
3889        // Should keep handlers1's panic handler (400)
3890        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    // =========================================================================
3923    // Lifecycle Hooks Tests
3924    // =========================================================================
3925
3926    // --- Startup Hooks: Registration ---
3927
3928    #[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    // --- Startup Hooks: Execution Order (FIFO) ---
3968
3969    #[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        // FIFO: 1, 2, 3
3997        assert_eq!(*order.lock(), vec![1, 2, 3]);
3998
3999        // Hooks consumed
4000        assert_eq!(app.pending_startup_hooks(), 0);
4001    }
4002
4003    // --- Shutdown Hooks: Execution Order (LIFO) ---
4004
4005    #[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        // LIFO: 3, 2, 1
4029        assert_eq!(*order.lock(), vec![3, 2, 1]);
4030
4031        // Hooks consumed
4032        assert_eq!(app.pending_shutdown_hooks(), 0);
4033    }
4034
4035    // --- Startup Hooks: Success Outcome ---
4036
4037    #[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    // --- Startup Hooks: Fatal Error Aborts ---
4051
4052    #[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(())) // Should not run
4058            .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    // --- Startup Hooks: Non-Fatal Error Continues ---
4073
4074    #[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(())) // Should still run
4080            .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    // --- Startup Hook Error Types ---
4094
4095    #[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    // --- Transfer Shutdown Hooks to Controller ---
4122
4123    #[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        // App hooks consumed
4144        assert_eq!(app.pending_shutdown_hooks(), 0);
4145
4146        // Controller has the hooks
4147        assert_eq!(controller.hook_count(), 2);
4148
4149        // Run via controller (LIFO order)
4150        while let Some(hook) = controller.pop_hook() {
4151            hook.run();
4152        }
4153
4154        // LIFO order via controller
4155        assert_eq!(*order.lock(), vec![2, 1]);
4156    }
4157
4158    // --- Debug Format Includes Hooks ---
4159
4160    #[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    // --- Startup Outcome Accessors ---
4183
4184    #[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    // --- Multiple Non-Fatal Errors ---
4210
4211    #[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    // --- Empty Hooks ---
4232
4233    #[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        // Should not panic with empty hooks
4246        futures_executor::block_on(app.run_shutdown_hooks());
4247    }
4248
4249    // --- Hooks Can Only Run Once ---
4250
4251    #[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        // First run
4265        futures_executor::block_on(app.run_startup_hooks());
4266        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4267
4268        // Second run - no hooks left
4269        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        // First run
4286        futures_executor::block_on(app.run_shutdown_hooks());
4287        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4288
4289        // Second run - no hooks left
4290        futures_executor::block_on(app.run_shutdown_hooks());
4291        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
4292    }
4293
4294    // =========================================================================
4295    // Async Lifecycle Hooks Tests
4296    // =========================================================================
4297
4298    #[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        // FIFO order: 1, 2, 3
4386        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        // Async hooks run first (LIFO within async), then sync hooks (LIFO within sync)
4416        // Async: [2], Sync LIFO: [3, 1] => [2, 3, 1]
4417        assert_eq!(*order.lock(), vec![2, 3, 1]);
4418    }
4419
4420    // =========================================================================
4421    // State Accessible in Handlers Tests
4422    // =========================================================================
4423
4424    #[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        // Multiple state types accessible
4445        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        // Non-existent state returns None
4454        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        // Replace with new value
4467        container.insert(Counter(42));
4468        assert_eq!(container.get::<Counter>().unwrap().0, 42);
4469
4470        // Still only one entry
4471        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    // =========================================================================
4487    // Configuration Validation Tests
4488    // =========================================================================
4489
4490    #[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        // Test various boolean string formats
4523        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        // Invalid values
4540        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    // =========================================================================
4551    // Full Lifecycle Integration Tests
4552    // =========================================================================
4553
4554    #[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        // Phase 1: Startup
4582        let outcome = futures_executor::block_on(app.run_startup_hooks());
4583        assert!(outcome.can_proceed());
4584
4585        // Phase 2: Serve (simulated - just verify app is functional)
4586        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        // Phase 3: Shutdown
4592        futures_executor::block_on(app.run_shutdown_hooks());
4593
4594        // Verify lifecycle order
4595        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        // Startup failed - should not proceed to serving
4614        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        // Before startup
4645        assert!(!state.initialized.load(std::sync::atomic::Ordering::SeqCst));
4646
4647        // Run startup
4648        let outcome = futures_executor::block_on(app.run_startup_hooks());
4649        assert!(outcome.can_proceed());
4650
4651        // After startup - state initialized
4652        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        // Startup fails
4669        let outcome = futures_executor::block_on(app.run_startup_hooks());
4670        assert!(!outcome.can_proceed());
4671
4672        // But shutdown hooks should still be available to run for cleanup
4673        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        // Run full lifecycle
4711        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        // Verify order: startup FIFO, shutdown LIFO
4717        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    // =========================================================================
4730    // Lifespan Tests
4731    // =========================================================================
4732
4733    #[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        // Cleanup should not be called yet
4749        assert!(!*cleanup_called.lock().unwrap());
4750
4751        // Take the cleanup
4752        let cleanup = scope.take_cleanup();
4753        assert!(cleanup.is_some());
4754
4755        // Run the cleanup
4756        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    /// Simulated database pool for testing.
4781    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        // Run startup hooks (which runs lifespan)
4798        let outcome = futures_executor::block_on(app.run_startup_hooks());
4799        assert!(outcome.can_proceed());
4800
4801        // State should now be available
4802        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        // Run startup
4828        let outcome = futures_executor::block_on(app.run_startup_hooks());
4829        assert!(outcome.can_proceed());
4830
4831        // Cleanup not called yet
4832        assert!(cleanup_log.lock().unwrap().is_empty());
4833
4834        // Run shutdown
4835        futures_executor::block_on(app.run_shutdown_hooks());
4836
4837        // Cleanup should have been called
4838        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        // Lifespan should run before regular hooks
4885        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        // Lifespan cleanup should run after regular hooks
4914        let events = log.lock().unwrap();
4915        assert_eq!(*events, vec!["regular_hook", "lifespan_cleanup"]);
4916    }
4917
4918    // =========================================================================
4919    // Sub-Application Mounting Tests
4920    // =========================================================================
4921
4922    #[test]
4923    fn mounted_app_prefix_matching() {
4924        let mounted = MountedApp::new("/admin", App::builder().build());
4925
4926        // Exact match returns "/"
4927        assert_eq!(mounted.match_prefix("/admin"), Some("/"));
4928
4929        // Prefix match with remaining path
4930        assert_eq!(mounted.match_prefix("/admin/users"), Some("/users"));
4931        assert_eq!(mounted.match_prefix("/admin/users/123"), Some("/users/123"));
4932
4933        // No match - different prefix
4934        assert_eq!(mounted.match_prefix("/api"), None);
4935        assert_eq!(mounted.match_prefix("/"), None);
4936
4937        // No match - partial prefix without slash boundary
4938        assert_eq!(mounted.match_prefix("/administrator"), None);
4939    }
4940
4941    #[test]
4942    fn mounted_app_prefix_normalization() {
4943        // Prefix without leading slash gets normalized
4944        let mounted = MountedApp::new("admin", App::builder().build());
4945        assert_eq!(mounted.prefix(), "/admin");
4946
4947        // Trailing slash gets removed
4948        let mounted = MountedApp::new("/admin/", App::builder().build());
4949        assert_eq!(mounted.prefix(), "/admin");
4950
4951        // Multiple trailing slashes get removed
4952        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        // Request to parent app
5007        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        // Request to mounted app (exact prefix)
5015        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        // Request to mounted app (with path)
5023        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        // Request to mounted app for unknown route
5043        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    // ========================================================================
5049    // Debug Config Integration Tests
5050    // ========================================================================
5051
5052    #[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        // Correct token
5121        let headers = vec![("X-Debug-Token".to_owned(), b"my-secret".to_vec())];
5122        assert!(config.debug_config.is_authorized(&headers));
5123
5124        // Wrong token
5125        let headers = vec![("X-Debug-Token".to_owned(), b"wrong".to_vec())];
5126        assert!(!config.debug_config.is_authorized(&headers));
5127
5128        // Missing header
5129        let headers: Vec<(String, Vec<u8>)> = vec![];
5130        assert!(!config.debug_config.is_authorized(&headers));
5131    }
5132}