foxtive_ntex/http/extractors/
string_body.rs1use crate::error::HttpError;
2use foxtive::prelude::{AppMessage, AppResult};
3use ntex::http::Payload;
4use ntex::util::BytesMut;
5use ntex::web::{FromRequest, HttpRequest};
6use tracing::debug;
7
8pub struct StringBody {
19 body: String,
20}
21
22impl StringBody {
23 pub fn body(&self) -> &String {
25 &self.body
26 }
27
28 pub fn into_body(self) -> String {
30 self.body
31 }
32
33 pub fn len(&self) -> usize {
35 self.body.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
40 self.body.is_empty()
41 }
42
43 pub fn parse<T: std::str::FromStr>(&self) -> AppResult<T>
46 where
47 <T as std::str::FromStr>::Err: ToString,
48 {
49 self.body.parse::<T>().map_err(|e| {
50 HttpError::AppMessage(AppMessage::WarningMessageString(e.to_string())).into_app_error()
51 })
52 }
53}
54
55impl From<String> for StringBody {
56 fn from(body: String) -> Self {
57 Self { body }
58 }
59}
60
61impl From<&str> for StringBody {
62 fn from(body: &str) -> Self {
63 Self {
64 body: body.to_owned(),
65 }
66 }
67}
68
69impl<Err> FromRequest<Err> for StringBody {
70 type Error = HttpError;
71
72 async fn from_request(_req: &HttpRequest, payload: &mut Payload) -> Result<Self, Self::Error> {
73 let mut bytes = BytesMut::new();
74 while let Some(chunk) = ntex::util::stream_recv(payload).await {
75 bytes.extend_from_slice(&chunk?);
76 }
77
78 let raw = String::from_utf8(bytes.to_vec())?;
79 debug!("[string-body] {raw}");
80 Ok(Self { body: raw })
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use ntex::http::StatusCode;
88 use ntex::web::WebResponseError;
89
90 #[test]
91 fn test_body_and_into_body() {
92 let data = "hello string body".to_string();
93 let sb = StringBody::from(data.clone());
94 assert_eq!(sb.body(), &data);
95
96 let sb = StringBody::from(&data[..]);
97 assert_eq!(sb.body(), &data);
98
99 let moved = sb.into_body();
100 assert_eq!(moved, data);
101 }
102
103 #[test]
104 fn test_len_and_is_empty() {
105 let empty = StringBody::from("");
106 assert!(empty.is_empty());
107 assert_eq!(empty.len(), 0);
108
109 let s = StringBody::from("abcde");
110 assert!(!s.is_empty());
111 assert_eq!(s.len(), 5);
112 }
113
114 #[test]
115 #[allow(clippy::approx_constant)]
116 fn test_parse_success() {
117 let s = StringBody::from("42");
118 let val: i32 = s.parse().unwrap();
119 assert_eq!(val, 42);
120
121 let s = StringBody::from("3.1415");
122 let val: f64 = s.parse().unwrap();
123 assert!((val - 3.1415).abs() < 1e-6);
124 }
125
126 #[test]
127 fn test_parse_failure() {
128 let s = StringBody::from("not_a_number");
129 let result: AppResult<i32> = s.parse();
130 assert!(result.is_err());
131 let err = result.unwrap_err().downcast::<HttpError>().unwrap();
132 assert_eq!(err.status_code(), StatusCode::BAD_REQUEST);
133 assert!(err.to_string().to_lowercase().contains("invalid"));
135 }
136
137 #[test]
138 fn test_deprecated_raw() {
139 let data = "raw string body".to_string();
140 let sb = StringBody::from(data.clone());
141 assert_eq!(sb.body(), &data);
142 }
143}