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