limiting_factor_axum/api/
guards.rs1use axum::{
13 extract::{FromRequest, Request},
14 http::StatusCode,
15 response::{IntoResponse, Response},
16};
17use http_body_util::BodyExt;
18
19use limiting_factor_core::api::guards::{RequestBody, REQUEST_BODY_LIMIT};
20
21#[derive(Debug, Clone)]
23pub struct AxumRequestBody(pub RequestBody);
24
25impl AxumRequestBody {
26 pub fn new() -> Self {
27 Self(RequestBody::new())
28 }
29
30 pub fn into_string(self) -> String {
32 self.0.into_string()
33 }
34
35 pub fn into_optional_string(self) -> Option<String> {
36 self.0.into_optional_string()
37 }
38}
39
40impl From<RequestBody> for AxumRequestBody {
41 fn from(data: RequestBody) -> Self {
42 Self(data)
43 }
44}
45
46impl From<AxumRequestBody> for RequestBody {
47 fn from(body: AxumRequestBody) -> Self {
48 body.0
49 }
50}
51
52#[derive(Debug)]
54pub enum RequestBodyError {
55 TooLarge,
57
58 InvalidEncoding,
60
61 ReadError(String),
63}
64
65impl IntoResponse for RequestBodyError {
66 fn into_response(self) -> Response {
67 let (status, message) = match self {
68 RequestBodyError::TooLarge => (
69 StatusCode::PAYLOAD_TOO_LARGE,
70 "Request body too large".to_string(),
71 ),
72
73 RequestBodyError::InvalidEncoding => (
74 StatusCode::BAD_REQUEST,
75 "Request body contains invalid characters when trying to decode as UTF-8".to_string(),
76 ),
77
78 RequestBodyError::ReadError(err) => (
79 StatusCode::INTERNAL_SERVER_ERROR,
80 format!("Failed to read request body: {}", err),
81 ),
82 };
83
84 (status, message).into_response()
85 }
86}
87
88impl<S> FromRequest<S> for AxumRequestBody
89where
90 S: Send + Sync,
91{
92 type Rejection = RequestBodyError;
93
94 async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
95 let body = req.into_body();
97
98 let collected = match body.collect().await {
100 Ok(collected) => collected,
101 Err(e) => return Err(RequestBodyError::ReadError(e.to_string())),
102 };
103
104 let bytes = collected.to_bytes();
105
106 if bytes.len() > REQUEST_BODY_LIMIT {
108 return Err(RequestBodyError::TooLarge);
109 }
110
111 let content = match String::from_utf8(bytes.to_vec()) {
113 Ok(content) => content,
114 Err(_) => return Err(RequestBodyError::InvalidEncoding),
115 };
116
117 Ok(Self(RequestBody { content }))
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 #[tokio::test]
126 async fn test_empty_body_extraction() {
127 use axum::body::Body;
128 use axum::http::Request;
129
130 let req = Request::builder()
131 .body(Body::empty())
132 .unwrap();
133
134 let body = AxumRequestBody::from_request(req, &()).await.unwrap();
135 assert_eq!("", body.0.content);
136 assert_eq!(None, body.into_optional_string());
137 }
138
139 #[tokio::test]
140 async fn test_body_extraction() {
141 use axum::body::Body;
142 use axum::http::Request;
143
144 let req = Request::builder()
145 .body(Body::from("lorem ipsum dolor"))
146 .unwrap();
147
148 let body = AxumRequestBody::from_request(req, &()).await.unwrap();
149 assert_eq!("lorem ipsum dolor", body.0.content);
150 assert_eq!(Some("lorem ipsum dolor".to_string()), body.into_optional_string());
151 }
152}