tork_core/extract/
valid.rs1use garde::Validate;
4use serde::de::DeserializeOwned;
5
6use crate::error::{Error, Result};
7use crate::extract::body::{
8 configured_body_limit, ensure_json_depth_within_limit, read_body_capped_with,
9};
10use crate::extract::{FromRequest, RequestContext};
11
12#[derive(Debug, Clone)]
20pub struct Valid<T>(pub T);
21
22impl<T> Valid<T> {
23 pub fn into_inner(self) -> T {
25 self.0
26 }
27}
28
29impl<T> FromRequest for Valid<T>
30where
31 T: DeserializeOwned + Validate<Context = ()> + Send,
32{
33 fn from_request(
34 ctx: &RequestContext,
35 ) -> impl std::future::Future<Output = Result<Self>> + Send {
36 let taken = ctx.take_body();
37 let limit = configured_body_limit(ctx);
38 async move {
39 let bytes = read_body_capped_with(taken?, limit).await?;
40 ensure_json_depth_within_limit(&bytes)?;
41 let value: T = serde_json::from_slice(&bytes)
42 .map_err(|_| Error::unprocessable("request body is not valid JSON"))?;
43 value.validate().map_err(Error::from_garde_report)?;
44 Ok(Valid(value))
45 }
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use crate::body::box_body;
53 use crate::error::ErrorKind;
54 use crate::extract::PathParams;
55 use crate::state::StateMap;
56
57 use bytes::Bytes;
58 use http_body_util::Full;
59 use serde::Deserialize;
60 use std::sync::Arc;
61
62 #[derive(Debug, Deserialize, garde::Validate)]
63 struct Sample {
64 #[garde(range(min = 1))]
65 count: i64,
66 }
67
68 fn context_with_body(json: &str) -> RequestContext {
69 let head = http::Request::new(()).into_parts().0;
70 let body = box_body(Full::new(Bytes::copy_from_slice(json.as_bytes())));
71 RequestContext::new(head, PathParams::new(), Arc::new(StateMap::new()), body)
72 }
73
74 #[tokio::test]
75 async fn valid_body_is_accepted() {
76 let ctx = context_with_body(r#"{"count": 5}"#);
77 let valid = <Valid<Sample> as FromRequest>::from_request(&ctx)
78 .await
79 .expect("should validate");
80 assert_eq!(valid.into_inner().count, 5);
81 }
82
83 #[tokio::test]
84 async fn invalid_body_is_unprocessable_with_details() {
85 let ctx = context_with_body(r#"{"count": 0}"#);
86 let error = <Valid<Sample> as FromRequest>::from_request(&ctx)
87 .await
88 .unwrap_err();
89 assert_eq!(error.kind(), ErrorKind::Unprocessable);
90 assert!(
91 !error.details().is_empty(),
92 "should report the failing field"
93 );
94 }
95
96 #[tokio::test]
97 async fn deeply_nested_body_is_rejected_before_validation() {
98 let json = format!(
99 "{}0{}",
100 "{\"count\":".to_owned() + &"[".repeat(crate::extract::body::MAX_JSON_NESTING + 1),
101 "]".repeat(crate::extract::body::MAX_JSON_NESTING + 1) + "}"
102 );
103 let ctx = context_with_body(&json);
104 let error = <Valid<Sample> as FromRequest>::from_request(&ctx)
105 .await
106 .unwrap_err();
107 assert_eq!(error.kind(), ErrorKind::BadRequest);
108 assert_eq!(error.message(), "request body is too deeply nested");
109 }
110}