Skip to content

Commit ea31951

Browse files
committed
Add body limit and Prometheus metrics middleware
Introduces a body size limit middleware to protect against large payload attacks, configurable via the RustApi builder. Adds Prometheus-based metrics middleware (feature-gated) with a /metrics endpoint, and exposes error environment configuration and error IDs for improved error handling and observability. Updates dependencies and public API exports accordingly.
1 parent 7e78dc4 commit ea31951

15 files changed

Lines changed: 3061 additions & 31 deletions

File tree

Cargo.lock

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ inventory = "0.3"
6060
# Validation
6161
validator = { version = "0.18", features = ["derive"] }
6262

63+
# UUID
64+
uuid = { version = "1.6", features = ["v4"] }
65+
66+
# Metrics
67+
prometheus = "0.13"
68+
6369
# OpenAPI
6470
utoipa = { version = "4.2" }
6571

crates/rustapi-core/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,17 @@ thiserror = { workspace = true }
4040
tracing = { workspace = true }
4141
tracing-subscriber = { workspace = true }
4242
inventory = { workspace = true }
43+
uuid = { workspace = true }
4344

4445
# Cookies (optional)
4546
cookie = { version = "0.18", optional = true }
4647

4748
# Validation
4849
rustapi-validate = { workspace = true }
4950

51+
# Metrics (optional)
52+
prometheus = { workspace = true, optional = true }
53+
5054
# SQLx (optional)
5155
sqlx = { version = "0.8", optional = true, default-features = false }
5256

@@ -62,3 +66,4 @@ swagger-ui = ["rustapi-openapi/swagger-ui"]
6266
test-utils = []
6367
cookies = ["dep:cookie"]
6468
sqlx = ["dep:sqlx"]
69+
metrics = ["dep:prometheus"]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Seeds for failure cases proptest has generated in the past. It is
2+
# automatically read and these particular cases re-run before any
3+
# novel cases are generated.
4+
#
5+
# It is recommended to check this file in to source control so that
6+
# everyone who runs the test benefits from these saved cases.
7+
cc 8650e551b5fa0957402d6dd6c864be825443b29eb5b8972df6ef70e9ee040b20 # shrinks to sensitive_message = "-", internal_details = "0", status_code = 500

crates/rustapi-core/src/app.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! RustApi application builder
22
33
use crate::error::Result;
4-
use crate::middleware::{LayerStack, MiddlewareLayer};
4+
use crate::middleware::{BodyLimitLayer, LayerStack, MiddlewareLayer, DEFAULT_BODY_LIMIT};
55
use crate::router::{MethodRouter, Router};
66
use crate::server::Server;
77
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
@@ -27,6 +27,7 @@ pub struct RustApi {
2727
router: Router,
2828
openapi_spec: rustapi_openapi::OpenApiSpec,
2929
layers: LayerStack,
30+
body_limit: Option<usize>,
3031
}
3132

3233
impl RustApi {
@@ -48,9 +49,52 @@ impl RustApi {
4849
.register::<rustapi_openapi::ValidationErrorSchema>()
4950
.register::<rustapi_openapi::FieldErrorSchema>(),
5051
layers: LayerStack::new(),
52+
body_limit: Some(DEFAULT_BODY_LIMIT), // Default 1MB limit
5153
}
5254
}
5355

56+
/// Set the global body size limit for request bodies
57+
///
58+
/// This protects against denial-of-service attacks via large payloads.
59+
/// The default limit is 1MB (1024 * 1024 bytes).
60+
///
61+
/// # Arguments
62+
///
63+
/// * `limit` - Maximum body size in bytes
64+
///
65+
/// # Example
66+
///
67+
/// ```rust,ignore
68+
/// use rustapi_rs::prelude::*;
69+
///
70+
/// RustApi::new()
71+
/// .body_limit(5 * 1024 * 1024) // 5MB limit
72+
/// .route("/upload", post(upload_handler))
73+
/// .run("127.0.0.1:8080")
74+
/// .await
75+
/// ```
76+
pub fn body_limit(mut self, limit: usize) -> Self {
77+
self.body_limit = Some(limit);
78+
self
79+
}
80+
81+
/// Disable the body size limit
82+
///
83+
/// Warning: This removes protection against large payload attacks.
84+
/// Only use this if you have other mechanisms to limit request sizes.
85+
///
86+
/// # Example
87+
///
88+
/// ```rust,ignore
89+
/// RustApi::new()
90+
/// .no_body_limit() // Disable body size limit
91+
/// .route("/upload", post(upload_handler))
92+
/// ```
93+
pub fn no_body_limit(mut self) -> Self {
94+
self.body_limit = None;
95+
self
96+
}
97+
5498
/// Add a middleware layer to the application
5599
///
56100
/// Layers are executed in the order they are added (outermost first).
@@ -336,7 +380,13 @@ impl RustApi {
336380
/// .run("127.0.0.1:8080")
337381
/// .await
338382
/// ```
339-
pub async fn run(self, addr: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
383+
pub async fn run(mut self, addr: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
384+
// Apply body limit layer if configured (should be first in the chain)
385+
if let Some(limit) = self.body_limit {
386+
// Prepend body limit layer so it's the first to process requests
387+
self.layers.prepend(Box::new(BodyLimitLayer::new(limit)));
388+
}
389+
340390
let server = Server::new(self.router, self.layers);
341391
server.run(addr).await
342392
}

0 commit comments

Comments
 (0)