camber 0.1.2

Opinionated async Rust for IO-bound services on top of Tokio
Documentation
use super::middleware::Next;
use super::request::Request;
use super::response::Response;
use serde::de::DeserializeOwned;
use std::future::Future;
use std::pin::Pin;

/// Validate the request body as JSON of type `T`.
///
/// Requests with an empty body pass through without validation (e.g. GET).
/// Invalid JSON returns 400 Bad Request with the parse error message.
///
/// The body string is parsed twice: once here for validation and once in the
/// handler via `req.json()`. Both calls deserialize the same cached `&str`
/// (body text is stored in a `OnceLock`). The cost is CPU-only -- no IO or
/// allocation for the body itself. A type-erased cache would eliminate the
/// second parse but adds `Any + Send + Sync` complexity for marginal gain.
pub fn json<T: DeserializeOwned + 'static>()
-> impl Fn(&Request, Next) -> Pin<Box<dyn Future<Output = Response> + Send>> + Send + Sync + 'static
{
    move |req, next| {
        if req.body().is_empty() {
            return next.call(req);
        }
        match req.json::<T>() {
            Ok(_) => next.call(req),
            Err(e) => {
                let resp = Response::text_raw(400, &e.to_string());
                Box::pin(async move { resp })
            }
        }
    }
}