Skip to main content

soma_http_server/middleware/
body_limit.rs

1//! Generic request body size limit.
2
3pub use tower_http::limit::RequestBodyLimitLayer;
4
5/// Build a layer that rejects request bodies larger than `bytes`.
6pub fn body_limit_layer(bytes: usize) -> RequestBodyLimitLayer {
7    RequestBodyLimitLayer::new(bytes)
8}
9
10#[cfg(test)]
11mod tests {
12    use super::*;
13    use axum::{body::Body, body::Bytes, http::Request, routing::post, Router};
14    use tower::ServiceExt;
15
16    // Handlers extract `Bytes` so the body is actually streamed and read —
17    // `RequestBodyLimitLayer` enforces its limit while the body is
18    // consumed, so a handler that ignores the body entirely would never
19    // observe the rejection.
20
21    #[tokio::test]
22    async fn request_within_limit_is_accepted() {
23        let app = Router::new()
24            .route(
25                "/",
26                post(|body: Bytes| async move { body.len().to_string() }),
27            )
28            .layer(body_limit_layer(16));
29
30        let request = Request::builder()
31            .method("POST")
32            .uri("/")
33            .body(Body::from("small"))
34            .unwrap();
35        let response = app.oneshot(request).await.unwrap();
36        assert_eq!(response.status(), axum::http::StatusCode::OK);
37    }
38
39    #[tokio::test]
40    async fn request_exactly_at_limit_is_accepted() {
41        // Boundary check: a body of exactly `bytes` must be accepted, not
42        // rejected — guards against an off-by-one (`>` vs `>=`) regression
43        // in the limit comparison.
44        let app = Router::new()
45            .route(
46                "/",
47                post(|body: Bytes| async move { body.len().to_string() }),
48            )
49            .layer(body_limit_layer(8));
50
51        let request = Request::builder()
52            .method("POST")
53            .uri("/")
54            .body(Body::from("exactly8"))
55            .unwrap();
56        let response = app.oneshot(request).await.unwrap();
57        assert_eq!(response.status(), axum::http::StatusCode::OK);
58    }
59
60    #[tokio::test]
61    async fn request_over_limit_is_rejected() {
62        let app = Router::new()
63            .route(
64                "/",
65                post(|body: Bytes| async move { body.len().to_string() }),
66            )
67            .layer(body_limit_layer(4));
68
69        let request = Request::builder()
70            .method("POST")
71            .uri("/")
72            .body(Body::from("this body is far too large"))
73            .unwrap();
74        let response = app.oneshot(request).await.unwrap();
75        assert_eq!(response.status(), axum::http::StatusCode::PAYLOAD_TOO_LARGE);
76    }
77}