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    #[must_use]
1457    pub fn test_client(self: Arc<Self>) -> crate::testing::TestClient<Arc<Self>> {
1458        crate::testing::TestClient::new(self)
1459    }
1460
1461    /// Create an in-process test client with a deterministic seed.
1462    #[must_use]
1463    pub fn test_client_with_seed(
1464        self: Arc<Self>,
1465        seed: u64,
1466    ) -> crate::testing::TestClient<Arc<Self>> {
1467        crate::testing::TestClient::with_seed(self, seed)
1468    }
1469
1470    /// Returns the application configuration.
1471    #[must_use]
1472    pub fn config(&self) -> &AppConfig {
1473        &self.config
1474    }
1475
1476    /// Returns the number of registered routes.
1477    #[must_use]
1478    pub fn route_count(&self) -> usize {
1479        self.routes.len()
1480    }
1481
1482    /// Returns the number of registered websocket routes.
1483    #[must_use]
1484    pub fn websocket_route_count(&self) -> usize {
1485        self.ws_routes.len()
1486    }
1487
1488    /// Returns true if a websocket route matches the given path.
1489    #[must_use]
1490    pub fn has_websocket_route(&self, path: &str) -> bool {
1491        matches!(
1492            self.ws_router.lookup(path, Method::Get),
1493            RouteLookup::Match(_)
1494        )
1495    }
1496
1497    /// Returns an iterator over route metadata (method, path).
1498    ///
1499    /// This is useful for generating OpenAPI specifications or debugging.
1500    pub fn routes(&self) -> impl Iterator<Item = (Method, &str)> {
1501        self.routes.iter().map(|r| (r.method, r.path.as_str()))
1502    }
1503
1504    /// Returns the generated OpenAPI specification JSON, if OpenAPI is enabled.
1505    ///
1506    /// # Example
1507    ///
1508    /// ```ignore
1509    /// let app = App::builder()
1510    ///     .openapi(OpenApiConfig::new().title("My API"))
1511    ///     .build();
1512    ///
1513    /// if let Some(spec) = app.openapi_spec() {
1514    ///     println!("OpenAPI spec: {}", spec);
1515    /// }
1516    /// ```
1517    #[must_use]
1518    pub fn openapi_spec(&self) -> Option<&str> {
1519        self.openapi_spec.as_ref().map(|s| s.as_str())
1520    }
1521
1522    /// Returns the shared state container.
1523    #[must_use]
1524    pub fn state(&self) -> &Arc<StateContainer> {
1525        &self.state
1526    }
1527
1528    /// Gets a reference to shared state of type T.
1529    pub fn get_state<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
1530        self.state.get::<T>()
1531    }
1532
1533    /// Returns the exception handlers registry.
1534    #[must_use]
1535    pub fn exception_handlers(&self) -> &Arc<ExceptionHandlers> {
1536        &self.exception_handlers
1537    }
1538
1539    /// Register a fixed override value for a dependency type.
1540    ///
1541    /// This is a test-focused convenience API; production code typically wires
1542    /// dependencies via [`crate::dependency::FromDependency`] implementations.
1543    pub fn override_dependency_value<T>(&self, value: T)
1544    where
1545        T: crate::dependency::FromDependency,
1546    {
1547        self.dependency_overrides.insert_value(value);
1548    }
1549
1550    /// Clear all registered dependency overrides.
1551    pub fn clear_dependency_overrides(&self) {
1552        self.dependency_overrides.clear();
1553    }
1554
1555    /// Returns the shared dependency overrides registry.
1556    #[must_use]
1557    pub fn dependency_overrides(&self) -> Arc<crate::dependency::DependencyOverrides> {
1558        Arc::clone(&self.dependency_overrides)
1559    }
1560
1561    /// Take request-scoped background tasks (if any) from a request.
1562    ///
1563    /// The HTTP server calls this after writing the response so any deferred work
1564    /// runs outside the main request handler path.
1565    pub fn take_background_tasks(req: &mut Request) -> Option<crate::request::BackgroundTasks> {
1566        req.take_extension::<crate::request::BackgroundTasks>()
1567    }
1568
1569    /// Handles an error using registered exception handlers.
1570    ///
1571    /// If a handler is registered for the error type, it will be invoked.
1572    /// Otherwise, returns `None`.
1573    pub fn handle_error<E>(&self, ctx: &RequestContext, err: E) -> Option<Response>
1574    where
1575        E: std::error::Error + Send + Sync + 'static,
1576    {
1577        self.exception_handlers.handle(ctx, err)
1578    }
1579
1580    /// Handles an error, returning a 500 response if no handler is registered.
1581    pub fn handle_error_or_default<E>(&self, ctx: &RequestContext, err: E) -> Response
1582    where
1583        E: std::error::Error + Send + Sync + 'static,
1584    {
1585        self.exception_handlers.handle_or_default(ctx, err)
1586    }
1587
1588    /// Handles an incoming request.
1589    ///
1590    /// This matches the request against registered routes, runs middleware,
1591    /// and returns the response.
1592    pub async fn handle(&self, ctx: &RequestContext, req: &mut Request) -> Response {
1593        // Use the trie-based router for efficient matching with path parameter extraction
1594        match self.router.lookup(req.path(), req.method()) {
1595            RouteLookup::Match(route_match) => {
1596                // Find the handler by matching the route path
1597                let entry = self.routes.iter().find(|e| {
1598                    e.method == route_match.route.method && e.path == route_match.route.path
1599                });
1600
1601                let Some(entry) = entry else {
1602                    // This should never happen if router and routes are in sync
1603                    return Response::with_status(StatusCode::INTERNAL_SERVER_ERROR);
1604                };
1605
1606                // Store extracted path parameters in the request
1607                if !route_match.params.is_empty() {
1608                    let path_params = crate::extract::PathParams::from_pairs(
1609                        route_match
1610                            .params
1611                            .iter()
1612                            .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
1613                            .collect(),
1614                    );
1615                    req.insert_extension(path_params);
1616                }
1617
1618                // Create a handler that wraps the route
1619                let handler = RouteHandler { entry };
1620                self.middleware.execute(&handler, ctx, req).await
1621            }
1622            RouteLookup::MethodNotAllowed { allowed } => {
1623                // Auto-handle `OPTIONS` by returning 204 with an `Allow` header.
1624                // For other methods, return 405 with the `Allow` header.
1625                if req.method() == Method::Options {
1626                    let mut methods = allowed.methods().to_vec();
1627                    if !methods.contains(&Method::Options) {
1628                        methods.push(Method::Options);
1629                    }
1630                    let allow = fastapi_router::AllowedMethods::new(methods);
1631                    Response::with_status(StatusCode::NO_CONTENT)
1632                        .header("allow", allow.header_value().as_bytes().to_vec())
1633                } else {
1634                    Response::with_status(StatusCode::METHOD_NOT_ALLOWED)
1635                        .header("allow", allowed.header_value().as_bytes().to_vec())
1636                }
1637            }
1638            RouteLookup::NotFound => Response::with_status(StatusCode::NOT_FOUND),
1639        }
1640    }
1641
1642    /// Handles an incoming websocket upgrade request after the handshake has been accepted.
1643    ///
1644    /// The HTTP server is responsible for validating the upgrade headers and writing the 101
1645    /// response. This function only performs path matching and calls the websocket handler.
1646    pub async fn handle_websocket(
1647        &self,
1648        ctx: &RequestContext,
1649        req: &mut Request,
1650        ws: crate::websocket::WebSocket,
1651    ) -> Result<(), crate::websocket::WebSocketError> {
1652        match self.ws_router.lookup(req.path(), Method::Get) {
1653            RouteLookup::Match(route_match) => {
1654                let entry = self
1655                    .ws_routes
1656                    .iter()
1657                    .find(|e| e.path == route_match.route.path);
1658                let Some(entry) = entry else {
1659                    return Err(crate::websocket::WebSocketError::Protocol(
1660                        "websocket route missing handler",
1661                    ));
1662                };
1663
1664                if !route_match.params.is_empty() {
1665                    let path_params = crate::extract::PathParams::from_pairs(
1666                        route_match
1667                            .params
1668                            .iter()
1669                            .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
1670                            .collect(),
1671                    );
1672                    req.insert_extension(path_params);
1673                }
1674
1675                entry.call(ctx, req, ws).await
1676            }
1677            _ => Err(crate::websocket::WebSocketError::Protocol(
1678                "no websocket route matched",
1679            )),
1680        }
1681    }
1682
1683    // =========================================================================
1684    // Lifecycle Hook Execution
1685    // =========================================================================
1686
1687    /// Runs all startup hooks.
1688    ///
1689    /// Hooks run in registration order (FIFO). If a hook returns an error
1690    /// with `abort: true`, execution stops and returns `StartupOutcome::Aborted`.
1691    ///
1692    /// This consumes the startup hooks - they can only be run once.
1693    ///
1694    /// # Returns
1695    ///
1696    /// - `StartupOutcome::Success` if all hooks succeeded
1697    /// - `StartupOutcome::PartialSuccess` if some hooks had non-fatal errors
1698    /// - `StartupOutcome::Aborted` if a fatal hook error occurred
1699    pub async fn run_startup_hooks(&self) -> StartupOutcome {
1700        let hooks: Vec<StartupHook> = std::mem::take(&mut *self.startup_hooks.lock());
1701        let mut warnings = 0;
1702
1703        for hook in hooks {
1704            match hook.run() {
1705                Ok(None) => {
1706                    // Sync hook succeeded
1707                }
1708                Ok(Some(fut)) => {
1709                    // Async hook - await it
1710                    match fut.await {
1711                        Ok(()) => {}
1712                        Err(e) if e.abort => {
1713                            return StartupOutcome::Aborted(e);
1714                        }
1715                        Err(_) => {
1716                            warnings += 1;
1717                        }
1718                    }
1719                }
1720                Err(e) if e.abort => {
1721                    return StartupOutcome::Aborted(e);
1722                }
1723                Err(_) => {
1724                    warnings += 1;
1725                }
1726            }
1727        }
1728
1729        if warnings > 0 {
1730            StartupOutcome::PartialSuccess { warnings }
1731        } else {
1732            StartupOutcome::Success
1733        }
1734    }
1735
1736    /// Runs all shutdown hooks.
1737    ///
1738    /// Hooks run in reverse registration order (LIFO). Errors are logged
1739    /// but do not stop other hooks from running.
1740    ///
1741    /// This consumes the shutdown hooks - they can only be run once.
1742    pub async fn run_shutdown_hooks(&self) {
1743        // Run async hooks first (LIFO)
1744        let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
1745        for hook in async_hooks.into_iter().rev() {
1746            let fut = hook();
1747            fut.await;
1748        }
1749
1750        // Run sync hooks (LIFO)
1751        let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
1752        for hook in sync_hooks.into_iter().rev() {
1753            hook();
1754        }
1755    }
1756
1757    /// Transfers shutdown hooks to a [`ShutdownController`].
1758    ///
1759    /// This moves all registered shutdown hooks to the controller, which
1760    /// will run them during the appropriate shutdown phase.
1761    ///
1762    /// Call this when integrating with the server's shutdown mechanism.
1763    pub fn transfer_shutdown_hooks(&self, controller: &ShutdownController) {
1764        // Transfer sync hooks (they'll run in LIFO due to how pop_hook works)
1765        let sync_hooks: Vec<_> = std::mem::take(&mut *self.shutdown_hooks.lock());
1766        for hook in sync_hooks {
1767            controller.register_hook(hook);
1768        }
1769
1770        // Transfer async hooks
1771        let async_hooks: Vec<_> = std::mem::take(&mut *self.async_shutdown_hooks.lock());
1772        for hook in async_hooks {
1773            controller.register_async_hook(move || hook());
1774        }
1775    }
1776
1777    /// Returns the number of pending startup hooks.
1778    #[must_use]
1779    pub fn pending_startup_hooks(&self) -> usize {
1780        self.startup_hooks.lock().len()
1781    }
1782
1783    /// Returns the number of pending shutdown hooks.
1784    #[must_use]
1785    pub fn pending_shutdown_hooks(&self) -> usize {
1786        self.shutdown_hooks.lock().len() + self.async_shutdown_hooks.lock().len()
1787    }
1788}
1789
1790impl std::fmt::Debug for App {
1791    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1792        f.debug_struct("App")
1793            .field("config", &self.config)
1794            .field("routes", &self.routes.len())
1795            .field("middleware", &self.middleware.len())
1796            .field("state", &self.state)
1797            .field("exception_handlers", &self.exception_handlers)
1798            .field("startup_hooks", &self.startup_hooks.lock().len())
1799            .field("shutdown_hooks", &self.pending_shutdown_hooks())
1800            .finish()
1801    }
1802}
1803
1804// Allow `App` to be used anywhere a middleware `Handler` is expected (e.g. TestClient).
1805impl Handler for App {
1806    fn call<'a>(
1807        &'a self,
1808        ctx: &'a RequestContext,
1809        req: &'a mut Request,
1810    ) -> BoxFuture<'a, Response> {
1811        Box::pin(async move { self.handle(ctx, req).await })
1812    }
1813
1814    fn dependency_overrides(&self) -> Option<Arc<crate::dependency::DependencyOverrides>> {
1815        Some(Arc::clone(&self.dependency_overrides))
1816    }
1817}
1818
1819/// Handler wrapper for a route entry.
1820struct RouteHandler<'a> {
1821    entry: &'a RouteEntry,
1822}
1823
1824impl<'a> Handler for RouteHandler<'a> {
1825    fn call<'b>(
1826        &'b self,
1827        ctx: &'b RequestContext,
1828        req: &'b mut Request,
1829    ) -> BoxFuture<'b, Response> {
1830        let handler = self.entry.handler.clone();
1831        Box::pin(async move { handler(ctx, req).await })
1832    }
1833}
1834
1835#[cfg(test)]
1836mod tests {
1837    use super::*;
1838
1839    use crate::response::ResponseBody;
1840
1841    // Test handlers that return 'static futures (no borrowing from parameters)
1842    fn test_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
1843        std::future::ready(Response::ok().body(ResponseBody::Bytes(b"Hello, World!".to_vec())))
1844    }
1845
1846    fn health_handler(_ctx: &RequestContext, _req: &mut Request) -> std::future::Ready<Response> {
1847        std::future::ready(Response::ok().body(ResponseBody::Bytes(b"OK".to_vec())))
1848    }
1849
1850    fn test_context() -> RequestContext {
1851        let cx = asupersync::Cx::for_testing();
1852        RequestContext::new(cx, 1)
1853    }
1854
1855    #[test]
1856    fn app_builder_creates_app() {
1857        let app = App::builder()
1858            .config(AppConfig::new().name("Test App"))
1859            .get("/", test_handler)
1860            .get("/health", health_handler)
1861            .build();
1862
1863        assert_eq!(app.route_count(), 2);
1864        assert_eq!(app.config().name, "Test App");
1865    }
1866
1867    #[test]
1868    fn app_config_builder() {
1869        let config = AppConfig::new()
1870            .name("My API")
1871            .version("1.0.0")
1872            .debug(true)
1873            .max_body_size(2 * 1024 * 1024)
1874            .request_timeout_ms(60_000);
1875
1876        assert_eq!(config.name, "My API");
1877        assert_eq!(config.version, "1.0.0");
1878        assert!(config.debug);
1879        assert_eq!(config.max_body_size, 2 * 1024 * 1024);
1880        assert_eq!(config.request_timeout_ms, 60_000);
1881    }
1882
1883    #[test]
1884    fn state_container_insert_and_get() {
1885        #[derive(Debug, PartialEq)]
1886        struct MyState {
1887            value: i32,
1888        }
1889
1890        let mut container = StateContainer::new();
1891        container.insert(MyState { value: 42 });
1892
1893        let state = container.get::<MyState>();
1894        assert!(state.is_some());
1895        assert_eq!(state.unwrap().value, 42);
1896    }
1897
1898    #[test]
1899    fn state_container_multiple_types() {
1900        struct TypeA(i32);
1901        struct TypeB(String);
1902
1903        let mut container = StateContainer::new();
1904        container.insert(TypeA(1));
1905        container.insert(TypeB("hello".to_string()));
1906
1907        assert!(container.contains::<TypeA>());
1908        assert!(container.contains::<TypeB>());
1909        assert!(!container.contains::<i64>());
1910
1911        assert_eq!(container.get::<TypeA>().unwrap().0, 1);
1912        assert_eq!(container.get::<TypeB>().unwrap().0, "hello");
1913    }
1914
1915    #[test]
1916    fn app_builder_with_state() {
1917        struct DbPool {
1918            connection_count: usize,
1919        }
1920
1921        let app = App::builder()
1922            .state(DbPool {
1923                connection_count: 10,
1924            })
1925            .get("/", test_handler)
1926            .build();
1927
1928        let pool = app.get_state::<DbPool>();
1929        assert!(pool.is_some());
1930        assert_eq!(pool.unwrap().connection_count, 10);
1931    }
1932
1933    #[test]
1934    fn app_handles_get_request() {
1935        let app = App::builder().get("/", test_handler).build();
1936
1937        let ctx = test_context();
1938        let mut req = Request::new(Method::Get, "/");
1939
1940        let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1941        assert_eq!(response.status().as_u16(), 200);
1942    }
1943
1944    #[test]
1945    fn app_returns_404_for_unknown_path() {
1946        let app = App::builder().get("/", test_handler).build();
1947
1948        let ctx = test_context();
1949        let mut req = Request::new(Method::Get, "/unknown");
1950
1951        let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1952        assert_eq!(response.status().as_u16(), 404);
1953    }
1954
1955    #[test]
1956    fn app_returns_405_for_wrong_method() {
1957        let app = App::builder().get("/", test_handler).build();
1958
1959        let ctx = test_context();
1960        let mut req = Request::new(Method::Post, "/");
1961
1962        let response = futures_executor::block_on(app.handle(&ctx, &mut req));
1963        assert_eq!(response.status().as_u16(), 405);
1964    }
1965
1966    #[test]
1967    fn app_builder_all_methods() {
1968        let app = App::builder()
1969            .get("/get", test_handler)
1970            .post("/post", test_handler)
1971            .put("/put", test_handler)
1972            .delete("/delete", test_handler)
1973            .patch("/patch", test_handler)
1974            .build();
1975
1976        assert_eq!(app.route_count(), 5);
1977    }
1978
1979    #[test]
1980    fn route_entry_debug() {
1981        let entry = RouteEntry::new(Method::Get, "/test", test_handler);
1982        let debug = format!("{:?}", entry);
1983        assert!(debug.contains("RouteEntry"));
1984        assert!(debug.contains("Get"));
1985        assert!(debug.contains("/test"));
1986    }
1987
1988    #[test]
1989    fn app_with_middleware() {
1990        use crate::middleware::NoopMiddleware;
1991
1992        let app = App::builder()
1993            .middleware(NoopMiddleware)
1994            .middleware(NoopMiddleware)
1995            .get("/", test_handler)
1996            .build();
1997
1998        let ctx = test_context();
1999        let mut req = Request::new(Method::Get, "/");
2000
2001        let response = futures_executor::block_on(app.handle(&ctx, &mut req));
2002        assert_eq!(response.status().as_u16(), 200);
2003    }
2004
2005    // =========================================================================
2006    // Exception Handlers Tests
2007    // =========================================================================
2008
2009    // Custom error type for testing
2010    #[derive(Debug)]
2011    struct TestError {
2012        message: String,
2013        code: u32,
2014    }
2015
2016    impl std::fmt::Display for TestError {
2017        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2018            write!(f, "TestError({}): {}", self.code, self.message)
2019        }
2020    }
2021
2022    impl std::error::Error for TestError {}
2023
2024    // Another custom error type
2025    #[derive(Debug)]
2026    struct AnotherError(String);
2027
2028    impl std::fmt::Display for AnotherError {
2029        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2030            write!(f, "AnotherError: {}", self.0)
2031        }
2032    }
2033
2034    impl std::error::Error for AnotherError {}
2035
2036    // --- Unit Tests: Handler Registration ---
2037
2038    #[test]
2039    fn exception_handlers_new_is_empty() {
2040        let handlers = ExceptionHandlers::new();
2041        assert!(handlers.is_empty());
2042        assert_eq!(handlers.len(), 0);
2043    }
2044
2045    #[test]
2046    fn exception_handlers_register_single() {
2047        let mut handlers = ExceptionHandlers::new();
2048        handlers.register::<TestError>(|_ctx, err| {
2049            Response::with_status(StatusCode::BAD_REQUEST)
2050                .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2051        });
2052
2053        assert!(handlers.has_handler::<TestError>());
2054        assert!(!handlers.has_handler::<AnotherError>());
2055        assert_eq!(handlers.len(), 1);
2056    }
2057
2058    #[test]
2059    fn exception_handlers_register_multiple() {
2060        let mut handlers = ExceptionHandlers::new();
2061        handlers.register::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2062        handlers.register::<AnotherError>(|_ctx, _err| {
2063            Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2064        });
2065
2066        assert!(handlers.has_handler::<TestError>());
2067        assert!(handlers.has_handler::<AnotherError>());
2068        assert_eq!(handlers.len(), 2);
2069    }
2070
2071    #[test]
2072    fn exception_handlers_builder_pattern() {
2073        let handlers = ExceptionHandlers::new()
2074            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2075            .handler::<AnotherError>(|_ctx, _err| {
2076                Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2077            });
2078
2079        assert!(handlers.has_handler::<TestError>());
2080        assert!(handlers.has_handler::<AnotherError>());
2081        assert_eq!(handlers.len(), 2);
2082    }
2083
2084    #[test]
2085    fn exception_handlers_with_defaults() {
2086        let handlers = ExceptionHandlers::with_defaults();
2087
2088        assert!(handlers.has_handler::<crate::HttpError>());
2089        assert!(handlers.has_handler::<crate::ValidationErrors>());
2090        assert_eq!(handlers.len(), 2);
2091    }
2092
2093    #[test]
2094    fn exception_handlers_merge() {
2095        let mut handlers1 = ExceptionHandlers::new()
2096            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2097
2098        let handlers2 = ExceptionHandlers::new().handler::<AnotherError>(|_ctx, _err| {
2099            Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2100        });
2101
2102        handlers1.merge(handlers2);
2103
2104        assert!(handlers1.has_handler::<TestError>());
2105        assert!(handlers1.has_handler::<AnotherError>());
2106        assert_eq!(handlers1.len(), 2);
2107    }
2108
2109    // --- Unit Tests: Handler Invocation ---
2110
2111    #[test]
2112    fn exception_handlers_handle_registered_error() {
2113        let handlers = ExceptionHandlers::new().handler::<TestError>(|_ctx, err| {
2114            Response::with_status(StatusCode::BAD_REQUEST)
2115                .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2116        });
2117
2118        let ctx = test_context();
2119        let err = TestError {
2120            message: "test error".into(),
2121            code: 42,
2122        };
2123
2124        let response = handlers.handle(&ctx, err);
2125        assert!(response.is_some());
2126
2127        let response = response.unwrap();
2128        assert_eq!(response.status().as_u16(), 400);
2129    }
2130
2131    #[test]
2132    fn exception_handlers_handle_unregistered_error() {
2133        let handlers = ExceptionHandlers::new()
2134            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2135
2136        let ctx = test_context();
2137        let err = AnotherError("unhandled".into());
2138
2139        let response = handlers.handle(&ctx, err);
2140        assert!(response.is_none());
2141    }
2142
2143    #[test]
2144    fn exception_handlers_handle_or_default_registered() {
2145        let handlers = ExceptionHandlers::new()
2146            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2147
2148        let ctx = test_context();
2149        let err = TestError {
2150            message: "test".into(),
2151            code: 1,
2152        };
2153
2154        let response = handlers.handle_or_default(&ctx, err);
2155        assert_eq!(response.status().as_u16(), 400);
2156    }
2157
2158    #[test]
2159    fn exception_handlers_handle_or_default_unregistered() {
2160        let handlers = ExceptionHandlers::new();
2161
2162        let ctx = test_context();
2163        let err = TestError {
2164            message: "test".into(),
2165            code: 1,
2166        };
2167
2168        let response = handlers.handle_or_default(&ctx, err);
2169        assert_eq!(response.status().as_u16(), 500);
2170    }
2171
2172    #[test]
2173    fn exception_handlers_error_values_passed_to_handler() {
2174        use std::sync::atomic::{AtomicU32, Ordering};
2175
2176        let captured_code = Arc::new(AtomicU32::new(0));
2177        let captured_code_clone = captured_code.clone();
2178
2179        let handlers = ExceptionHandlers::new().handler::<TestError>(move |_ctx, err| {
2180            captured_code_clone.store(err.code, Ordering::SeqCst);
2181            Response::with_status(StatusCode::BAD_REQUEST)
2182        });
2183
2184        let ctx = test_context();
2185        let err = TestError {
2186            message: "test".into(),
2187            code: 12345,
2188        };
2189
2190        let _ = handlers.handle(&ctx, err);
2191        assert_eq!(captured_code.load(Ordering::SeqCst), 12345);
2192    }
2193
2194    // --- Integration Tests: Custom Error Type Handling ---
2195
2196    #[test]
2197    fn app_builder_exception_handler_single() {
2198        let app = App::builder()
2199            .exception_handler::<TestError, _>(|_ctx, err| {
2200                Response::with_status(StatusCode::BAD_REQUEST)
2201                    .body(ResponseBody::Bytes(err.message.as_bytes().to_vec()))
2202            })
2203            .get("/", test_handler)
2204            .build();
2205
2206        assert!(app.exception_handlers().has_handler::<TestError>());
2207    }
2208
2209    #[test]
2210    fn app_builder_exception_handler_multiple() {
2211        let app = App::builder()
2212            .exception_handler::<TestError, _>(|_ctx, _err| {
2213                Response::with_status(StatusCode::BAD_REQUEST)
2214            })
2215            .exception_handler::<AnotherError, _>(|_ctx, _err| {
2216                Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2217            })
2218            .get("/", test_handler)
2219            .build();
2220
2221        assert!(app.exception_handlers().has_handler::<TestError>());
2222        assert!(app.exception_handlers().has_handler::<AnotherError>());
2223    }
2224
2225    #[test]
2226    fn app_builder_with_default_exception_handlers() {
2227        let app = App::builder()
2228            .with_default_exception_handlers()
2229            .get("/", test_handler)
2230            .build();
2231
2232        assert!(app.exception_handlers().has_handler::<crate::HttpError>());
2233        assert!(
2234            app.exception_handlers()
2235                .has_handler::<crate::ValidationErrors>()
2236        );
2237    }
2238
2239    #[test]
2240    fn app_builder_exception_handlers_registry() {
2241        let handlers = ExceptionHandlers::new()
2242            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2243            .handler::<AnotherError>(|_ctx, _err| {
2244                Response::with_status(StatusCode::INTERNAL_SERVER_ERROR)
2245            });
2246
2247        let app = App::builder()
2248            .exception_handlers(handlers)
2249            .get("/", test_handler)
2250            .build();
2251
2252        assert!(app.exception_handlers().has_handler::<TestError>());
2253        assert!(app.exception_handlers().has_handler::<AnotherError>());
2254    }
2255
2256    #[test]
2257    fn app_handle_error_registered() {
2258        let app = App::builder()
2259            .exception_handler::<TestError, _>(|_ctx, _err| {
2260                Response::with_status(StatusCode::BAD_REQUEST)
2261            })
2262            .get("/", test_handler)
2263            .build();
2264
2265        let ctx = test_context();
2266        let err = TestError {
2267            message: "test".into(),
2268            code: 1,
2269        };
2270
2271        let response = app.handle_error(&ctx, err);
2272        assert!(response.is_some());
2273        assert_eq!(response.unwrap().status().as_u16(), 400);
2274    }
2275
2276    #[test]
2277    fn app_handle_error_unregistered() {
2278        let app = App::builder().get("/", test_handler).build();
2279
2280        let ctx = test_context();
2281        let err = TestError {
2282            message: "test".into(),
2283            code: 1,
2284        };
2285
2286        let response = app.handle_error(&ctx, err);
2287        assert!(response.is_none());
2288    }
2289
2290    #[test]
2291    fn app_handle_error_or_default() {
2292        let app = App::builder().get("/", test_handler).build();
2293
2294        let ctx = test_context();
2295        let err = TestError {
2296            message: "test".into(),
2297            code: 1,
2298        };
2299
2300        let response = app.handle_error_or_default(&ctx, err);
2301        assert_eq!(response.status().as_u16(), 500);
2302    }
2303
2304    // --- Integration Tests: Override Default Handler ---
2305
2306    #[test]
2307    fn exception_handlers_override_on_register() {
2308        let handlers = ExceptionHandlers::new()
2309            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST))
2310            .handler::<TestError>(|_ctx, _err| {
2311                Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
2312            });
2313
2314        // Only one handler for TestError
2315        assert_eq!(handlers.len(), 1);
2316
2317        let ctx = test_context();
2318        let err = TestError {
2319            message: "test".into(),
2320            code: 1,
2321        };
2322
2323        // Should use the second (overriding) handler
2324        let response = handlers.handle(&ctx, err);
2325        assert!(response.is_some());
2326        assert_eq!(response.unwrap().status().as_u16(), 422);
2327    }
2328
2329    #[test]
2330    fn exception_handlers_merge_overrides() {
2331        let mut handlers1 = ExceptionHandlers::new()
2332            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2333
2334        let handlers2 = ExceptionHandlers::new().handler::<TestError>(|_ctx, _err| {
2335            Response::with_status(StatusCode::UNPROCESSABLE_ENTITY)
2336        });
2337
2338        handlers1.merge(handlers2);
2339
2340        // Only one handler for TestError after merge
2341        assert_eq!(handlers1.len(), 1);
2342
2343        let ctx = test_context();
2344        let err = TestError {
2345            message: "test".into(),
2346            code: 1,
2347        };
2348
2349        // Merged handlers should override
2350        let response = handlers1.handle(&ctx, err);
2351        assert!(response.is_some());
2352        assert_eq!(response.unwrap().status().as_u16(), 422);
2353    }
2354
2355    #[test]
2356    fn exception_handlers_override_default_http_error() {
2357        // Start with default handlers
2358        let mut handlers = ExceptionHandlers::with_defaults();
2359
2360        // Override HttpError handler
2361        handlers.register::<crate::HttpError>(|_ctx, err| {
2362            // Custom handler that adds extra header
2363            let detail = err.detail.as_deref().unwrap_or("Unknown error");
2364            Response::with_status(err.status)
2365                .header("x-custom-error", b"true".to_vec())
2366                .body(ResponseBody::Bytes(detail.as_bytes().to_vec()))
2367        });
2368
2369        // Still has 2 handlers (HttpError and ValidationErrors)
2370        assert_eq!(handlers.len(), 2);
2371
2372        let ctx = test_context();
2373        let err = crate::HttpError::bad_request().with_detail("test error");
2374
2375        let response = handlers.handle(&ctx, err);
2376        assert!(response.is_some());
2377
2378        let response = response.unwrap();
2379        assert_eq!(response.status().as_u16(), 400);
2380
2381        // Check custom header was added
2382        let custom_header = response
2383            .headers()
2384            .iter()
2385            .find(|(name, _)| name.eq_ignore_ascii_case("x-custom-error"))
2386            .map(|(_, v)| v.as_slice());
2387        assert_eq!(custom_header, Some(b"true".as_slice()));
2388    }
2389
2390    #[test]
2391    fn exception_handlers_override_default_validation_errors() {
2392        // Start with default handlers
2393        let mut handlers = ExceptionHandlers::with_defaults();
2394
2395        // Override ValidationErrors handler
2396        handlers.register::<crate::ValidationErrors>(|_ctx, errs| {
2397            // Custom handler that returns 400 instead of 422
2398            Response::with_status(StatusCode::BAD_REQUEST)
2399                .header("x-error-count", errs.len().to_string().as_bytes().to_vec())
2400        });
2401
2402        let ctx = test_context();
2403        let mut errs = crate::ValidationErrors::new();
2404        errs.push(crate::ValidationError::missing(
2405            crate::error::loc::body_field("name"),
2406        ));
2407        errs.push(crate::ValidationError::missing(
2408            crate::error::loc::body_field("email"),
2409        ));
2410
2411        let response = handlers.handle(&ctx, errs);
2412        assert!(response.is_some());
2413
2414        let response = response.unwrap();
2415        // Custom handler returns 400 instead of 422
2416        assert_eq!(response.status().as_u16(), 400);
2417
2418        // Check custom header
2419        let count_header = response
2420            .headers()
2421            .iter()
2422            .find(|(name, _)| name.eq_ignore_ascii_case("x-error-count"))
2423            .map(|(_, v)| v.as_slice());
2424        assert_eq!(count_header, Some(b"2".as_slice()));
2425    }
2426
2427    #[test]
2428    fn exception_handlers_debug_format() {
2429        let handlers = ExceptionHandlers::new()
2430            .handler::<TestError>(|_ctx, _err| Response::with_status(StatusCode::BAD_REQUEST));
2431
2432        let debug = format!("{:?}", handlers);
2433        assert!(debug.contains("ExceptionHandlers"));
2434        assert!(debug.contains("count"));
2435        assert!(debug.contains("1"));
2436    }
2437
2438    #[test]
2439    fn app_debug_includes_exception_handlers() {
2440        let app = App::builder()
2441            .exception_handler::<TestError, _>(|_ctx, _err| {
2442                Response::with_status(StatusCode::BAD_REQUEST)
2443            })
2444            .get("/", test_handler)
2445            .build();
2446
2447        let debug = format!("{:?}", app);
2448        assert!(debug.contains("exception_handlers"));
2449    }
2450
2451    #[test]
2452    fn app_builder_debug_includes_exception_handlers() {
2453        let builder = App::builder().exception_handler::<TestError, _>(|_ctx, _err| {
2454            Response::with_status(StatusCode::BAD_REQUEST)
2455        });
2456
2457        let debug = format!("{:?}", builder);
2458        assert!(debug.contains("exception_handlers"));
2459    }
2460
2461    // =========================================================================
2462    // Lifecycle Hooks Tests
2463    // =========================================================================
2464
2465    // --- Startup Hooks: Registration ---
2466
2467    #[test]
2468    fn app_builder_startup_hook_registration() {
2469        let builder = App::builder().on_startup(|| Ok(())).on_startup(|| Ok(()));
2470
2471        assert_eq!(builder.startup_hook_count(), 2);
2472    }
2473
2474    #[test]
2475    fn app_builder_shutdown_hook_registration() {
2476        let builder = App::builder().on_shutdown(|| {}).on_shutdown(|| {});
2477
2478        assert_eq!(builder.shutdown_hook_count(), 2);
2479    }
2480
2481    #[test]
2482    fn app_builder_mixed_hooks() {
2483        let builder = App::builder()
2484            .on_startup(|| Ok(()))
2485            .on_shutdown(|| {})
2486            .on_startup(|| Ok(()))
2487            .on_shutdown(|| {});
2488
2489        assert_eq!(builder.startup_hook_count(), 2);
2490        assert_eq!(builder.shutdown_hook_count(), 2);
2491    }
2492
2493    #[test]
2494    fn app_pending_hooks_count() {
2495        let app = App::builder()
2496            .on_startup(|| Ok(()))
2497            .on_startup(|| Ok(()))
2498            .on_shutdown(|| {})
2499            .get("/", test_handler)
2500            .build();
2501
2502        assert_eq!(app.pending_startup_hooks(), 2);
2503        assert_eq!(app.pending_shutdown_hooks(), 1);
2504    }
2505
2506    // --- Startup Hooks: Execution Order (FIFO) ---
2507
2508    #[test]
2509    fn startup_hooks_run_in_fifo_order() {
2510        let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2511
2512        let order1 = Arc::clone(&order);
2513        let order2 = Arc::clone(&order);
2514        let order3 = Arc::clone(&order);
2515
2516        let app = App::builder()
2517            .on_startup(move || {
2518                order1.lock().push(1);
2519                Ok(())
2520            })
2521            .on_startup(move || {
2522                order2.lock().push(2);
2523                Ok(())
2524            })
2525            .on_startup(move || {
2526                order3.lock().push(3);
2527                Ok(())
2528            })
2529            .get("/", test_handler)
2530            .build();
2531
2532        let outcome = futures_executor::block_on(app.run_startup_hooks());
2533        assert!(outcome.can_proceed());
2534
2535        // FIFO: 1, 2, 3
2536        assert_eq!(*order.lock(), vec![1, 2, 3]);
2537
2538        // Hooks consumed
2539        assert_eq!(app.pending_startup_hooks(), 0);
2540    }
2541
2542    // --- Shutdown Hooks: Execution Order (LIFO) ---
2543
2544    #[test]
2545    fn shutdown_hooks_run_in_lifo_order() {
2546        let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2547
2548        let order1 = Arc::clone(&order);
2549        let order2 = Arc::clone(&order);
2550        let order3 = Arc::clone(&order);
2551
2552        let app = App::builder()
2553            .on_shutdown(move || {
2554                order1.lock().push(1);
2555            })
2556            .on_shutdown(move || {
2557                order2.lock().push(2);
2558            })
2559            .on_shutdown(move || {
2560                order3.lock().push(3);
2561            })
2562            .get("/", test_handler)
2563            .build();
2564
2565        futures_executor::block_on(app.run_shutdown_hooks());
2566
2567        // LIFO: 3, 2, 1
2568        assert_eq!(*order.lock(), vec![3, 2, 1]);
2569
2570        // Hooks consumed
2571        assert_eq!(app.pending_shutdown_hooks(), 0);
2572    }
2573
2574    // --- Startup Hooks: Success Outcome ---
2575
2576    #[test]
2577    fn startup_hooks_success_outcome() {
2578        let app = App::builder()
2579            .on_startup(|| Ok(()))
2580            .on_startup(|| Ok(()))
2581            .get("/", test_handler)
2582            .build();
2583
2584        let outcome = futures_executor::block_on(app.run_startup_hooks());
2585        assert!(matches!(outcome, StartupOutcome::Success));
2586        assert!(outcome.can_proceed());
2587    }
2588
2589    // --- Startup Hooks: Fatal Error Aborts ---
2590
2591    #[test]
2592    fn startup_hooks_fatal_error_aborts() {
2593        let app = App::builder()
2594            .on_startup(|| Ok(()))
2595            .on_startup(|| Err(StartupHookError::new("database connection failed")))
2596            .on_startup(|| Ok(())) // Should not run
2597            .get("/", test_handler)
2598            .build();
2599
2600        let outcome = futures_executor::block_on(app.run_startup_hooks());
2601        assert!(!outcome.can_proceed());
2602
2603        if let StartupOutcome::Aborted(err) = outcome {
2604            assert!(err.message.contains("database connection failed"));
2605            assert!(err.abort);
2606        } else {
2607            panic!("Expected Aborted outcome");
2608        }
2609    }
2610
2611    // --- Startup Hooks: Non-Fatal Error Continues ---
2612
2613    #[test]
2614    fn startup_hooks_non_fatal_error_continues() {
2615        let app = App::builder()
2616            .on_startup(|| Ok(()))
2617            .on_startup(|| Err(StartupHookError::non_fatal("optional feature unavailable")))
2618            .on_startup(|| Ok(())) // Should still run
2619            .get("/", test_handler)
2620            .build();
2621
2622        let outcome = futures_executor::block_on(app.run_startup_hooks());
2623        assert!(outcome.can_proceed());
2624
2625        if let StartupOutcome::PartialSuccess { warnings } = outcome {
2626            assert_eq!(warnings, 1);
2627        } else {
2628            panic!("Expected PartialSuccess outcome");
2629        }
2630    }
2631
2632    // --- Startup Hook Error Types ---
2633
2634    #[test]
2635    fn startup_hook_error_builder() {
2636        let err = StartupHookError::new("test error")
2637            .with_hook_name("database_init")
2638            .with_abort(false);
2639
2640        assert_eq!(err.hook_name.as_deref(), Some("database_init"));
2641        assert_eq!(err.message, "test error");
2642        assert!(!err.abort);
2643    }
2644
2645    #[test]
2646    fn startup_hook_error_display() {
2647        let err = StartupHookError::new("connection failed").with_hook_name("redis_init");
2648
2649        let display = format!("{}", err);
2650        assert!(display.contains("redis_init"));
2651        assert!(display.contains("connection failed"));
2652    }
2653
2654    #[test]
2655    fn startup_hook_error_non_fatal() {
2656        let err = StartupHookError::non_fatal("optional feature");
2657        assert!(!err.abort);
2658    }
2659
2660    // --- Transfer Shutdown Hooks to Controller ---
2661
2662    #[test]
2663    fn transfer_shutdown_hooks_to_controller() {
2664        let order = Arc::new(parking_lot::Mutex::new(Vec::new()));
2665
2666        let order1 = Arc::clone(&order);
2667        let order2 = Arc::clone(&order);
2668
2669        let app = App::builder()
2670            .on_shutdown(move || {
2671                order1.lock().push(1);
2672            })
2673            .on_shutdown(move || {
2674                order2.lock().push(2);
2675            })
2676            .get("/", test_handler)
2677            .build();
2678
2679        let controller = ShutdownController::new();
2680        app.transfer_shutdown_hooks(&controller);
2681
2682        // App hooks consumed
2683        assert_eq!(app.pending_shutdown_hooks(), 0);
2684
2685        // Controller has the hooks
2686        assert_eq!(controller.hook_count(), 2);
2687
2688        // Run via controller (LIFO order)
2689        while let Some(hook) = controller.pop_hook() {
2690            hook.run();
2691        }
2692
2693        // LIFO order via controller
2694        assert_eq!(*order.lock(), vec![2, 1]);
2695    }
2696
2697    // --- Debug Format Includes Hooks ---
2698
2699    #[test]
2700    fn app_debug_includes_hooks() {
2701        let app = App::builder()
2702            .on_startup(|| Ok(()))
2703            .on_shutdown(|| {})
2704            .get("/", test_handler)
2705            .build();
2706
2707        let debug = format!("{:?}", app);
2708        assert!(debug.contains("startup_hooks"));
2709        assert!(debug.contains("shutdown_hooks"));
2710    }
2711
2712    #[test]
2713    fn app_builder_debug_includes_hooks() {
2714        let builder = App::builder().on_startup(|| Ok(())).on_shutdown(|| {});
2715
2716        let debug = format!("{:?}", builder);
2717        assert!(debug.contains("startup_hooks"));
2718        assert!(debug.contains("shutdown_hooks"));
2719    }
2720
2721    // --- Startup Outcome Accessors ---
2722
2723    #[test]
2724    fn startup_outcome_success() {
2725        let outcome = StartupOutcome::Success;
2726        assert!(outcome.can_proceed());
2727        assert!(outcome.into_error().is_none());
2728    }
2729
2730    #[test]
2731    fn startup_outcome_partial_success() {
2732        let outcome = StartupOutcome::PartialSuccess { warnings: 2 };
2733        assert!(outcome.can_proceed());
2734        assert!(outcome.into_error().is_none());
2735    }
2736
2737    #[test]
2738    fn startup_outcome_aborted() {
2739        let err = StartupHookError::new("fatal");
2740        let outcome = StartupOutcome::Aborted(err);
2741        assert!(!outcome.can_proceed());
2742
2743        let err = outcome.into_error();
2744        assert!(err.is_some());
2745        assert_eq!(err.unwrap().message, "fatal");
2746    }
2747
2748    // --- Multiple Non-Fatal Errors ---
2749
2750    #[test]
2751    fn startup_hooks_multiple_non_fatal_errors() {
2752        let app = App::builder()
2753            .on_startup(|| Err(StartupHookError::non_fatal("warning 1")))
2754            .on_startup(|| Ok(()))
2755            .on_startup(|| Err(StartupHookError::non_fatal("warning 2")))
2756            .on_startup(|| Err(StartupHookError::non_fatal("warning 3")))
2757            .get("/", test_handler)
2758            .build();
2759
2760        let outcome = futures_executor::block_on(app.run_startup_hooks());
2761        assert!(outcome.can_proceed());
2762
2763        if let StartupOutcome::PartialSuccess { warnings } = outcome {
2764            assert_eq!(warnings, 3);
2765        } else {
2766            panic!("Expected PartialSuccess");
2767        }
2768    }
2769
2770    // --- Empty Hooks ---
2771
2772    #[test]
2773    fn empty_startup_hooks() {
2774        let app = App::builder().get("/", test_handler).build();
2775
2776        let outcome = futures_executor::block_on(app.run_startup_hooks());
2777        assert!(matches!(outcome, StartupOutcome::Success));
2778    }
2779
2780    #[test]
2781    fn empty_shutdown_hooks() {
2782        let app = App::builder().get("/", test_handler).build();
2783
2784        // Should not panic with empty hooks
2785        futures_executor::block_on(app.run_shutdown_hooks());
2786    }
2787
2788    // --- Hooks Can Only Run Once ---
2789
2790    #[test]
2791    fn startup_hooks_consumed_after_run() {
2792        let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2793        let counter_clone = Arc::clone(&counter);
2794
2795        let app = App::builder()
2796            .on_startup(move || {
2797                counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2798                Ok(())
2799            })
2800            .get("/", test_handler)
2801            .build();
2802
2803        // First run
2804        futures_executor::block_on(app.run_startup_hooks());
2805        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2806
2807        // Second run - no hooks left
2808        futures_executor::block_on(app.run_startup_hooks());
2809        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2810    }
2811
2812    #[test]
2813    fn shutdown_hooks_consumed_after_run() {
2814        let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
2815        let counter_clone = Arc::clone(&counter);
2816
2817        let app = App::builder()
2818            .on_shutdown(move || {
2819                counter_clone.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
2820            })
2821            .get("/", test_handler)
2822            .build();
2823
2824        // First run
2825        futures_executor::block_on(app.run_shutdown_hooks());
2826        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2827
2828        // Second run - no hooks left
2829        futures_executor::block_on(app.run_shutdown_hooks());
2830        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 1);
2831    }
2832
2833    #[test]
2834    fn root_path_strips_trailing_slashes() {
2835        let config = AppConfig::new().root_path("/api/");
2836        assert_eq!(config.root_path, "/api");
2837
2838        let config = AppConfig::new().root_path("/api///");
2839        assert_eq!(config.root_path, "/api");
2840
2841        let config = AppConfig::new().root_path("/api");
2842        assert_eq!(config.root_path, "/api");
2843
2844        // Empty root_path stays empty
2845        let config = AppConfig::new().root_path("");
2846        assert_eq!(config.root_path, "");
2847    }
2848}