Skip to main content

rustapi_core/app/
builder.rs

1use super::config::RustApiConfig;
2use super::dispatcher::RequestDispatcher;
3use super::types::RustApi;
4use crate::events::LifecycleHooks;
5use crate::interceptor::{InterceptorChain, RequestInterceptor, ResponseInterceptor};
6use crate::middleware::{LayerStack, MiddlewareLayer, DEFAULT_BODY_LIMIT};
7use crate::router::Router;
8use std::future::Future;
9use std::sync::Arc;
10
11impl RustApi {
12    /// Create a new RustAPI application.
13    ///
14    /// Initialize `tracing-subscriber` in `main` when using the `tracing` feature;
15    /// `cargo-rustapi` templates already do this.
16    pub fn new() -> Self {
17        Self {
18            router: Router::new(),
19            openapi_spec: rustapi_openapi::OpenApiSpec::new("RustAPI Application", "1.0.0")
20                .register::<rustapi_openapi::ErrorSchema>()
21                .register::<rustapi_openapi::ErrorBodySchema>()
22                .register::<rustapi_openapi::ValidationErrorSchema>()
23                .register::<rustapi_openapi::ValidationErrorBodySchema>()
24                .register::<rustapi_openapi::FieldErrorSchema>(),
25            layers: LayerStack::new(),
26            body_limit: Some(DEFAULT_BODY_LIMIT), // Default 1MB limit
27            interceptors: InterceptorChain::new(),
28            lifecycle_hooks: LifecycleHooks::new(),
29            hot_reload: false,
30            #[cfg(feature = "http3")]
31            http3_config: None,
32            health_check: None,
33            health_endpoint_config: None,
34            status_config: None,
35            #[cfg(feature = "dashboard")]
36            dashboard_config: None,
37        }
38    }
39
40    /// The primary way to build a RustAPI application.
41    ///
42    /// Collects all routes decorated with `#[rustapi_rs::get]`, `#[rustapi_rs::post]`, etc.
43    /// at link time via `linkme` and registers them automatically — no manual `.route()`
44    /// or `.mount_route()` calls needed. This is baked into the core and requires no
45    /// feature flags.
46    ///
47    /// When the `swagger-ui` feature is enabled (included in the default `core` feature),
48    /// Swagger UI is served at `/docs`. Without it, only the auto-discovered routes are
49    /// registered.
50    ///
51    /// Use [`RustApi::new()`] when handlers are plain `async fn` not annotated with
52    /// the route macros, or when you need full manual control over route registration.
53    ///
54    /// # Example
55    ///
56    /// ```rust,ignore
57    /// use rustapi_rs::prelude::*;
58    ///
59    /// #[rustapi_rs::get("/users")]
60    /// async fn list_users() -> Json<Vec<User>> {
61    ///     Json(vec![])
62    /// }
63    ///
64    /// #[rustapi_rs::main]
65    /// async fn main() -> Result<()> {
66    ///     RustApi::auto().run("0.0.0.0:8080").await
67    /// }
68    /// ```
69    #[cfg(feature = "swagger-ui")]
70    pub fn auto() -> Self {
71        Self::new().mount_auto_routes_grouped().docs("/docs")
72    }
73
74    #[cfg(not(feature = "swagger-ui"))]
75    pub fn auto() -> Self {
76        Self::new().mount_auto_routes_grouped()
77    }
78
79    /// Create a configurable RustAPI application with auto-routes.
80    ///
81    /// Provides builder methods for customization while still
82    /// auto-registering all decorated routes.
83    ///
84    /// # Example
85    ///
86    /// ```rust,ignore
87    /// use rustapi_rs::prelude::*;
88    ///
89    /// RustApi::config()
90    ///     .docs_path("/api-docs")
91    ///     .body_limit(5 * 1024 * 1024)  // 5MB
92    ///     .openapi_info("My API", "2.0.0", Some("API Description"))
93    ///     .run("0.0.0.0:8080")
94    ///     .await?;
95    /// ```
96    pub fn config() -> RustApiConfig {
97        RustApiConfig::new()
98    }
99
100    /// Set the global body size limit for request bodies
101    ///
102    /// This protects against denial-of-service attacks via large payloads.
103    /// The default limit is 1MB (1024 * 1024 bytes).
104    ///
105    /// # Arguments
106    ///
107    /// * `limit` - Maximum body size in bytes
108    ///
109    /// # Example
110    ///
111    /// ```rust,ignore
112    /// use rustapi_rs::prelude::*;
113    ///
114    /// RustApi::new()
115    ///     .body_limit(5 * 1024 * 1024)  // 5MB limit
116    ///     .route("/upload", post(upload_handler))
117    ///     .run("127.0.0.1:8080")
118    ///     .await
119    /// ```
120    pub fn body_limit(mut self, limit: usize) -> Self {
121        self.body_limit = Some(limit);
122        self
123    }
124
125    /// Disable the body size limit
126    ///
127    /// Warning: This removes protection against large payload attacks.
128    /// Only use this if you have other mechanisms to limit request sizes.
129    ///
130    /// # Example
131    ///
132    /// ```rust,ignore
133    /// RustApi::new()
134    ///     .no_body_limit()  // Disable body size limit
135    ///     .route("/upload", post(upload_handler))
136    /// ```
137    pub fn no_body_limit(mut self) -> Self {
138        self.body_limit = None;
139        self
140    }
141
142    /// Add a middleware layer to the application
143    ///
144    /// Layers are executed in the order they are added (outermost first).
145    /// The first layer added will be the first to process the request and
146    /// the last to process the response.
147    ///
148    /// # Example
149    ///
150    /// ```rust,ignore
151    /// use rustapi_rs::prelude::*;
152    /// use rustapi_core::middleware::{RequestIdLayer, TracingLayer};
153    ///
154    /// RustApi::new()
155    ///     .layer(RequestIdLayer::new())  // First to process request
156    ///     .layer(TracingLayer::new())    // Second to process request
157    ///     .route("/", get(handler))
158    ///     .run("127.0.0.1:8080")
159    ///     .await
160    /// ```
161    pub fn layer<L>(mut self, layer: L) -> Self
162    where
163        L: MiddlewareLayer,
164    {
165        self.layers.push(Box::new(layer));
166        self
167    }
168
169    /// Add a request interceptor to the application
170    ///
171    /// Request interceptors are executed in registration order before the route handler.
172    /// Each interceptor can modify the request before passing it to the next interceptor
173    /// or handler.
174    ///
175    /// # Example
176    ///
177    /// ```rust,ignore
178    /// use rustapi_core::{RustApi, interceptor::RequestInterceptor, Request};
179    ///
180    /// #[derive(Clone)]
181    /// struct AddRequestId;
182    ///
183    /// impl RequestInterceptor for AddRequestId {
184    ///     fn intercept(&self, mut req: Request) -> Request {
185    ///         req.extensions_mut().insert(uuid::Uuid::new_v4());
186    ///         req
187    ///     }
188    ///
189    ///     fn clone_box(&self) -> Box<dyn RequestInterceptor> {
190    ///         Box::new(self.clone())
191    ///     }
192    /// }
193    ///
194    /// RustApi::new()
195    ///     .request_interceptor(AddRequestId)
196    ///     .route("/", get(handler))
197    ///     .run("127.0.0.1:8080")
198    ///     .await
199    /// ```
200    pub fn request_interceptor<I>(mut self, interceptor: I) -> Self
201    where
202        I: RequestInterceptor,
203    {
204        self.interceptors.add_request_interceptor(interceptor);
205        self
206    }
207
208    /// Add a response interceptor to the application
209    ///
210    /// Response interceptors are executed in reverse registration order after the route
211    /// handler completes. Each interceptor can modify the response before passing it
212    /// to the previous interceptor or client.
213    ///
214    /// # Example
215    ///
216    /// ```rust,ignore
217    /// use rustapi_core::{RustApi, interceptor::ResponseInterceptor, Response};
218    ///
219    /// #[derive(Clone)]
220    /// struct AddServerHeader;
221    ///
222    /// impl ResponseInterceptor for AddServerHeader {
223    ///     fn intercept(&self, mut res: Response) -> Response {
224    ///         res.headers_mut().insert("X-Server", "RustAPI".parse().unwrap());
225    ///         res
226    ///     }
227    ///
228    ///     fn clone_box(&self) -> Box<dyn ResponseInterceptor> {
229    ///         Box::new(self.clone())
230    ///     }
231    /// }
232    ///
233    /// RustApi::new()
234    ///     .response_interceptor(AddServerHeader)
235    ///     .route("/", get(handler))
236    ///     .run("127.0.0.1:8080")
237    ///     .await
238    /// ```
239    pub fn response_interceptor<I>(mut self, interceptor: I) -> Self
240    where
241        I: ResponseInterceptor,
242    {
243        self.interceptors.add_response_interceptor(interceptor);
244        self
245    }
246
247    /// Add application state
248    ///
249    /// State is shared across all handlers and can be extracted using `State<T>`.
250    ///
251    /// # Example
252    ///
253    /// ```rust,ignore
254    /// #[derive(Clone)]
255    /// struct AppState {
256    ///     db: DbPool,
257    /// }
258    ///
259    /// RustApi::new()
260    ///     .state(AppState::new())
261    /// ```
262    pub fn state<S>(self, _state: S) -> Self
263    where
264        S: Clone + Send + Sync + 'static,
265    {
266        // Store state in the router's shared Extensions so `State<T>` extractor can retrieve it.
267        let state = _state;
268        let mut app = self;
269        let r = std::mem::take(&mut app.router);
270        app.router = r.state(state);
271        app
272    }
273
274    /// Register an `on_start` lifecycle hook
275    ///
276    /// The callback runs **after** route registration and **before** the server
277    /// begins accepting connections. Multiple hooks execute in registration order.
278    ///
279    /// # Example
280    ///
281    /// ```rust,ignore
282    /// RustApi::new()
283    ///     .on_start(|| async {
284    ///         println!("Server starting...");
285    ///         // e.g. run DB migrations, warm caches
286    ///     })
287    ///     .run("127.0.0.1:8080")
288    ///     .await
289    /// ```
290    pub fn on_start<F, Fut>(mut self, hook: F) -> Self
291    where
292        F: FnOnce() -> Fut + Send + 'static,
293        Fut: Future<Output = ()> + Send + 'static,
294    {
295        self.lifecycle_hooks
296            .on_start
297            .push(Box::new(move || Box::pin(hook())));
298        self
299    }
300
301    /// Register an `on_shutdown` lifecycle hook
302    ///
303    /// The callback runs **after** the shutdown signal is received and the server
304    /// stops accepting new connections. Multiple hooks execute in registration order.
305    ///
306    /// # Example
307    ///
308    /// ```rust,ignore
309    /// RustApi::new()
310    ///     .on_shutdown(|| async {
311    ///         println!("Server shutting down...");
312    ///         // e.g. flush logs, close DB connections
313    ///     })
314    ///     .run_with_shutdown("127.0.0.1:8080", ctrl_c())
315    ///     .await
316    /// ```
317    pub fn on_shutdown<F, Fut>(mut self, hook: F) -> Self
318    where
319        F: FnOnce() -> Fut + Send + 'static,
320        Fut: Future<Output = ()> + Send + 'static,
321    {
322        self.lifecycle_hooks
323            .on_shutdown
324            .push(Box::new(move || Box::pin(hook())));
325        self
326    }
327
328    /// Enable hot-reload mode for development
329    ///
330    /// When enabled:
331    /// - A dev-mode banner is printed at startup
332    /// - The `RUSTAPI_HOT_RELOAD` env var is set so that `cargo rustapi watch`
333    ///   can detect the server is reload-aware
334    /// - If the server is **not** already running under the CLI watcher,
335    ///   a helpful hint is printed suggesting `cargo rustapi run --watch`
336    ///
337    /// # Example
338    ///
339    /// ```rust,ignore
340    /// RustApi::new()
341    ///     .hot_reload(true)
342    ///     .route("/", get(hello))
343    ///     .run("127.0.0.1:8080")
344    ///     .await
345    /// ```
346    pub fn hot_reload(mut self, enabled: bool) -> Self {
347        self.hot_reload = enabled;
348        self
349    }
350
351    /// Get the inner router (for testing or advanced usage)
352    pub fn into_router(self) -> Router {
353        self.router
354    }
355
356    /// Get a reference to the inner router (for advanced usage, e.g. in-process MCP dispatch).
357    pub fn router(&self) -> &Router {
358        &self.router
359    }
360
361    /// Get the layer stack (for testing)
362    pub fn layers(&self) -> &LayerStack {
363        &self.layers
364    }
365
366    /// Get the interceptor chain (for testing)
367    pub fn interceptors(&self) -> &InterceptorChain {
368        &self.interceptors
369    }
370
371    /// Returns a dispatcher that can execute requests directly through this
372    /// app's router + layers + interceptors, with zero network overhead.
373    ///
374    /// This is intended for in-process protocol integrations (e.g. MCP tool calls
375    /// when running side-by-side with the main HTTP server).
376    pub fn request_dispatcher(&self) -> RequestDispatcher {
377        RequestDispatcher {
378            router: Arc::new(self.router.clone()),
379            layers: self.layers().clone(),
380            interceptors: self.interceptors().clone(),
381        }
382    }
383}
384
385impl Default for RustApi {
386    fn default() -> Self {
387        Self::new()
388    }
389}