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::future::Future;
38use std::pin::Pin;
39use std::sync::Arc;
40
41use crate::context::RequestContext;
42use crate::middleware::{BoxFuture, Handler, Middleware, MiddlewareStack};
43use crate::request::{Method, Request};
44use crate::response::{Response, StatusCode};
45use crate::shutdown::ShutdownController;
46use fastapi_router::{Route, RouteLookup, Router};
47
48// ============================================================================
49// Lifecycle Hook Types
50// ============================================================================
51
52/// A startup hook that runs before the server starts accepting connections.
53pub enum StartupHook {
54    /// Synchronous startup function.
55    Sync(Box<dyn FnOnce() -> Result<(), StartupHookError> + Send>),
56    /// Factory for async startup future.
57    AsyncFactory(
58        Box<
59            dyn FnOnce() -> Pin<Box<dyn Future<Output = Result<(), StartupHookError>> + Send>>
60                + Send,
61        >,
62    ),
63}
64
65impl StartupHook {
66    /// Create a synchronous startup hook.
67    pub fn sync<F>(f: F) -> Self
68    where
69        F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,
70    {
71        Self::Sync(Box::new(f))
72    }
73
74    /// Create an async startup hook.
75    pub fn async_fn<F, Fut>(f: F) -> Self
76    where
77        F: FnOnce() -> Fut + Send + 'static,
78        Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,
79    {
80        Self::AsyncFactory(Box::new(move || Box::pin(f())))
81    }
82
83    /// Run the hook synchronously.
84    ///
85    /// For async hooks, this returns the future to await.
86    pub fn run(
87        self,
88    ) -> Result<
89        Option<Pin<Box<dyn Future<Output = Result<(), StartupHookError>> + Send>>>,
90        StartupHookError,
91    > {
92        match self {
93            Self::Sync(f) => f().map(|()| None),
94            Self::AsyncFactory(f) => Ok(Some(f())),
95        }
96    }
97}
98
99/// Error returned when a startup hook fails.
100#[derive(Debug)]
101pub struct StartupHookError {
102    /// Name of the hook that failed (if provided).
103    pub hook_name: Option<String>,
104    /// The underlying error message.
105    pub message: String,
106    /// Whether the application should abort startup.
107    pub abort: bool,
108}
109
110impl StartupHookError {
111    /// Create a new startup hook error.
112    pub fn new(message: impl Into<String>) -> Self {
113        Self {
114            hook_name: None,
115            message: message.into(),
116            abort: true,
117        }
118    }
119
120    /// Set the hook name.
121    #[must_use]
122    pub fn with_hook_name(mut self, name: impl Into<String>) -> Self {
123        self.hook_name = Some(name.into());
124        self
125    }
126
127    /// Set whether to abort startup.
128    #[must_use]
129    pub fn with_abort(mut self, abort: bool) -> Self {
130        self.abort = abort;
131        self
132    }
133
134    /// Create an error that doesn't abort startup (just logs warning).
135    pub fn non_fatal(message: impl Into<String>) -> Self {
136        Self {
137            hook_name: None,
138            message: message.into(),
139            abort: false,
140        }
141    }
142}
143
144impl std::fmt::Display for StartupHookError {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        if let Some(name) = &self.hook_name {
147            write!(f, "Startup hook '{}' failed: {}", name, self.message)
148        } else {
149            write!(f, "Startup hook failed: {}", self.message)
150        }
151    }
152}
153
154impl std::error::Error for StartupHookError {}
155
156/// Outcome of running all startup hooks.
157#[derive(Debug)]
158pub enum StartupOutcome {
159    /// All hooks succeeded.
160    Success,
161    /// Some hooks had non-fatal errors (logged but continued).
162    PartialSuccess {
163        /// Number of hooks that failed with non-fatal errors.
164        warnings: usize,
165    },
166    /// A fatal hook error aborted startup.
167    Aborted(StartupHookError),
168}
169
170impl StartupOutcome {
171    /// Returns true if startup can proceed (Success or PartialSuccess).
172    #[must_use]
173    pub fn can_proceed(&self) -> bool {
174        !matches!(self, Self::Aborted(_))
175    }
176
177    /// Returns the abort error, if any.
178    pub fn into_error(self) -> Option<StartupHookError> {
179        match self {
180            Self::Aborted(e) => Some(e),
181            _ => None,
182        }
183    }
184}
185
186/// A boxed handler function.
187///
188/// Note: The lifetime parameter allows the future to borrow from the context/request.
189pub type BoxHandler = Box<
190    dyn Fn(
191            &RequestContext,
192            &mut Request,
193        ) -> std::pin::Pin<Box<dyn Future<Output = Response> + Send>>
194        + Send
195        + Sync,
196>;
197
198/// A boxed websocket handler function.
199pub type BoxWebSocketHandler = Box<
200    dyn Fn(
201            &RequestContext,
202            &mut Request,
203            crate::websocket::WebSocket,
204        ) -> std::pin::Pin<
205            Box<dyn Future<Output = Result<(), crate::websocket::WebSocketError>> + Send>,
206        > + Send
207        + Sync,
208>;
209
210/// A registered route with its handler.
211#[derive(Clone)]
212pub struct RouteEntry {
213    /// The HTTP method for this route.
214    pub method: Method,
215    /// The path pattern for this route.
216    pub path: String,
217    /// Optional router/OpenAPI metadata for this route.
218    ///
219    /// When routes are created by proc-macros, we preserve a full `fastapi_router::Route`
220    /// so OpenAPI generation can use stable operation IDs, tags, parameters, etc.
221    meta: Option<fastapi_router::Route>,
222    /// The handler function.
223    handler: Arc<BoxHandler>,
224}
225
226impl RouteEntry {
227    /// Creates a new route entry.
228    ///
229    /// Note: The handler's returned future must be `'static`, meaning it should not
230    /// hold references to the context or request beyond the call. If you need to
231    /// borrow from them, clone the data you need first.
232    pub fn new<H, Fut>(method: Method, path: impl Into<String>, handler: H) -> Self
233    where
234        H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
235        Fut: Future<Output = Response> + Send + 'static,
236    {
237        let handler: BoxHandler = Box::new(move |ctx, req| {
238            let fut = handler(ctx, req);
239            Box::pin(fut)
240        });
241        Self {
242            method,
243            path: path.into(),
244            meta: None,
245            handler: Arc::new(handler),
246        }
247    }
248
249    /// Creates a new route entry from a `fastapi_router::Route` metadata object.
250    ///
251    /// This is the preferred constructor for proc-macro generated routes, since it preserves
252    /// OpenAPI metadata (operation_id, tags, request body, etc).
253    pub fn from_route<H, Fut>(route: fastapi_router::Route, handler: H) -> Self
254    where
255        H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
256        Fut: Future<Output = Response> + Send + 'static,
257    {
258        let method = route.method;
259        let path = route.path.clone();
260        let mut entry = Self::new(method, path, handler);
261        entry.meta = Some(route);
262        entry
263    }
264
265    /// Returns the preserved route metadata, if any.
266    pub fn route_meta(&self) -> Option<&fastapi_router::Route> {
267        self.meta.as_ref()
268    }
269
270    /// Calls the handler with the given context and request.
271    pub async fn call(&self, ctx: &RequestContext, req: &mut Request) -> Response {
272        (self.handler)(ctx, req).await
273    }
274}
275
276impl std::fmt::Debug for RouteEntry {
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        f.debug_struct("RouteEntry")
279            .field("method", &self.method)
280            .field("path", &self.path)
281            .field("meta", &self.meta.as_ref().map(|r| r.operation_id.as_str()))
282            .finish_non_exhaustive()
283    }
284}
285
286/// A registered websocket route with its handler.
287#[derive(Clone)]
288pub struct WebSocketRouteEntry {
289    /// The path pattern for this websocket route.
290    pub path: String,
291    /// Handler function.
292    handler: Arc<BoxWebSocketHandler>,
293}
294
295impl WebSocketRouteEntry {
296    /// Create a new websocket route entry.
297    pub fn new<H, Fut>(path: impl Into<String>, handler: H) -> Self
298    where
299        H: Fn(&RequestContext, &mut Request, crate::websocket::WebSocket) -> Fut
300            + Send
301            + Sync
302            + 'static,
303        Fut: Future<Output = Result<(), crate::websocket::WebSocketError>> + Send + 'static,
304    {
305        let handler: BoxWebSocketHandler = Box::new(move |ctx, req, ws| {
306            let fut = handler(ctx, req, ws);
307            Box::pin(fut)
308        });
309        Self {
310            path: path.into(),
311            handler: Arc::new(handler),
312        }
313    }
314
315    /// Calls the websocket handler.
316    pub async fn call(
317        &self,
318        ctx: &RequestContext,
319        req: &mut Request,
320        ws: crate::websocket::WebSocket,
321    ) -> Result<(), crate::websocket::WebSocketError> {
322        (self.handler)(ctx, req, ws).await
323    }
324}
325
326impl std::fmt::Debug for WebSocketRouteEntry {
327    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328        f.debug_struct("WebSocketRouteEntry")
329            .field("path", &self.path)
330            .finish_non_exhaustive()
331    }
332}
333
334/// Type-safe application state container.
335///
336/// State is stored by type and can be accessed by handlers through
337/// the `State<T>` extractor.
338#[derive(Default)]
339pub struct StateContainer {
340    state: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
341}
342
343impl StateContainer {
344    /// Creates a new empty state container.
345    #[must_use]
346    pub fn new() -> Self {
347        Self {
348            state: HashMap::new(),
349        }
350    }
351
352    /// Inserts a value into the state container.
353    ///
354    /// If a value of the same type already exists, it is replaced.
355    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
356        self.state.insert(TypeId::of::<T>(), Arc::new(value));
357    }
358
359    /// Gets a reference to a value in the state container.
360    pub fn get<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
361        self.state
362            .get(&TypeId::of::<T>())
363            .and_then(|v| Arc::clone(v).downcast::<T>().ok())
364    }
365
366    /// Returns true if the state container contains a value of type T.
367    pub fn contains<T: 'static>(&self) -> bool {
368        self.state.contains_key(&TypeId::of::<T>())
369    }
370
371    /// Returns the number of values in the state container.
372    pub fn len(&self) -> usize {
373        self.state.len()
374    }
375
376    /// Returns true if the state container is empty.
377    pub fn is_empty(&self) -> bool {
378        self.state.is_empty()
379    }
380}
381
382impl std::fmt::Debug for StateContainer {
383    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
384        f.debug_struct("StateContainer")
385            .field("count", &self.state.len())
386            .finish()
387    }
388}
389
390// ============================================================================
391// Exception Handler Registry
392// ============================================================================
393
394/// A boxed exception handler function.
395///
396/// The handler receives the RequestContext and a boxed error, and returns a Response.
397pub type BoxExceptionHandler = Box<
398    dyn Fn(&RequestContext, Box<dyn std::error::Error + Send + Sync>) -> Response + Send + Sync,
399>;
400
401/// Registry for custom exception handlers.
402///
403/// This allows applications to register handlers for specific error types,
404/// converting errors into HTTP responses in a customizable way.
405///
406/// # Default Handlers
407///
408/// The registry comes with default handlers for common error types:
409/// - [`HttpError`](crate::HttpError) → JSON response with status/detail
410/// - [`ValidationErrors`](crate::ValidationErrors) → 422 with error list
411///
412/// # Example
413///
414/// ```ignore
415/// use fastapi_core::app::ExceptionHandlers;
416/// use fastapi_core::{RequestContext, Response, HttpError};
417///
418/// #[derive(Debug)]
419/// struct MyCustomError(String);
420///
421/// impl std::fmt::Display for MyCustomError {
422///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423///         write!(f, "Custom error: {}", self.0)
424///     }
425/// }
426///
427/// impl std::error::Error for MyCustomError {}
428///
429/// let handlers = ExceptionHandlers::new()
430///     .handler(|_ctx, err: MyCustomError| {
431///         Response::with_status(StatusCode::BAD_REQUEST)
432///             .body_json(&serde_json::json!({"error": err.0}))
433///     });
434/// ```
435#[derive(Default)]
436pub struct ExceptionHandlers {
437    handlers: HashMap<TypeId, BoxExceptionHandler>,
438}
439
440impl ExceptionHandlers {
441    /// Creates a new empty exception handler registry.
442    #[must_use]
443    pub fn new() -> Self {
444        Self {
445            handlers: HashMap::new(),
446        }
447    }
448
449    /// Creates a registry with default handlers for common error types.
450    #[must_use]
451    pub fn with_defaults() -> Self {
452        let mut handlers = Self::new();
453
454        // Default handler for HttpError
455        handlers.register::<crate::HttpError>(|_ctx, err| {
456            use crate::IntoResponse;
457            err.into_response()
458        });
459
460        // Default handler for ValidationErrors
461        handlers.register::<crate::ValidationErrors>(|_ctx, err| {
462            use crate::IntoResponse;
463            err.into_response()
464        });
465
466        handlers
467    }
468
469    /// Registers a handler for a specific error type.
470    ///
471    /// The handler receives the error value directly (not boxed) for type safety.
472    /// If a handler for the same type already exists, it is replaced.
473    pub fn register<E>(
474        &mut self,
475        handler: impl Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
476    ) where
477        E: std::error::Error + Send + Sync + 'static,
478    {
479        let boxed_handler: BoxExceptionHandler = Box::new(move |ctx, err| {
480            // Try to downcast the error to the expected type
481            match err.downcast::<E>() {
482                Ok(typed_err) => handler(ctx, *typed_err),
483                Err(_) => {
484                    // This shouldn't happen if the registry is used correctly
485                    Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
486                }
487            }
488        });
489        self.handlers.insert(TypeId::of::<E>(), boxed_handler);
490    }
491
492    /// Registers a handler for a specific error type (builder pattern).
493    #[must_use]
494    pub fn handler<E>(
495        mut self,
496        handler: impl Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
497    ) -> Self
498    where
499        E: std::error::Error + Send + Sync + 'static,
500    {
501        self.register::<E>(handler);
502        self
503    }
504
505    /// Handles an error by finding and invoking the appropriate handler.
506    ///
507    /// Returns `Some(Response)` if a handler was found for the error type,
508    /// or `None` if no handler is registered.
509    pub fn handle<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
510    where
511        E: std::error::Error + Send + Sync + 'static,
512    {
513        let type_id = TypeId::of::<E>();
514        self.handlers
515            .get(&type_id)
516            .map(|handler| handler(ctx, Box::new(err)))
517    }
518
519    /// Handles an error, falling back to a default 500 response if no handler is found.
520    pub fn handle_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
521    where
522        E: std::error::Error + Send + Sync + 'static,
523    {
524        self.handle(ctx, err)
525            .unwrap_or_else(|| Response::with_status(StatusCode::INTERNAL_SERVER_ERROR))
526    }
527
528    /// Returns true if a handler is registered for the given error type.
529    pub fn has_handler<E: 'static>(&self) -> bool {
530        self.handlers.contains_key(&TypeId::of::<E>())
531    }
532
533    /// Returns the number of registered handlers.
534    pub fn len(&self) -> usize {
535        self.handlers.len()
536    }
537
538    /// Returns true if no handlers are registered.
539    pub fn is_empty(&self) -> bool {
540        self.handlers.is_empty()
541    }
542
543    /// Merges another handler registry into this one.
544    ///
545    /// Handlers from `other` will override handlers in `self` for the same error types.
546    pub fn merge(&mut self, other: ExceptionHandlers) {
547        self.handlers.extend(other.handlers);
548    }
549}
550
551impl std::fmt::Debug for ExceptionHandlers {
552    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553        f.debug_struct("ExceptionHandlers")
554            .field("count", &self.handlers.len())
555            .finish()
556    }
557}
558
559/// Application configuration.
560#[derive(Debug, Clone)]
561pub struct AppConfig {
562    /// Application name (used in logging and OpenAPI).
563    pub name: String,
564    /// Application version.
565    pub version: String,
566    /// Enable debug mode.
567    pub debug: bool,
568    /// Root path for proxied deployments (FastAPI's `root_path`).
569    ///
570    /// When set, URL generation and redirects should be prefixed with this path.
571    pub root_path: String,
572    /// Whether to include the `root_path` in OpenAPI `servers` entries.
573    pub root_path_in_servers: bool,
574    /// Trailing slash normalization mode for routing.
575    pub trailing_slash_mode: crate::routing::TrailingSlashMode,
576    /// Debug surface configuration (e.g. debug endpoints / debug auth).
577    pub debug_config: crate::error::DebugConfig,
578    /// Maximum request body size in bytes.
579    pub max_body_size: usize,
580    /// Default request timeout in milliseconds.
581    pub request_timeout_ms: u64,
582}
583
584impl Default for AppConfig {
585    fn default() -> Self {
586        Self {
587            name: String::from("fastapi_rust"),
588            version: String::from("0.1.0"),
589            debug: false,
590            root_path: String::new(),
591            root_path_in_servers: false,
592            trailing_slash_mode: crate::routing::TrailingSlashMode::Strict,
593            debug_config: crate::error::DebugConfig::default(),
594            max_body_size: 1024 * 1024, // 1MB
595            request_timeout_ms: 30_000, // 30 seconds
596        }
597    }
598}
599
600impl AppConfig {
601    /// Creates a new configuration with defaults.
602    #[must_use]
603    pub fn new() -> Self {
604        Self::default()
605    }
606
607    /// Sets the application name.
608    #[must_use]
609    pub fn name(mut self, name: impl Into<String>) -> Self {
610        self.name = name.into();
611        self
612    }
613
614    /// Sets the application version.
615    #[must_use]
616    pub fn version(mut self, version: impl Into<String>) -> Self {
617        self.version = version.into();
618        self
619    }
620
621    /// Enables or disables debug mode.
622    #[must_use]
623    pub fn debug(mut self, debug: bool) -> Self {
624        self.debug = debug;
625        self
626    }
627
628    /// Set the root path for proxied deployments.
629    ///
630    /// The value is normalized: trailing slashes are stripped, and the value
631    /// must start with `/` (or be empty). This prevents misconfigured
632    /// root paths from producing broken redirects or URL generation.
633    #[must_use]
634    pub fn root_path(mut self, root_path: impl Into<String>) -> Self {
635        let mut rp = root_path.into();
636        // Strip trailing slashes (consistent with UrlRegistry::with_root_path)
637        while rp.ends_with('/') {
638            rp.pop();
639        }
640        self.root_path = rp;
641        self
642    }
643
644    /// Control whether `root_path` is included in OpenAPI servers.
645    #[must_use]
646    pub fn root_path_in_servers(mut self, enabled: bool) -> Self {
647        self.root_path_in_servers = enabled;
648        self
649    }
650
651    /// Configure trailing slash normalization for routing.
652    #[must_use]
653    pub fn trailing_slash_mode(mut self, mode: crate::routing::TrailingSlashMode) -> Self {
654        self.trailing_slash_mode = mode;
655        self
656    }
657
658    /// Configure debug behavior.
659    #[must_use]
660    pub fn debug_config(mut self, config: crate::error::DebugConfig) -> Self {
661        self.debug_config = config;
662        self
663    }
664
665    /// Sets the maximum request body size.
666    #[must_use]
667    pub fn max_body_size(mut self, size: usize) -> Self {
668        self.max_body_size = size;
669        self
670    }
671
672    /// Sets the default request timeout in milliseconds.
673    #[must_use]
674    pub fn request_timeout_ms(mut self, timeout: u64) -> Self {
675        self.request_timeout_ms = timeout;
676        self
677    }
678}
679
680// ============================================================================
681// OpenAPI Configuration
682// ============================================================================
683
684/// Configuration for OpenAPI documentation generation.
685///
686/// When enabled, the application will automatically generate an OpenAPI 3.1
687/// specification and serve it at the configured endpoint (default `/openapi.json`).
688///
689/// # Example
690///
691/// ```ignore
692/// use fastapi_core::app::{App, OpenApiConfig};
693///
694/// let app = App::builder()
695///     .openapi(OpenApiConfig::new()
696///         .title("My API")
697///         .version("1.0.0")
698///         .description("A sample API"))
699///     .build();
700/// ```
701#[derive(Debug, Clone)]
702pub struct OpenApiConfig {
703    /// Whether OpenAPI documentation is enabled.
704    pub enabled: bool,
705    /// API title for the OpenAPI spec.
706    pub title: String,
707    /// API version for the OpenAPI spec.
708    pub version: String,
709    /// API description.
710    pub description: Option<String>,
711    /// Path to serve the OpenAPI JSON (default: "/openapi.json").
712    pub openapi_path: String,
713    /// Servers to include in the spec.
714    pub servers: Vec<(String, Option<String>)>,
715    /// Tags for organizing operations.
716    pub tags: Vec<(String, Option<String>)>,
717}
718
719impl Default for OpenApiConfig {
720    fn default() -> Self {
721        Self {
722            enabled: true,
723            title: "FastAPI Rust".to_string(),
724            version: "0.1.0".to_string(),
725            description: None,
726            openapi_path: "/openapi.json".to_string(),
727            servers: Vec::new(),
728            tags: Vec::new(),
729        }
730    }
731}
732
733impl OpenApiConfig {
734    /// Create a new OpenAPI configuration with defaults.
735    #[must_use]
736    pub fn new() -> Self {
737        Self::default()
738    }
739
740    /// Set the API title.
741    #[must_use]
742    pub fn title(mut self, title: impl Into<String>) -> Self {
743        self.title = title.into();
744        self
745    }
746
747    /// Set the API version.
748    #[must_use]
749    pub fn version(mut self, version: impl Into<String>) -> Self {
750        self.version = version.into();
751        self
752    }
753
754    /// Set the API description.
755    #[must_use]
756    pub fn description(mut self, description: impl Into<String>) -> Self {
757        self.description = Some(description.into());
758        self
759    }
760
761    /// Set the path for the OpenAPI JSON endpoint.
762    #[must_use]
763    pub fn path(mut self, path: impl Into<String>) -> Self {
764        self.openapi_path = path.into();
765        self
766    }
767
768    /// Add a server to the spec.
769    #[must_use]
770    pub fn server(mut self, url: impl Into<String>, description: Option<String>) -> Self {
771        self.servers.push((url.into(), description));
772        self
773    }
774
775    /// Add a tag to the spec.
776    #[must_use]
777    pub fn tag(mut self, name: impl Into<String>, description: Option<String>) -> Self {
778        self.tags.push((name.into(), description));
779        self
780    }
781
782    /// Disable OpenAPI documentation.
783    #[must_use]
784    pub fn disable(mut self) -> Self {
785        self.enabled = false;
786        self
787    }
788}
789
790/// Builder for constructing an [`App`].
791///
792/// Use this to configure routes, middleware, and shared state before
793/// building the final application.
794///
795/// # Example
796///
797/// ```ignore
798/// let app = App::builder()
799///     .config(AppConfig::new().name("My API"))
800///     .state(DatabasePool::new())
801///     .middleware(LoggingMiddleware::new())
802///     .on_startup(|| {
803///         println!("Server starting...");
804///         Ok(())
805///     })
806///     .on_shutdown(|| {
807///         println!("Server stopping...");
808///     })
809///     .route("/", Method::Get, index_handler)
810///     .route("/items", Method::Get, list_items)
811///     .route("/items", Method::Post, create_item)
812///     .route("/items/{id}", Method::Get, get_item)
813///     .build();
814/// ```
815pub struct AppBuilder {
816    config: AppConfig,
817    routes: Vec<RouteEntry>,
818    ws_routes: Vec<WebSocketRouteEntry>,
819    middleware: Vec<Arc<dyn Middleware>>,
820    state: StateContainer,
821    exception_handlers: ExceptionHandlers,
822    startup_hooks: Vec<StartupHook>,
823    shutdown_hooks: Vec<Box<dyn FnOnce() + Send>>,
824    async_shutdown_hooks: Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
825    openapi_config: Option<OpenApiConfig>,
826    docs_config: Option<crate::docs::DocsConfig>,
827}
828
829impl Default for AppBuilder {
830    fn default() -> Self {
831        Self {
832            config: AppConfig::default(),
833            routes: Vec::new(),
834            ws_routes: Vec::new(),
835            middleware: Vec::new(),
836            state: StateContainer::default(),
837            exception_handlers: ExceptionHandlers::default(),
838            startup_hooks: Vec::new(),
839            shutdown_hooks: Vec::new(),
840            async_shutdown_hooks: Vec::new(),
841            openapi_config: None,
842            docs_config: None,
843        }
844    }
845}
846
847impl AppBuilder {
848    /// Creates a new application builder.
849    #[must_use]
850    pub fn new() -> Self {
851        Self::default()
852    }
853
854    /// Sets the application configuration.
855    #[must_use]
856    pub fn config(mut self, config: AppConfig) -> Self {
857        self.config = config;
858        self
859    }
860
861    /// Enables and configures OpenAPI documentation.
862    ///
863    /// When enabled, the application will automatically generate an OpenAPI 3.1
864    /// specification and serve it at the configured endpoint.
865    ///
866    /// # Example
867    ///
868    /// ```ignore
869    /// let app = App::builder()
870    ///     .openapi(OpenApiConfig::new()
871    ///         .title("My API")
872    ///         .version("1.0.0")
873    ///         .description("A sample API"))
874    ///     .build();
875    /// ```
876    #[must_use]
877    pub fn openapi(mut self, config: OpenApiConfig) -> Self {
878        self.openapi_config = Some(config);
879        self
880    }
881
882    /// Enables and configures interactive API documentation endpoints.
883    ///
884    /// This wires [`crate::docs::DocsConfig`] into the application build and ensures an
885    /// OpenAPI JSON endpoint is served at `config.openapi_path` unless overridden later.
886    ///
887    /// Notes:
888    /// - Docs endpoints are appended during `build()` so they don't appear in the OpenAPI spec.
889    /// - If OpenAPI is already configured, its `openapi_path` is updated to match `DocsConfig`.
890    #[must_use]
891    pub fn enable_docs(mut self, mut config: crate::docs::DocsConfig) -> Self {
892        // If the user didn't customize the docs title, default it to the app name.
893        if config.title == crate::docs::DocsConfig::default().title {
894            config.title.clone_from(&self.config.name);
895        }
896
897        // Keep OpenAPI + docs in sync on the OpenAPI JSON path.
898        match self.openapi_config.take() {
899            Some(mut openapi) => {
900                openapi.openapi_path.clone_from(&config.openapi_path);
901                self.openapi_config = Some(openapi);
902            }
903            None => {
904                self.openapi_config = Some(
905                    OpenApiConfig::new()
906                        .title(self.config.name.clone())
907                        .version(self.config.version.clone())
908                        .path(config.openapi_path.clone()),
909                );
910            }
911        }
912
913        self.docs_config = Some(config);
914        self
915    }
916
917    /// Adds a route to the application.
918    ///
919    /// Routes are matched in the order they are added.
920    #[must_use]
921    pub fn route<H, Fut>(mut self, path: impl Into<String>, method: Method, handler: H) -> Self
922    where
923        H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
924        Fut: Future<Output = Response> + Send + 'static,
925    {
926        self.routes.push(RouteEntry::new(method, path, handler));
927        self
928    }
929
930    /// Adds a pre-built [`RouteEntry`] to the application.
931    ///
932    /// This is primarily used by proc-macro generated route builders that already
933    /// know the method/path and can build an extraction wrapper.
934    #[must_use]
935    pub fn route_entry(mut self, entry: RouteEntry) -> Self {
936        self.routes.push(entry);
937        self
938    }
939
940    /// Adds a websocket route to the application.
941    ///
942    /// WebSocket routes are matched only when the server receives a valid
943    /// websocket upgrade request. They do not appear in OpenAPI output.
944    #[must_use]
945    pub fn websocket<H, Fut>(mut self, path: impl Into<String>, handler: H) -> Self
946    where
947        H: Fn(&RequestContext, &mut Request, crate::websocket::WebSocket) -> Fut
948            + Send
949            + Sync
950            + 'static,
951        Fut: Future<Output = Result<(), crate::websocket::WebSocketError>> + Send + 'static,
952    {
953        self.ws_routes.push(WebSocketRouteEntry::new(path, handler));
954        self
955    }
956
957    /// Adds a GET route.
958    #[must_use]
959    pub fn get<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
960    where
961        H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
962        Fut: Future<Output = Response> + Send + 'static,
963    {
964        self.route(path, Method::Get, handler)
965    }
966
967    /// Adds a POST route.
968    #[must_use]
969    pub fn post<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
970    where
971        H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
972        Fut: Future<Output = Response> + Send + 'static,
973    {
974        self.route(path, Method::Post, handler)
975    }
976
977    /// Adds a PUT route.
978    #[must_use]
979    pub fn put<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
980    where
981        H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
982        Fut: Future<Output = Response> + Send + 'static,
983    {
984        self.route(path, Method::Put, handler)
985    }
986
987    /// Adds a DELETE route.
988    #[must_use]
989    pub fn delete<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
990    where
991        H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
992        Fut: Future<Output = Response> + Send + 'static,
993    {
994        self.route(path, Method::Delete, handler)
995    }
996
997    /// Adds a PATCH route.
998    #[must_use]
999    pub fn patch<H, Fut>(self, path: impl Into<String>, handler: H) -> Self
1000    where
1001        H: Fn(&RequestContext, &mut Request) -> Fut + Send + Sync + 'static,
1002        Fut: Future<Output = Response> + Send + 'static,
1003    {
1004        self.route(path, Method::Patch, handler)
1005    }
1006
1007    /// Adds middleware to the application.
1008    ///
1009    /// Middleware is executed in the order it is added:
1010    /// - `before` hooks run first-to-last
1011    /// - `after` hooks run last-to-first
1012    #[must_use]
1013    pub fn middleware<M: Middleware + 'static>(mut self, middleware: M) -> Self {
1014        self.middleware.push(Arc::new(middleware));
1015        self
1016    }
1017
1018    /// Adds shared state to the application.
1019    ///
1020    /// State can be accessed by handlers through the `State<T>` extractor.
1021    #[must_use]
1022    pub fn state<T: Send + Sync + 'static>(mut self, state: T) -> Self {
1023        self.state.insert(state);
1024        self
1025    }
1026
1027    /// Registers a custom exception handler for a specific error type.
1028    ///
1029    /// When an error of type `E` occurs during request handling, the registered
1030    /// handler will be called to convert it into a response.
1031    ///
1032    /// # Example
1033    ///
1034    /// ```ignore
1035    /// #[derive(Debug)]
1036    /// struct AuthError(String);
1037    ///
1038    /// impl std::fmt::Display for AuthError {
1039    ///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1040    ///         write!(f, "Auth error: {}", self.0)
1041    ///     }
1042    /// }
1043    ///
1044    /// impl std::error::Error for AuthError {}
1045    ///
1046    /// let app = App::builder()
1047    ///     .exception_handler(|_ctx, err: AuthError| {
1048    ///         Response::with_status(StatusCode::UNAUTHORIZED)
1049    ///             .header("www-authenticate", b"Bearer".to_vec())
1050    ///             .body_json(&json!({"error": err.0}))
1051    ///     })
1052    ///     .build();
1053    /// ```
1054    #[must_use]
1055    pub fn exception_handler<E, H>(mut self, handler: H) -> Self
1056    where
1057        E: std::error::Error + Send + Sync + 'static,
1058        H: Fn(&RequestContext, E) -> Response + Send + Sync + 'static,
1059    {
1060        self.exception_handlers.register::<E>(handler);
1061        self
1062    }
1063
1064    /// Sets the exception handlers registry.
1065    ///
1066    /// This replaces any previously registered handlers.
1067    #[must_use]
1068    pub fn exception_handlers(mut self, handlers: ExceptionHandlers) -> Self {
1069        self.exception_handlers = handlers;
1070        self
1071    }
1072
1073    /// Uses default exception handlers for common error types.
1074    ///
1075    /// This registers handlers for:
1076    /// - [`HttpError`](crate::HttpError) → JSON response with status/detail
1077    /// - [`ValidationErrors`](crate::ValidationErrors) → 422 with error list
1078    #[must_use]
1079    pub fn with_default_exception_handlers(mut self) -> Self {
1080        self.exception_handlers = ExceptionHandlers::with_defaults();
1081        self
1082    }
1083
1084    // =========================================================================
1085    // Lifecycle Hooks
1086    // =========================================================================
1087
1088    /// Registers a synchronous startup hook.
1089    ///
1090    /// Startup hooks run before the server starts accepting connections,
1091    /// in the order they are registered (FIFO).
1092    ///
1093    /// # Example
1094    ///
1095    /// ```ignore
1096    /// let app = App::builder()
1097    ///     .on_startup(|| {
1098    ///         println!("Connecting to database...");
1099    ///         Ok(())
1100    ///     })
1101    ///     .on_startup(|| {
1102    ///         println!("Loading configuration...");
1103    ///         Ok(())
1104    ///     })
1105    ///     .build();
1106    /// ```
1107    #[must_use]
1108    pub fn on_startup<F>(mut self, hook: F) -> Self
1109    where
1110        F: FnOnce() -> Result<(), StartupHookError> + Send + 'static,
1111    {
1112        self.startup_hooks.push(StartupHook::Sync(Box::new(hook)));
1113        self
1114    }
1115
1116    /// Registers an async startup hook.
1117    ///
1118    /// Async startup hooks are awaited in registration order.
1119    ///
1120    /// # Example
1121    ///
1122    /// ```ignore
1123    /// let app = App::builder()
1124    ///     .on_startup_async(|| async {
1125    ///         let pool = connect_to_database().await?;
1126    ///         Ok(())
1127    ///     })
1128    ///     .build();
1129    /// ```
1130    #[must_use]
1131    pub fn on_startup_async<F, Fut>(mut self, hook: F) -> Self
1132    where
1133        F: FnOnce() -> Fut + Send + 'static,
1134        Fut: Future<Output = Result<(), StartupHookError>> + Send + 'static,
1135    {
1136        self.startup_hooks.push(StartupHook::AsyncFactory(Box::new(
1137            move || Box::pin(hook()),
1138        )));
1139        self
1140    }
1141
1142    /// Registers a synchronous shutdown hook.
1143    ///
1144    /// Shutdown hooks run after the server stops accepting connections
1145    /// and all in-flight requests complete (or are cancelled).
1146    ///
1147    /// Shutdown hooks run in reverse registration order (LIFO), matching
1148    /// typical resource cleanup patterns (last acquired, first released).
1149    ///
1150    /// # Example
1151    ///
1152    /// ```ignore
1153    /// let app = App::builder()
1154    ///     .on_shutdown(|| {
1155    ///         println!("Closing database connections...");
1156    ///     })
1157    ///     .build();
1158    /// ```
1159    #[must_use]
1160    pub fn on_shutdown<F>(mut self, hook: F) -> Self
1161    where
1162        F: FnOnce() + Send + 'static,
1163    {
1164        self.shutdown_hooks.push(Box::new(hook));
1165        self
1166    }
1167
1168    /// Registers an async shutdown hook.
1169    ///
1170    /// Async shutdown hooks are awaited in reverse registration order (LIFO).
1171    ///
1172    /// # Example
1173    ///
1174    /// ```ignore
1175    /// let app = App::builder()
1176    ///     .on_shutdown_async(|| async {
1177    ///         flush_metrics().await;
1178    ///     })
1179    ///     .build();
1180    /// ```
1181    #[must_use]
1182    pub fn on_shutdown_async<F, Fut>(mut self, hook: F) -> Self
1183    where
1184        F: FnOnce() -> Fut + Send + 'static,
1185        Fut: Future<Output = ()> + Send + 'static,
1186    {
1187        self.async_shutdown_hooks
1188            .push(Box::new(move || Box::pin(hook())));
1189        self
1190    }
1191
1192    /// Returns the number of registered startup hooks.
1193    #[must_use]
1194    pub fn startup_hook_count(&self) -> usize {
1195        self.startup_hooks.len()
1196    }
1197
1198    /// Returns the number of registered shutdown hooks.
1199    #[must_use]
1200    pub fn shutdown_hook_count(&self) -> usize {
1201        self.shutdown_hooks.len() + self.async_shutdown_hooks.len()
1202    }
1203
1204    /// Builds the application.
1205    ///
1206    /// This consumes the builder and returns the configured [`App`].
1207    ///
1208    /// # Panics
1209    ///
1210    /// Panics if any routes conflict (same method + structurally identical path pattern).
1211    #[must_use]
1212    #[allow(clippy::too_many_lines)]
1213    pub fn build(mut self) -> App {
1214        // Generate OpenAPI spec if configured
1215        let (openapi_spec, openapi_path) = if let Some(ref openapi_config) = self.openapi_config {
1216            if openapi_config.enabled {
1217                let spec = self.generate_openapi_spec(openapi_config);
1218                let spec_json =
1219                    serde_json::to_string_pretty(&spec).unwrap_or_else(|_| "{}".to_string());
1220                (
1221                    Some(Arc::new(spec_json)),
1222                    Some(openapi_config.openapi_path.clone()),
1223                )
1224            } else {
1225                (None, None)
1226            }
1227        } else {
1228            (None, None)
1229        };
1230
1231        // Add OpenAPI endpoint if spec was generated
1232        if let (Some(spec), Some(path)) = (&openapi_spec, &openapi_path) {
1233            let spec_clone = Arc::clone(spec);
1234            self.routes.push(RouteEntry::new(
1235                Method::Get,
1236                path.clone(),
1237                move |_ctx: &RequestContext, _req: &mut Request| {
1238                    let spec = Arc::clone(&spec_clone);
1239                    async move {
1240                        Response::ok()
1241                            .header("content-type", b"application/json".to_vec())
1242                            .body(crate::response::ResponseBody::Bytes(
1243                                spec.as_bytes().to_vec(),
1244                            ))
1245                    }
1246                },
1247            ));
1248        }
1249
1250        // Add interactive docs endpoints (Swagger UI / ReDoc) if configured and OpenAPI is enabled.
1251        //
1252        // These are appended after OpenAPI generation so they do not appear in the OpenAPI spec.
1253        if let (Some(openapi_url), Some(docs_config)) = (openapi_path.clone(), self.docs_config) {
1254            let docs_config = Arc::new(docs_config);
1255            let openapi_url = Arc::new(openapi_url);
1256
1257            if let Some(docs_path) = docs_config.docs_path.clone() {
1258                let cfg = Arc::clone(&docs_config);
1259                let url = Arc::clone(&openapi_url);
1260                self.routes.push(RouteEntry::new(
1261                    Method::Get,
1262                    docs_path.clone(),
1263                    move |_ctx: &RequestContext, _req: &mut Request| {
1264                        let cfg = Arc::clone(&cfg);
1265                        let url = Arc::clone(&url);
1266                        async move { crate::docs::swagger_ui_response(&cfg, &url) }
1267                    },
1268                ));
1269
1270                // FastAPI uses `/docs/oauth2-redirect` by default, relative to docs_path.
1271                let docs_prefix = docs_path.trim_end_matches('/');
1272                let oauth2_redirect_path = if docs_prefix.is_empty() {
1273                    "/oauth2-redirect".to_string()
1274                } else {
1275                    format!("{docs_prefix}/oauth2-redirect")
1276                };
1277                self.routes.push(RouteEntry::new(
1278                    Method::Get,
1279                    oauth2_redirect_path,
1280                    |_ctx: &RequestContext, _req: &mut Request| async move {
1281                        crate::docs::oauth2_redirect_response()
1282                    },
1283                ));
1284            }
1285
1286            if let Some(redoc_path) = docs_config.redoc_path.clone() {
1287                let cfg = Arc::clone(&docs_config);
1288                let url = Arc::clone(&openapi_url);
1289                self.routes.push(RouteEntry::new(
1290                    Method::Get,
1291                    redoc_path,
1292                    move |_ctx: &RequestContext, _req: &mut Request| {
1293                        let cfg = Arc::clone(&cfg);
1294                        let url = Arc::clone(&url);
1295                        async move { crate::docs::redoc_response(&cfg, &url) }
1296                    },
1297                ));
1298            }
1299        }
1300
1301        let mut middleware_stack = MiddlewareStack::with_capacity(self.middleware.len());
1302        for mw in self.middleware {
1303            middleware_stack.push_arc(mw);
1304        }
1305
1306        // Build the trie-based router from registered routes
1307        let mut router = Router::new();
1308        for entry in &self.routes {
1309            let route = entry
1310                .route_meta()
1311                .cloned()
1312                .unwrap_or_else(|| Route::new(entry.method, &entry.path));
1313            router
1314                .add(route)
1315                .expect("route conflict during App::build()");
1316        }
1317
1318        // Build the websocket router separately (so websocket + HTTP can share the same path).
1319        let mut ws_router = Router::new();
1320        for entry in &self.ws_routes {
1321            ws_router
1322                .add(Route::new(Method::Get, &entry.path))
1323                .expect("websocket route conflict during App::build()");
1324        }
1325
1326        App {
1327            config: self.config,
1328            routes: self.routes,
1329            ws_routes: self.ws_routes,
1330            router,
1331            ws_router,
1332            middleware: middleware_stack,
1333            state: Arc::new(self.state),
1334            exception_handlers: Arc::new(self.exception_handlers),
1335            dependency_overrides: Arc::new(crate::dependency::DependencyOverrides::new()),
1336            startup_hooks: parking_lot::Mutex::new(self.startup_hooks),
1337            shutdown_hooks: parking_lot::Mutex::new(self.shutdown_hooks),
1338            async_shutdown_hooks: parking_lot::Mutex::new(self.async_shutdown_hooks),
1339            openapi_spec,
1340        }
1341    }
1342
1343    /// Generate an OpenAPI spec from registered routes.
1344    fn generate_openapi_spec(&self, config: &OpenApiConfig) -> fastapi_openapi::OpenApi {
1345        use fastapi_openapi::{OpenApiBuilder, Operation, Response as OAResponse};
1346        use std::collections::HashMap;
1347
1348        let mut builder = OpenApiBuilder::new(&config.title, &config.version);
1349
1350        // Add description if provided
1351        if let Some(ref desc) = config.description {
1352            builder = builder.description(desc);
1353        }
1354
1355        // Add servers
1356        for (url, desc) in &config.servers {
1357            builder = builder.server(url, desc.clone());
1358        }
1359
1360        // Add tags
1361        for (name, desc) in &config.tags {
1362            builder = builder.tag(name, desc.clone());
1363        }
1364
1365        // Add operations for each registered route
1366        for entry in &self.routes {
1367            if let Some(route) = entry.route_meta() {
1368                builder.add_route(route);
1369                continue;
1370            }
1371
1372            // Create a basic operation with default response
1373            let mut responses = HashMap::new();
1374            responses.insert(
1375                "200".to_string(),
1376                OAResponse {
1377                    description: "Successful response".to_string(),
1378                    content: HashMap::new(),
1379                },
1380            );
1381
1382            let operation = Operation {
1383                operation_id: Some(format!(
1384                    "{}_{}",
1385                    entry.method.as_str().to_lowercase(),
1386                    entry
1387                        .path
1388                        .replace('/', "_")
1389                        .replace(['{', '}'], "")
1390                        .trim_matches('_')
1391                )),
1392                summary: None,
1393                description: None,
1394                tags: Vec::new(),
1395                parameters: Vec::new(),
1396                request_body: None,
1397                responses,
1398                deprecated: false,
1399            };
1400
1401            builder = builder.operation(entry.method.as_str(), &entry.path, operation);
1402        }
1403
1404        builder.build()
1405    }
1406}
1407
1408impl std::fmt::Debug for AppBuilder {
1409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1410        f.debug_struct("AppBuilder")
1411            .field("config", &self.config)
1412            .field("routes", &self.routes.len())
1413            .field("middleware", &self.middleware.len())
1414            .field("state", &self.state)
1415            .field("exception_handlers", &self.exception_handlers)
1416            .field("startup_hooks", &self.startup_hooks.len())
1417            .field("shutdown_hooks", &self.shutdown_hook_count())
1418            .finish()
1419    }
1420}
1421
1422/// A configured web application.
1423///
1424/// The `App` holds all routes, middleware, state, and lifecycle hooks,
1425/// and provides methods to handle incoming requests.
1426pub struct App {
1427    config: AppConfig,
1428    routes: Vec<RouteEntry>,
1429    ws_routes: Vec<WebSocketRouteEntry>,
1430    router: Router,
1431    ws_router: Router,
1432    middleware: MiddlewareStack,
1433    state: Arc<StateContainer>,
1434    exception_handlers: Arc<ExceptionHandlers>,
1435    dependency_overrides: Arc<crate::dependency::DependencyOverrides>,
1436    startup_hooks: parking_lot::Mutex<Vec<StartupHook>>,
1437    shutdown_hooks: parking_lot::Mutex<Vec<Box<dyn FnOnce() + Send>>>,
1438    async_shutdown_hooks: parking_lot::Mutex<
1439        Vec<Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()> + Send>> + Send>>,
1440    >,
1441    /// The generated OpenAPI specification (if enabled).
1442    openapi_spec: Option<Arc<String>>,
1443}
1444
1445impl App {
1446    /// Creates a new application builder.
1447    #[must_use]
1448    pub fn builder() -> AppBuilder {
1449        AppBuilder::new()
1450    }
1451
1452    /// Create an in-process test client for this app.
1453    ///
1454    /// This takes `Arc<Self>` so tests can keep using shared `Arc<App>` values
1455    /// without extra boilerplate.
1456    #[cfg(feature = "testing")]
1457    #[must_use]
1458    pub fn test_client(self: Arc<Self>) -> crate::testing::TestClient<Arc<Self>> {
1459        crate::testing::TestClient::new(self)
1460    }
1461
1462    /// Create an in-process test client with a deterministic seed.
1463    #[cfg(feature = "testing")]
1464    #[must_use]
1465    pub fn test_client_with_seed(
1466        self: Arc<Self>,
1467        seed: u64,
1468    ) -> crate::testing::TestClient<Arc<Self>> {
1469        crate::testing::TestClient::with_seed(self, seed)
1470    }
1471
1472    /// Returns the application configuration.
1473    #[must_use]
1474    pub fn config(&self) -> &AppConfig {
1475        &self.config
1476    }
1477
1478    /// Returns the number of registered routes.
1479    #[must_use]
1480    pub fn route_count(&self) -> usize {
1481        self.routes.len()
1482    }
1483
1484    /// Returns the number of registered websocket routes.
1485    #[must_use]
1486    pub fn websocket_route_count(&self) -> usize {
1487        self.ws_routes.len()
1488    }
1489
1490    /// Returns true if a websocket route matches the given path.
1491    #[must_use]
1492    pub fn has_websocket_route(&self, path: &str) -> bool {
1493        matches!(
1494            self.ws_router.lookup(path, Method::Get),
1495            RouteLookup::Match(_)
1496        )
1497    }
1498
1499    /// Returns an iterator over route metadata (method, path).
1500    ///
1501    /// This is useful for generating OpenAPI specifications or debugging.
1502    pub fn routes(&self) -> impl Iterator<Item = (Method, &str)> {
1503        self.routes.iter().map(|r| (r.method, r.path.as_str()))
1504    }
1505
1506    /// Returns the generated OpenAPI specification JSON, if OpenAPI is enabled.
1507    ///
1508    /// # Example
1509    ///
1510    /// ```ignore
1511    /// let app = App::builder()
1512    ///     .openapi(OpenApiConfig::new().title("My API"))
1513    ///     .build();
1514    ///
1515    /// if let Some(spec) = app.openapi_spec() {
1516    ///     println!("OpenAPI spec: {}", spec);
1517    /// }
1518    /// ```
1519    #[must_use]
1520    pub fn openapi_spec(&self) -> Option<&str> {
1521        self.openapi_spec.as_ref().map(|s| s.as_str())
1522    }
1523
1524    /// Returns the shared state container.
1525    #[must_use]
1526    pub fn state(&self) -> &Arc<StateContainer> {
1527        &self.state
1528    }
1529
1530    /// Gets a reference to shared state of type T.
1531    pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
1532        self.state.get::<T>()
1533    }
1534
1535    /// Returns the exception handlers registry.
1536    #[must_use]
1537    pub fn exception_handlers(&self) -> &Arc<ExceptionHandlers> {
1538        &self.exception_handlers
1539    }
1540
1541    /// Register a fixed override value for a dependency type.
1542    ///
1543    /// This is a test-focused convenience API; production code typically wires
1544    /// dependencies via [`crate::dependency::FromDependency`] implementations.
1545    pub fn override_dependency_value<T>(&self, value: T)
1546    where
1547        T: crate::dependency::FromDependency,
1548    {
1549        self.dependency_overrides.insert_value(value);
1550    }
1551
1552    /// Clear all registered dependency overrides.
1553    pub fn clear_dependency_overrides(&self) {
1554        self.dependency_overrides.clear();
1555    }
1556
1557    /// Returns the shared dependency overrides registry.
1558    #[must_use]
1559    pub fn dependency_overrides(&self) -> Arc<crate::dependency::DependencyOverrides> {
1560        Arc::clone(&self.dependency_overrides)
1561    }
1562
1563    /// Take request-scoped background tasks (if any) from a request.
1564    ///
1565    /// The HTTP server calls this after writing the response so any deferred work
1566    /// runs outside the main request handler path.
1567    pub fn take_background_tasks(req: &mut Request) -> Option<crate::request::BackgroundTasks> {
1568        req.take_extension::<crate::request::BackgroundTasks>()
1569    }
1570
1571    /// Handles an error using registered exception handlers.
1572    ///
1573    /// If a handler is registered for the error type, it will be invoked.
1574    /// Otherwise, returns `None`.
1575    pub fn handle_error<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
1576    where
1577        E: std::error::Error + Send + Sync + 'static,
1578    {
1579        self.exception_handlers.handle(ctx, err)
1580    }
1581
1582    /// Handles an error, returning a 500 response if no handler is registered.
1583    pub fn handle_error_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
1584    where
1585        E: std::error::Error + Send + Sync + 'static,
1586    {
1587        self.exception_handlers.handle_or_default(ctx, err)
1588    }
1589
1590    /// Handles an incoming request.
1591    ///
1592    /// This matches the request against registered routes, runs middleware,
1593    /// and returns the response.
1594    pub async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response {
1595        // Use the trie-based router for efficient matching with path parameter extraction
1596        match self.router.lookup(req.path(), req.method()) {
1597            RouteLookup::Match(route_match) => {
1598                // Find the handler by matching the route path
1599                let entry = self.routes.iter().find(|e| {
1600                    e.method == route_match.route.method && e.path == route_match.route.path
1601                });
1602
1603                let Some(entry) = entry else {
1604                    // This should never happen if router and routes are in sync
1605                    return Response::with_status(StatusCode::INTERNAL_SERVER_ERROR);
1606                };
1607
1608                // Store extracted path parameters in the request
1609                if !route_match.params.is_empty() {
1610                    let path_params = crate::extract::PathParams::from_pairs(
1611                        route_match
1612                            .params
1613                            .iter()
1614                            .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
1615                            .collect(),
1616                    );
1617                    req.insert_extension(path_params);
1618                }
1619
1620                // Create a handler that wraps the route
1621                let handler = RouteHandler { entry };
1622                self.middleware.execute(&handler, ctx, req).await
1623            }
1624            RouteLookup::MethodNotAllowed { allowed } => {
1625                // Auto-handle `OPTIONS` by returning 204 with an `Allow` header.
1626                // For other methods, return 405 with the `Allow` header.
1627                if req.method() == Method::Options {
1628                    let mut methods = allowed.methods().to_vec();
1629                    if !methods.contains(&Method::Options) {
1630                        methods.push(Method::Options);
1631                    }
1632                    let allow = fastapi_router::AllowedMethods::new(methods);
1633                    Response::with_status(StatusCode::NO_CONTENT)
1634                        .header("allow", allow.header_value().as_bytes().to_vec())
1635                } else {
1636                    Response::with_status(StatusCode::METHOD_NOT_ALLOWED)
1637                        .header("allow", allowed.header_value().as_bytes().to_vec())
1638                }
1639            }
1640            RouteLookup::NotFound => Response::with_status(StatusCode::NOT_FOUND),
1641        }
1642    }
1643
1644    /// Handles an incoming websocket upgrade request after the handshake has been accepted.
1645    ///
1646    /// The HTTP server is responsible for validating the upgrade headers and writing the 101
1647    /// response. This function only performs path matching and calls the websocket handler.
1648    pub async fn handle_websocket(
1649        &self,
1650        ctx: &RequestContext,
1651        req: &mut Request,
1652        ws: crate::websocket::WebSocket,
1653    ) -> Result<(), crate::websocket::WebSocketError> {
1654        match self.ws_router.lookup(req.path(), Method::Get) {
1655            RouteLookup::Match(route_match) => {
1656                let entry = self
1657                    .ws_routes
1658                    .iter()
1659                    .find(|e| e.path == route_match.route.path);
1660                let Some(entry) = entry else {
1661                    return Err(crate::websocket::WebSocketError::Protocol(
1662                        "websocket route missing handler",
1663                    ));
1664                };
1665
1666                if !route_match.params.is_empty() {
1667                    let path_params = crate::extract::PathParams::from_pairs(
1668                        route_match
1669                            .params
1670                            .iter()
1671                            .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
1672                            .collect(),
1673                    );
1674                    req.insert_extension(path_params);
1675                }
1676
1677                entry.call(ctx, req, ws).await
1678            }
1679            _ => Err(crate::websocket::WebSocketError::Protocol(
1680                "no websocket route matched",
1681            )),
1682        }
1683    }
1684
1685    // =========================================================================
1686    // Lifecycle Hook Execution
1687    // =========================================================================
1688
1689    /// Runs all startup hooks.
1690    ///
1691    /// Hooks run in registration order (FIFO). If a hook returns an error
1692    /// with `abort: true`, execution stops and returns `StartupOutcome::Aborted`.
1693    ///
1694    /// This consumes the startup hooks - they can only be run once.
1695    ///
1696    /// # Returns
1697    ///
1698    /// - `StartupOutcome::Success` if all hooks succeeded
1699    /// - `StartupOutcome::PartialSuccess` if some hooks had non-fatal errors
1700    /// - `StartupOutcome::Aborted` if a fatal hook error occurred
1701    pub async fn run_startup_hooks(&self) -> StartupOutcome {
1702        let hooks: Vec<StartupHook> = std::mem::take(&mut *self.startup_hooks.lock());
1703        let mut warnings = 0;
1704
1705        for hook in hooks {
1706            match hook.run() {
1707                Ok(None) => {
1708                    // Sync hook succeeded
1709                }
1710                Ok(Some(fut)) => {
1711                    // Async hook - await it
1712                    match fut.await {
1713                        Ok(()) => {}
1714                        Err(e) if e.abort => {
1715                            return StartupOutcome::Aborted(e);
1716                        }
1717                        Err(_) => {
1718                            warnings += 1;
1719                        }
1720                    }
1721                }
1722                Err(e) if e.abort => {
1723                    return StartupOutcome::Aborted(e);
1724                }
1725                Err(_) => {
1726                    warnings += 1;
1727                }
1728            }
1729        }
1730
1731        if warnings > 0 {
1732            StartupOutcome::PartialSuccess { warnings }
1733        } else {
1734            StartupOutcome::Success
1735        }
1736    }
1737
1738    /// Runs all shutdown hooks.
1739    ///
1740    /// Hooks run in reverse registration order (LIFO). Errors are logged
1741    /// but do not stop other hooks from running.
1742    ///
1743    /// This consumes the shutdown hooks - they can only be run once.
1744    pub async fn run_shutdown_hooks(&self) {
1745        // Run async hooks first (LIFO)
1746        let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
1747        for hook in async_hooks.into_iter().rev() {
1748            let fut = hook();
1749            fut.await;
1750        }
1751
1752        // Run sync hooks (LIFO)
1753        let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
1754        for hook in sync_hooks.into_iter().rev() {
1755            hook();
1756        }
1757    }
1758
1759    /// Transfers shutdown hooks to a [`ShutdownController`].
1760    ///
1761    /// This moves all registered shutdown hooks to the controller, which
1762    /// will run them during the appropriate shutdown phase.
1763    ///
1764    /// Call this when integrating with the server's shutdown mechanism.
1765    pub fn transfer_shutdown_hooks(&self, controller: &ShutdownController) {
1766        // Transfer sync hooks (they'll run in LIFO due to how pop_hook works)
1767        let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
1768        for hook in sync_hooks {
1769            controller.register_hook(hook);
1770        }
1771
1772        // Transfer async hooks
1773        let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
1774        for hook in async_hooks {
1775            controller.register_async_hook(move || hook());
1776        }
1777    }
1778
1779    /// Returns the number of pending startup hooks.
1780    #[must_use]
1781    pub fn pending_startup_hooks(&self) -> usize {
1782        self.startup_hooks.lock().len()
1783    }
1784
1785    /// Returns the number of pending shutdown hooks.
1786    #[must_use]
1787    pub fn pending_shutdown_hooks(&self) -> usize {
1788        self.shutdown_hooks.lock().len() + self.async_shutdown_hooks.lock().len()
1789    }
1790}
1791
1792impl std::fmt::Debug for App {
1793    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1794        f.debug_struct("App")
1795            .field("config", &self.config)
1796            .field("routes", &self.routes.len())
1797            .field("middleware", &self.middleware.len())
1798            .field("state", &self.state)
1799            .field("exception_handlers", &self.exception_handlers)
1800            .field("startup_hooks", &self.startup_hooks.lock().len())
1801            .field("shutdown_hooks", &self.pending_shutdown_hooks())
1802            .finish()
1803    }
1804}
1805
1806// Allow `App` to be used anywhere a middleware `Handler` is expected (e.g. TestClient).
1807impl Handler for App {
1808    fn call<'a>(
1809        &'a self,
1810        ctx: &'a RequestContext,
1811        req: &'a mut Request,
1812    ) -> BoxFuture<'a, Response> {
1813        Box::pin(async move { self.handle(ctx, req).await })
1814    }
1815
1816    fn dependency_overrides(&self) -> Option<Arc<crate::dependency::DependencyOverrides>> {
1817        Some(Arc::clone(&self.dependency_overrides))
1818    }
1819}
1820
1821/// Handler wrapper for a route entry.
1822struct RouteHandler<'a> {
1823    entry: &'a RouteEntry,
1824}
1825
1826impl<'a> Handler for RouteHandler<'a> {
1827    fn call<'b>(
1828        &'b self,
1829        ctx: &'b RequestContext,
1830        req: &'b mut Request,
1831    ) -> BoxFuture<'b, Response> {
1832        let handler = self.entry.handler.clone();
1833        Box::pin(async move { handler(ctx, req).await })
1834    }
1835}
1836
1837#[cfg(test)]
1838mod tests {
1839    use super::*;
1840
1841    use crate::response::ResponseBody;
1842
1843    // Test handlers that return 'static futures (no borrowing from parameters)
1844    fn test_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
1845        std::future::ready(Response::ok().body(ResponseBody::Bytes(b"Hello, World!".to_vec())))
1846    }
1847
1848    fn health_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
1849        std::future::ready(Response::ok().body(ResponseBody::Bytes(b"OK".to_vec())))
1850    }
1851
1852    fn test_context() -> RequestContext {
1853        let cx = asupersync::Cx::for_testing();
1854        RequestContext::new(cx, 1)
1855    }
1856
1857    #[test]
1858    fn app_builder_creates_app() {
1859        let app = App::builder()
1860            .config(AppConfig::new().name("Test App"))
1861            .get("/", test_handler)
1862            .get("/health", health_handler)
1863            .build();
1864
1865        assert_eq!(app.route_count(), 2);
1866        assert_eq!(app.config().name, "Test App");
1867    }
1868
1869    #[test]
1870    fn app_config_builder() {
1871        let config = AppConfig::new()
1872            .name("My API")
1873            .version("1.0.0")
1874            .debug(true)
1875            .max_body_size(2 * 1024 * 1024)
1876            .request_timeout_ms(60_000);
1877
1878        assert_eq!(config.name, "My API");
1879        assert_eq!(config.version, "1.0.0");
1880        assert!(config.debug);
1881        assert_eq!(config.max_body_size, 2 * 1024 * 1024);
1882        assert_eq!(config.request_timeout_ms, 60_000);
1883    }
1884
1885    #[test]
1886    fn state_container_insert_and_get() {
1887        #[derive(Debug, PartialEq)]
1888        struct MyState {
1889            value: i32,
1890        }
1891
1892        let mut container = StateContainer::new();
1893        container.insert(MyState { value: 42 });
1894
1895        let state = container.get::<MyState>();
1896        assert!(state.is_some());
1897        assert_eq!(state.unwrap().value, 42);
1898    }
1899
1900    #[test]
1901    fn state_container_multiple_types() {
1902        struct TypeA(i32);
1903        struct TypeB(String);
1904
1905        let mut container = StateContainer::new();
1906        container.insert(TypeA(1));
1907        container.insert(TypeB("hello".to_string()));
1908
1909        assert!(container.contains::<TypeA>());
1910        assert!(container.contains::<TypeB>());
1911        assert!(!container.contains::<i64>());
1912
1913        assert_eq!(container.get::<TypeA>().unwrap().0, 1);
1914        assert_eq!(container.get::<TypeB>().unwrap().0, "hello");
1915    }
1916
1917    #[test]
1918    fn app_builder_with_state() {
1919        struct DbPool {
1920            connection_count: usize,
1921        }
1922
1923        let app = App::builder()
1924            .state(DbPool {
1925                connection_count: 10,
1926            })
1927            .get("/", test_handler)
1928            .build();
1929
1930        let pool = app.get_state::<DbPool>();
1931        assert!(pool.is_some());
1932        assert_eq!(pool.unwrap().connection_count, 10);
1933    }
1934
1935    #[test]
1936    fn app_handles_get_request() {
1937        let app = App::builder().get("/", test_handler).build();
1938
1939        let ctx = test_context();
1940        let mut req = Request::new(Method::Get, "/");
1941
1942        let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1943        assert_eq!(response.status().as_u16(), 200);
1944    }
1945
1946    #[test]
1947    fn app_returns_404_for_unknown_path() {
1948        let app = App::builder().get("/", test_handler).build();
1949
1950        let ctx = test_context();
1951        let mut req = Request::new(Method::Get, "/unknown");
1952
1953        let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1954        assert_eq!(response.status().as_u16(), 404);
1955    }
1956
1957    #[test]
1958    fn app_returns_405_for_wrong_method() {
1959        let app = App::builder().get("/", test_handler).build();
1960
1961        let ctx = test_context();
1962        let mut req = Request::new(Method::Post, "/");
1963
1964        let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1965        assert_eq!(response.status().as_u16(), 405);
1966    }
1967
1968    #[test]
1969    fn app_builder_all_methods() {
1970        let app = App::builder()
1971            .get("/get", test_handler)
1972            .post("/post", test_handler)
1973            .put("/put", test_handler)
1974            .delete("/delete", test_handler)
1975            .patch("/patch", test_handler)
1976            .build();
1977
1978        assert_eq!(app.route_count(), 5);
1979    }
1980
1981    #[test]
1982    fn route_entry_debug() {
1983        let entry = RouteEntry::new(Method::Get, "/test", test_handler);
1984        let debug = format!("{:?}", entry);
1985        assert!(debug.contains("RouteEntry"));
1986        assert!(debug.contains("Get"));
1987        assert!(debug.contains("/test"));
1988    }
1989
1990    #[test]
1991    fn app_with_middleware() {
1992        use crate::middleware::NoopMiddleware;
1993
1994        let app = App::builder()
1995            .middleware(NoopMiddleware)
1996            .middleware(NoopMiddleware)
1997            .get("/", test_handler)
1998            .build();
1999
2000        let ctx = test_context();
2001        let mut req = Request::new(Method::Get, "/");
2002
2003        let response = futures_executor::block_on(app.handle(&ctx, &mut req));
2004        assert_eq!(response.status().as_u16(), 200);
2005    }
2006
2007    // =========================================================================
2008    // Exception Handlers Tests
2009    // =========================================================================
2010
2011    // Custom error type for testing
2012    #[derive(Debug)]
2013    struct TestError {
2014        message: String,
2015        code: u32,
2016    }
2017
2018    impl std::fmt::Display for TestError {
2019        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2020            write!(f, "TestError({}): {}", self.code, self.message)
2021        }
2022    }
2023
2024    impl std::error::Error for TestError {}
2025
2026    // Another custom error type
2027    #[derive(Debug)]
2028    struct AnotherError(String);
2029
2030    impl std::fmt::Display for AnotherError {
2031        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2032            write!(f, "AnotherError: {}", self.0)
2033        }
2034    }
2035
2036    impl std::error::Error for AnotherError {}
2037
2038    // --- Unit Tests: Handler Registration ---
2039
2040    #[test]
2041    fn exception_handlers_new_is_empty() {
2042        let handlers = ExceptionHandlers::new();
2043        assert!(handlers.is_empty());
2044        assert_eq!(handlers.len(), 0);
2045    }
2046
2047    #[test]
2048    fn exception_handlers_register_single() {
2049        let mut handlers = ExceptionHandlers::new();
2050        handlers.register::<TestError>(|_ctx, err| {
2051            Response::with_status(StatusCode::BAD_REQUEST)
2052                .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2053        });
2054
2055        assert!(handlers.has_handler::<TestError>());
2056        assert!(!handlers.has_handler::<AnotherError>());
2057        assert_eq!(handlers.len(), 1);
2058    }
2059
2060    #[test]
2061    fn exception_handlers_register_multiple() {
2062        let mut handlers = ExceptionHandlers::new();
2063        handlers.register::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2064        handlers.register::<AnotherError>(|_ctx, _err| {
2065            Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2066        });
2067
2068        assert!(handlers.has_handler::<TestError>());
2069        assert!(handlers.has_handler::<AnotherError>());
2070        assert_eq!(handlers.len(), 2);
2071    }
2072
2073    #[test]
2074    fn exception_handlers_builder_pattern() {
2075        let handlers = ExceptionHandlers::new()
2076            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2077            .handler::<AnotherError>(|_ctx, _err| {
2078                Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2079            });
2080
2081        assert!(handlers.has_handler::<TestError>());
2082        assert!(handlers.has_handler::<AnotherError>());
2083        assert_eq!(handlers.len(), 2);
2084    }
2085
2086    #[test]
2087    fn exception_handlers_with_defaults() {
2088        let handlers = ExceptionHandlers::with_defaults();
2089
2090        assert!(handlers.has_handler::<crate::HttpError>());
2091        assert!(handlers.has_handler::<crate::ValidationErrors>());
2092        assert_eq!(handlers.len(), 2);
2093    }
2094
2095    #[test]
2096    fn exception_handlers_merge() {
2097        let mut handlers1 = ExceptionHandlers::new()
2098            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2099
2100        let handlers2 = ExceptionHandlers::new().handler::<AnotherError>(|_ctx, _err| {
2101            Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2102        });
2103
2104        handlers1.merge(handlers2);
2105
2106        assert!(handlers1.has_handler::<TestError>());
2107        assert!(handlers1.has_handler::<AnotherError>());
2108        assert_eq!(handlers1.len(), 2);
2109    }
2110
2111    // --- Unit Tests: Handler Invocation ---
2112
2113    #[test]
2114    fn exception_handlers_handle_registered_error() {
2115        let handlers = ExceptionHandlers::new().handler::<TestError>(|_ctx, err| {
2116            Response::with_status(StatusCode::BAD_REQUEST)
2117                .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2118        });
2119
2120        let ctx = test_context();
2121        let err = TestError {
2122            message: "test error".into(),
2123            code: 42,
2124        };
2125
2126        let response = handlers.handle(&ctx, err);
2127        assert!(response.is_some());
2128
2129        let response = response.unwrap();
2130        assert_eq!(response.status().as_u16(), 400);
2131    }
2132
2133    #[test]
2134    fn exception_handlers_handle_unregistered_error() {
2135        let handlers = ExceptionHandlers::new()
2136            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2137
2138        let ctx = test_context();
2139        let err = AnotherError("unhandled".into());
2140
2141        let response = handlers.handle(&ctx, err);
2142        assert!(response.is_none());
2143    }
2144
2145    #[test]
2146    fn exception_handlers_handle_or_default_registered() {
2147        let handlers = ExceptionHandlers::new()
2148            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2149
2150        let ctx = test_context();
2151        let err = TestError {
2152            message: "test".into(),
2153            code: 1,
2154        };
2155
2156        let response = handlers.handle_or_default(&ctx, err);
2157        assert_eq!(response.status().as_u16(), 400);
2158    }
2159
2160    #[test]
2161    fn exception_handlers_handle_or_default_unregistered() {
2162        let handlers = ExceptionHandlers::new();
2163
2164        let ctx = test_context();
2165        let err = TestError {
2166            message: "test".into(),
2167            code: 1,
2168        };
2169
2170        let response = handlers.handle_or_default(&ctx, err);
2171        assert_eq!(response.status().as_u16(), 500);
2172    }
2173
2174    #[test]
2175    fn exception_handlers_error_values_passed_to_handler() {
2176        use std::sync::atomic::{AtomicU32, Ordering};
2177
2178        let captured_code = Arc::new(AtomicU32::new(0));
2179        let captured_code_clone = captured_code.clone();
2180
2181        let handlers = ExceptionHandlers::new().handler::<TestError>(move |_ctx, err| {
2182            captured_code_clone.store(err.code, Ordering::SeqCst);
2183            Response::with_status(StatusCode::BAD_REQUEST)
2184        });
2185
2186        let ctx = test_context();
2187        let err = TestError {
2188            message: "test".into(),
2189            code: 12345,
2190        };
2191
2192        let _ = handlers.handle(&ctx, err);
2193        assert_eq!(captured_code.load(Ordering::SeqCst), 12345);
2194    }
2195
2196    // --- Integration Tests: Custom Error Type Handling ---
2197
2198    #[test]
2199    fn app_builder_exception_handler_single() {
2200        let app = App::builder()
2201            .exception_handler::<TestError, _>(|_ctx, err| {
2202                Response::with_status(StatusCode::BAD_REQUEST)
2203                    .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2204            })
2205            .get("/", test_handler)
2206            .build();
2207
2208        assert!(app.exception_handlers().has_handler::<TestError>());
2209    }
2210
2211    #[test]
2212    fn app_builder_exception_handler_multiple() {
2213        let app = App::builder()
2214            .exception_handler::<TestError, _>(|_ctx, _err| {
2215                Response::with_status(StatusCode::BAD_REQUEST)
2216            })
2217            .exception_handler::<AnotherError, _>(|_ctx, _err| {
2218                Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2219            })
2220            .get("/", test_handler)
2221            .build();
2222
2223        assert!(app.exception_handlers().has_handler::<TestError>());
2224        assert!(app.exception_handlers().has_handler::<AnotherError>());
2225    }
2226
2227    #[test]
2228    fn app_builder_with_default_exception_handlers() {
2229        let app = App::builder()
2230            .with_default_exception_handlers()
2231            .get("/", test_handler)
2232            .build();
2233
2234        assert!(app.exception_handlers().has_handler::<crate::HttpError>());
2235        assert!(
2236            app.exception_handlers()
2237                .has_handler::<crate::ValidationErrors>()
2238        );
2239    }
2240
2241    #[test]
2242    fn app_builder_exception_handlers_registry() {
2243        let handlers = ExceptionHandlers::new()
2244            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2245            .handler::<AnotherError>(|_ctx, _err| {
2246                Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2247            });
2248
2249        let app = App::builder()
2250            .exception_handlers(handlers)
2251            .get("/", test_handler)
2252            .build();
2253
2254        assert!(app.exception_handlers().has_handler::<TestError>());
2255        assert!(app.exception_handlers().has_handler::<AnotherError>());
2256    }
2257
2258    #[test]
2259    fn app_handle_error_registered() {
2260        let app = App::builder()
2261            .exception_handler::<TestError, _>(|_ctx, _err| {
2262                Response::with_status(StatusCode::BAD_REQUEST)
2263            })
2264            .get("/", test_handler)
2265            .build();
2266
2267        let ctx = test_context();
2268        let err = TestError {
2269            message: "test".into(),
2270            code: 1,
2271        };
2272
2273        let response = app.handle_error(&ctx, err);
2274        assert!(response.is_some());
2275        assert_eq!(response.unwrap().status().as_u16(), 400);
2276    }
2277
2278    #[test]
2279    fn app_handle_error_unregistered() {
2280        let app = App::builder().get("/", test_handler).build();
2281
2282        let ctx = test_context();
2283        let err = TestError {
2284            message: "test".into(),
2285            code: 1,
2286        };
2287
2288        let response = app.handle_error(&ctx, err);
2289        assert!(response.is_none());
2290    }
2291
2292    #[test]
2293    fn app_handle_error_or_default() {
2294        let app = App::builder().get("/", test_handler).build();
2295
2296        let ctx = test_context();
2297        let err = TestError {
2298            message: "test".into(),
2299            code: 1,
2300        };
2301
2302        let response = app.handle_error_or_default(&ctx, err);
2303        assert_eq!(response.status().as_u16(), 500);
2304    }
2305
2306    // --- Integration Tests: Override Default Handler ---
2307
2308    #[test]
2309    fn exception_handlers_override_on_register() {
2310        let handlers = ExceptionHandlers::new()
2311            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2312            .handler::<TestError>(|_ctx, _err| {
2313                Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
2314            });
2315
2316        // Only one handler for TestError
2317        assert_eq!(handlers.len(), 1);
2318
2319        let ctx = test_context();
2320        let err = TestError {
2321            message: "test".into(),
2322            code: 1,
2323        };
2324
2325        // Should use the second (overriding) handler
2326        let response = handlers.handle(&ctx, err);
2327        assert!(response.is_some());
2328        assert_eq!(response.unwrap().status().as_u16(), 422);
2329    }
2330
2331    #[test]
2332    fn exception_handlers_merge_overrides() {
2333        let mut handlers1 = ExceptionHandlers::new()
2334            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2335
2336        let handlers2 = ExceptionHandlers::new().handler::<TestError>(|_ctx, _err| {
2337            Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
2338        });
2339
2340        handlers1.merge(handlers2);
2341
2342        // Only one handler for TestError after merge
2343        assert_eq!(handlers1.len(), 1);
2344
2345        let ctx = test_context();
2346        let err = TestError {
2347            message: "test".into(),
2348            code: 1,
2349        };
2350
2351        // Merged handlers should override
2352        let response = handlers1.handle(&ctx, err);
2353        assert!(response.is_some());
2354        assert_eq!(response.unwrap().status().as_u16(), 422);
2355    }
2356
2357    #[test]
2358    fn exception_handlers_override_default_http_error() {
2359        // Start with default handlers
2360        let mut handlers = ExceptionHandlers::with_defaults();
2361
2362        // Override HttpError handler
2363        handlers.register::<crate::HttpError>(|_ctx, err| {
2364            // Custom handler that adds extra header
2365            let detail = err.detail.as_deref().unwrap_or("Unknown error");
2366            Response::with_status(err.status)
2367                .header("x-custom-error", b"true".to_vec())
2368                .body(ResponseBody::Bytes(detail.as_bytes().to_vec()))
2369        });
2370
2371        // Still has 2 handlers (HttpError and ValidationErrors)
2372        assert_eq!(handlers.len(), 2);
2373
2374        let ctx = test_context();
2375        let err = crate::HttpError::bad_request().with_detail("test error");
2376
2377        let response = handlers.handle(&ctx, err);
2378        assert!(response.is_some());
2379
2380        let response = response.unwrap();
2381        assert_eq!(response.status().as_u16(), 400);
2382
2383        // Check custom header was added
2384        let custom_header = response
2385            .headers()
2386            .iter()
2387            .find(|(name, _)| name.eq_ignore_ascii_case("x-custom-error"))
2388            .map(|(_, v)| v.as_slice());
2389        assert_eq!(custom_header, Some(b"true".as_slice()));
2390    }
2391
2392    #[test]
2393    fn exception_handlers_override_default_validation_errors() {
2394        // Start with default handlers
2395        let mut handlers = ExceptionHandlers::with_defaults();
2396
2397        // Override ValidationErrors handler
2398        handlers.register::<crate::ValidationErrors>(|_ctx, errs| {
2399            // Custom handler that returns 400 instead of 422
2400            Response::with_status(StatusCode::BAD_REQUEST)
2401                .header("x-error-count", errs.len().to_string().as_bytes().to_vec())
2402        });
2403
2404        let ctx = test_context();
2405        let mut errs = crate::ValidationErrors::new();
2406        errs.push(crate::ValidationError::missing(
2407            crate::error::loc::body_field("name"),
2408        ));
2409        errs.push(crate::ValidationError::missing(
2410            crate::error::loc::body_field("email"),
2411        ));
2412
2413        let response = handlers.handle(&ctx, errs);
2414        assert!(response.is_some());
2415
2416        let response = response.unwrap();
2417        // Custom handler returns 400 instead of 422
2418        assert_eq!(response.status().as_u16(), 400);
2419
2420        // Check custom header
2421        let count_header = response
2422            .headers()
2423            .iter()
2424            .find(|(name, _)| name.eq_ignore_ascii_case("x-error-count"))
2425            .map(|(_, v)| v.as_slice());
2426        assert_eq!(count_header, Some(b"2".as_slice()));
2427    }
2428
2429    #[test]
2430    fn exception_handlers_debug_format() {
2431        let handlers = ExceptionHandlers::new()
2432            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2433
2434        let debug = format!("{:?}", handlers);
2435        assert!(debug.contains("ExceptionHandlers"));
2436        assert!(debug.contains("count"));
2437        assert!(debug.contains("1"));
2438    }
2439
2440    #[test]
2441    fn app_debug_includes_exception_handlers() {
2442        let app = App::builder()
2443            .exception_handler::<TestError, _>(|_ctx, _err| {
2444                Response::with_status(StatusCode::BAD_REQUEST)
2445            })
2446            .get("/", test_handler)
2447            .build();
2448
2449        let debug = format!("{:?}", app);
2450        assert!(debug.contains("exception_handlers"));
2451    }
2452
2453    #[test]
2454    fn app_builder_debug_includes_exception_handlers() {
2455        let builder = App::builder().exception_handler::<TestError, _>(|_ctx, _err| {
2456            Response::with_status(StatusCode::BAD_REQUEST)
2457        });
2458
2459        let debug = format!("{:?}", builder);
2460        assert!(debug.contains("exception_handlers"));
2461    }
2462
2463    // =========================================================================
2464    // Lifecycle Hooks Tests
2465    // =========================================================================
2466
2467    // --- Startup Hooks: Registration ---
2468
2469    #[test]
2470    fn app_builder_startup_hook_registration() {
2471        let builder = App::builder().on_startup(|| Ok(())).on_startup(|| Ok(()));
2472
2473        assert_eq!(builder.startup_hook_count(), 2);
2474    }
2475
2476    #[test]
2477    fn app_builder_shutdown_hook_registration() {
2478        let builder = App::builder().on_shutdown(|| {}).on_shutdown(|| {});
2479
2480        assert_eq!(builder.shutdown_hook_count(), 2);
2481    }
2482
2483    #[test]
2484    fn app_builder_mixed_hooks() {
2485        let builder = App::builder()
2486            .on_startup(|| Ok(()))
2487            .on_shutdown(|| {})
2488            .on_startup(|| Ok(()))
2489            .on_shutdown(|| {});
2490
2491        assert_eq!(builder.startup_hook_count(), 2);
2492        assert_eq!(builder.shutdown_hook_count(), 2);
2493    }
2494
2495    #[test]
2496    fn app_pending_hooks_count() {
2497        let app = App::builder()
2498            .on_startup(|| Ok(()))
2499            .on_startup(|| Ok(()))
2500            .on_shutdown(|| {})
2501            .get("/", test_handler)
2502            .build();
2503
2504        assert_eq!(app.pending_startup_hooks(), 2);
2505        assert_eq!(app.pending_shutdown_hooks(), 1);
2506    }
2507
2508    // --- Startup Hooks: Execution Order (FIFO) ---
2509
2510    #[test]
2511    fn startup_hooks_run_in_fifo_order() {
2512        let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2513
2514        let order1 = Arc::clone(&order);
2515        let order2 = Arc::clone(&order);
2516        let order3 = Arc::clone(&order);
2517
2518        let app = App::builder()
2519            .on_startup(move || {
2520                order1.lock().push(1);
2521                Ok(())
2522            })
2523            .on_startup(move || {
2524                order2.lock().push(2);
2525                Ok(())
2526            })
2527            .on_startup(move || {
2528                order3.lock().push(3);
2529                Ok(())
2530            })
2531            .get("/", test_handler)
2532            .build();
2533
2534        let outcome = futures_executor::block_on(app.run_startup_hooks());
2535        assert!(outcome.can_proceed());
2536
2537        // FIFO: 1, 2, 3
2538        assert_eq!(*order.lock(), vec![1, 2, 3]);
2539
2540        // Hooks consumed
2541        assert_eq!(app.pending_startup_hooks(), 0);
2542    }
2543
2544    // --- Shutdown Hooks: Execution Order (LIFO) ---
2545
2546    #[test]
2547    fn shutdown_hooks_run_in_lifo_order() {
2548        let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2549
2550        let order1 = Arc::clone(&order);
2551        let order2 = Arc::clone(&order);
2552        let order3 = Arc::clone(&order);
2553
2554        let app = App::builder()
2555            .on_shutdown(move || {
2556                order1.lock().push(1);
2557            })
2558            .on_shutdown(move || {
2559                order2.lock().push(2);
2560            })
2561            .on_shutdown(move || {
2562                order3.lock().push(3);
2563            })
2564            .get("/", test_handler)
2565            .build();
2566
2567        futures_executor::block_on(app.run_shutdown_hooks());
2568
2569        // LIFO: 3, 2, 1
2570        assert_eq!(*order.lock(), vec![3, 2, 1]);
2571
2572        // Hooks consumed
2573        assert_eq!(app.pending_shutdown_hooks(), 0);
2574    }
2575
2576    // --- Startup Hooks: Success Outcome ---
2577
2578    #[test]
2579    fn startup_hooks_success_outcome() {
2580        let app = App::builder()
2581            .on_startup(|| Ok(()))
2582            .on_startup(|| Ok(()))
2583            .get("/", test_handler)
2584            .build();
2585
2586        let outcome = futures_executor::block_on(app.run_startup_hooks());
2587        assert!(matches!(outcome, StartupOutcome::Success));
2588        assert!(outcome.can_proceed());
2589    }
2590
2591    // --- Startup Hooks: Fatal Error Aborts ---
2592
2593    #[test]
2594    fn startup_hooks_fatal_error_aborts() {
2595        let app = App::builder()
2596            .on_startup(|| Ok(()))
2597            .on_startup(|| Err(StartupHookError::new("database connection failed")))
2598            .on_startup(|| Ok(())) // Should not run
2599            .get("/", test_handler)
2600            .build();
2601
2602        let outcome = futures_executor::block_on(app.run_startup_hooks());
2603        assert!(!outcome.can_proceed());
2604
2605        if let StartupOutcome::Aborted(err) = outcome {
2606            assert!(err.message.contains("database connection failed"));
2607            assert!(err.abort);
2608        } else {
2609            panic!("Expected Aborted outcome");
2610        }
2611    }
2612
2613    // --- Startup Hooks: Non-Fatal Error Continues ---
2614
2615    #[test]
2616    fn startup_hooks_non_fatal_error_continues() {
2617        let app = App::builder()
2618            .on_startup(|| Ok(()))
2619            .on_startup(|| Err(StartupHookError::non_fatal("optional feature unavailable")))
2620            .on_startup(|| Ok(())) // Should still run
2621            .get("/", test_handler)
2622            .build();
2623
2624        let outcome = futures_executor::block_on(app.run_startup_hooks());
2625        assert!(outcome.can_proceed());
2626
2627        if let StartupOutcome::PartialSuccess { warnings } = outcome {
2628            assert_eq!(warnings, 1);
2629        } else {
2630            panic!("Expected PartialSuccess outcome");
2631        }
2632    }
2633
2634    // --- Startup Hook Error Types ---
2635
2636    #[test]
2637    fn startup_hook_error_builder() {
2638        let err = StartupHookError::new("test error")
2639            .with_hook_name("database_init")
2640            .with_abort(false);
2641
2642        assert_eq!(err.hook_name.as_deref(), Some("database_init"));
2643        assert_eq!(err.message, "test error");
2644        assert!(!err.abort);
2645    }
2646
2647    #[test]
2648    fn startup_hook_error_display() {
2649        let err = StartupHookError::new("connection failed").with_hook_name("redis_init");
2650
2651        let display = format!("{}", err);
2652        assert!(display.contains("redis_init"));
2653        assert!(display.contains("connection failed"));
2654    }
2655
2656    #[test]
2657    fn startup_hook_error_non_fatal() {
2658        let err = StartupHookError::non_fatal("optional feature");
2659        assert!(!err.abort);
2660    }
2661
2662    // --- Transfer Shutdown Hooks to Controller ---
2663
2664    #[test]
2665    fn transfer_shutdown_hooks_to_controller() {
2666        let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2667
2668        let order1 = Arc::clone(&order);
2669        let order2 = Arc::clone(&order);
2670
2671        let app = App::builder()
2672            .on_shutdown(move || {
2673                order1.lock().push(1);
2674            })
2675            .on_shutdown(move || {
2676                order2.lock().push(2);
2677            })
2678            .get("/", test_handler)
2679            .build();
2680
2681        let controller = ShutdownController::new();
2682        app.transfer_shutdown_hooks(&controller);
2683
2684        // App hooks consumed
2685        assert_eq!(app.pending_shutdown_hooks(), 0);
2686
2687        // Controller has the hooks
2688        assert_eq!(controller.hook_count(), 2);
2689
2690        // Run via controller (LIFO order)
2691        while let Some(hook) = controller.pop_hook() {
2692            hook.run();
2693        }
2694
2695        // LIFO order via controller
2696        assert_eq!(*order.lock(), vec![2, 1]);
2697    }
2698
2699    // --- Debug Format Includes Hooks ---
2700
2701    #[test]
2702    fn app_debug_includes_hooks() {
2703        let app = App::builder()
2704            .on_startup(|| Ok(()))
2705            .on_shutdown(|| {})
2706            .get("/", test_handler)
2707            .build();
2708
2709        let debug = format!("{:?}", app);
2710        assert!(debug.contains("startup_hooks"));
2711        assert!(debug.contains("shutdown_hooks"));
2712    }
2713
2714    #[test]
2715    fn app_builder_debug_includes_hooks() {
2716        let builder = App::builder().on_startup(|| Ok(())).on_shutdown(|| {});
2717
2718        let debug = format!("{:?}", builder);
2719        assert!(debug.contains("startup_hooks"));
2720        assert!(debug.contains("shutdown_hooks"));
2721    }
2722
2723    // --- Startup Outcome Accessors ---
2724
2725    #[test]
2726    fn startup_outcome_success() {
2727        let outcome = StartupOutcome::Success;
2728        assert!(outcome.can_proceed());
2729        assert!(outcome.into_error().is_none());
2730    }
2731
2732    #[test]
2733    fn startup_outcome_partial_success() {
2734        let outcome = StartupOutcome::PartialSuccess { warnings: 2 };
2735        assert!(outcome.can_proceed());
2736        assert!(outcome.into_error().is_none());
2737    }
2738
2739    #[test]
2740    fn startup_outcome_aborted() {
2741        let err = StartupHookError::new("fatal");
2742        let outcome = StartupOutcome::Aborted(err);
2743        assert!(!outcome.can_proceed());
2744
2745        let err = outcome.into_error();
2746        assert!(err.is_some());
2747        assert_eq!(err.unwrap().message, "fatal");
2748    }
2749
2750    // --- Multiple Non-Fatal Errors ---
2751
2752    #[test]
2753    fn startup_hooks_multiple_non_fatal_errors() {
2754        let app = App::builder()
2755            .on_startup(|| Err(StartupHookError::non_fatal("warning 1")))
2756            .on_startup(|| Ok(()))
2757            .on_startup(|| Err(StartupHookError::non_fatal("warning 2")))
2758            .on_startup(|| Err(StartupHookError::non_fatal("warning 3")))
2759            .get("/", test_handler)
2760            .build();
2761
2762        let outcome = futures_executor::block_on(app.run_startup_hooks());
2763        assert!(outcome.can_proceed());
2764
2765        if let StartupOutcome::PartialSuccess { warnings } = outcome {
2766            assert_eq!(warnings, 3);
2767        } else {
2768            panic!("Expected PartialSuccess");
2769        }
2770    }
2771
2772    // --- Empty Hooks ---
2773
2774    #[test]
2775    fn empty_startup_hooks() {
2776        let app = App::builder().get("/", test_handler).build();
2777
2778        let outcome = futures_executor::block_on(app.run_startup_hooks());
2779        assert!(matches!(outcome, StartupOutcome::Success));
2780    }
2781
2782    #[test]
2783    fn empty_shutdown_hooks() {
2784        let app = App::builder().get("/", test_handler).build();
2785
2786        // Should not panic with empty hooks
2787        futures_executor::block_on(app.run_shutdown_hooks());
2788    }
2789
2790    // --- Hooks Can Only Run Once ---
2791
2792    #[test]
2793    fn startup_hooks_consumed_after_run() {
2794        let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2795        let counter_clone = Arc::clone(&counter);
2796
2797        let app = App::builder()
2798            .on_startup(move || {
2799                counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2800                Ok(())
2801            })
2802            .get("/", test_handler)
2803            .build();
2804
2805        // First run
2806        futures_executor::block_on(app.run_startup_hooks());
2807        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2808
2809        // Second run - no hooks left
2810        futures_executor::block_on(app.run_startup_hooks());
2811        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2812    }
2813
2814    #[test]
2815    fn shutdown_hooks_consumed_after_run() {
2816        let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2817        let counter_clone = Arc::clone(&counter);
2818
2819        let app = App::builder()
2820            .on_shutdown(move || {
2821                counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2822            })
2823            .get("/", test_handler)
2824            .build();
2825
2826        // First run
2827        futures_executor::block_on(app.run_shutdown_hooks());
2828        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2829
2830        // Second run - no hooks left
2831        futures_executor::block_on(app.run_shutdown_hooks());
2832        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2833    }
2834
2835    #[test]
2836    fn root_path_strips_trailing_slashes() {
2837        let config = AppConfig::new().root_path("/api/");
2838        assert_eq!(config.root_path, "/api");
2839
2840        let config = AppConfig::new().root_path("/api///");
2841        assert_eq!(config.root_path, "/api");
2842
2843        let config = AppConfig::new().root_path("/api");
2844        assert_eq!(config.root_path, "/api");
2845
2846        // Empty root_path stays empty
2847        let config = AppConfig::new().root_path("");
2848        assert_eq!(config.root_path, "");
2849    }
2850}