use crate::handler::BoxFuture;
use crate::middleware::{Middleware, Next};
use crate::request::Request;
use crate::response::Response;
use crate::status::Status;
use rustlavel_core::Json;
#[derive(Debug, Clone, Copy)]
pub struct BodyLimit {
max: usize,
}
impl BodyLimit {
pub fn bytes(max: usize) -> Self {
BodyLimit { max }
}
pub fn kilobytes(max: usize) -> Self {
BodyLimit::bytes(max * 1024)
}
pub fn megabytes(max: usize) -> Self {
BodyLimit::bytes(max * 1024 * 1024)
}
}
impl Middleware for BodyLimit {
fn handle(&self, request: Request, next: Next) -> BoxFuture<Response> {
let declared = request.headers().content_length().unwrap_or(0);
let actual = request.body().len();
let size = declared.max(actual);
if size <= self.max {
return next.run(request);
}
let max = self.max;
let wants_json = request.wants_json();
Box::pin(async move {
let message = format!("The request body is {size} bytes; this endpoint accepts at most {max}.");
let response = Response::new(Status::PAYLOAD_TOO_LARGE);
if wants_json {
response.with_json(Json::object([("message", Json::from(message))]))
} else {
response.with_text(message)
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::method::Method;
use crate::router::Router;
use crate::testing::TestClient;
fn client(limit: BodyLimit) -> TestClient {
let mut router = Router::new();
router.middleware(limit);
router.post("/notes", |req: Request| async move {
Response::text(format!("{} bytes", req.body().len()))
});
TestClient::new(router)
}
#[tokio::test]
async fn a_body_under_the_limit_reaches_the_handler() {
let request = Request::new(Method::Post, "/notes").with_body(vec![b'x'; 100]);
let response = client(BodyLimit::bytes(100)).send(request).await;
let response = response.assert_ok();
assert_eq!(response.body(), "100 bytes");
}
#[tokio::test]
async fn a_body_over_the_limit_is_a_413_with_the_numbers() {
let request = Request::new(Method::Post, "/notes")
.with_body(vec![b'x'; 101])
.with_header("accept", "application/json");
let response = client(BodyLimit::bytes(100)).send(request).await;
let response = response.assert_status(413);
let message = response.json().get("message").and_then(Json::as_str).unwrap().to_string();
assert!(message.contains("101 bytes") && message.contains("at most 100"), "{message}");
}
#[tokio::test]
async fn a_declared_length_over_the_limit_is_refused_too() {
let request = Request::new(Method::Post, "/notes").with_header("content-length", "5000000");
client(BodyLimit::kilobytes(64)).send(request).await.assert_status(413);
}
#[test]
fn the_unit_helpers_multiply_correctly() {
assert_eq!(BodyLimit::kilobytes(2).max, 2048);
assert_eq!(BodyLimit::megabytes(1).max, 1_048_576);
}
}