ngyn_shared/server/transformer.rs
1use std::{borrow::Cow, str::FromStr};
2
3use bytes::Bytes;
4use futures_util::StreamExt;
5use http::{header::CONTENT_TYPE, HeaderValue};
6use http_body_util::{BodyStream, Full};
7use multer::Multipart;
8use serde::Deserialize;
9
10use crate::server::NgynContext;
11
12/// Represents a transformer trait.
13pub trait Transformer<'a> {
14 /// Transforms the given `NgynContext` and returns an instance of `Self`.
15 ///
16 /// ### Arguments
17 ///
18 /// * `cx` - The mutable reference to the `NgynContext`.
19 ///
20 /// ### Examples
21 ///
22 /// ```rust ignore
23 /// struct MyTransformer;
24 ///
25 /// impl Transformer for MyTransformer {
26 /// fn transform(cx: &mut NgynContext) -> Self {
27 /// // Transformation logic goes here
28 /// MyTransformer {}
29 /// }
30 /// }
31 /// ```
32 #[must_use]
33 fn transform(cx: &'a mut NgynContext) -> Self
34 where
35 Self: Sized;
36}
37
38/// Represents a transducer struct.
39pub struct Transducer;
40
41impl<'a> Transducer {
42 /// Reduces the given `NgynContext` using the specified `Transformer` and returns an instance of `S`.
43 ///
44 /// ### Arguments
45 ///
46 /// * `cx` - The mutable reference to the `NgynContext`.
47 ///
48 /// ### Examples
49 ///
50 /// ```rust ignore
51 ///
52 /// struct MyTransformer;
53 ///
54 /// impl Transformer for MyTransformer {
55 /// fn transform(cx: &mut NgynContext) -> Option<Self> {
56 /// // Transformation logic goes here
57 /// MyTransformer
58 /// }
59 /// }
60 ///
61 /// let mut cx = NgynContext::default();
62 ///
63 /// let result: MyTransformer = Transducer::reduce(&mut cx);
64 /// ```
65 #[must_use]
66 pub fn reduce<S: Transformer<'a>>(cx: &'a mut NgynContext) -> S {
67 S::transform(cx)
68 }
69}
70
71/// Represents a parameter struct.
72pub struct Param<'a> {
73 data: Vec<(&'a str, &'a str)>,
74}
75
76impl<'a> Param<'a> {
77 /// Retrieves the value associated with the specified `id` from the parameter data.
78 ///
79 /// ### Arguments
80 ///
81 /// * `id` - The identifier to search for.
82 ///
83 /// ### Returns
84 ///
85 /// * `Some(String)` - The value associated with the `id`, if found.
86 /// * `None` - If no value is associated with the `id`.
87 ///
88 /// ### Examples
89 ///
90 /// ```rust ignore
91 /// let param = Param {
92 /// data: vec![
93 /// ("id", "123"),
94 /// ("name", "John"),
95 /// ],
96 /// };
97 ///
98 /// assert_eq!(param.get("id"), Some("123".to_string()));
99 /// assert_eq!(param.get("name"), Some("John".to_string()));
100 /// assert_eq!(param.get("age"), None);
101 /// ```
102 pub fn get<F: FromStr>(&self, id: &str) -> Option<F> {
103 for (key, value) in &self.data {
104 if *key == id {
105 if let Ok(value) = value.parse() {
106 return Some(value);
107 }
108 }
109 }
110 None
111 }
112}
113
114impl<'a: 'b, 'b> Transformer<'a> for Param<'b> {
115 /// Transforms the given `NgynContext` into a `Param` instance.
116 ///
117 /// ### Arguments
118 ///
119 /// * `cx` - The mutable reference to the `NgynContext`.
120 ///
121 /// ### Returns
122 ///
123 /// * `Param` - The transformed `Param` instance.
124 ///
125 /// ### Examples
126 ///
127 /// ```rust ignore
128 /// use crate::context::NgynContext;
129 ///
130 /// let mut cx = NgynContext::default();
131 /// let param: Param = Param::transform(&mut cx);
132 /// ```
133 fn transform(cx: &'a mut NgynContext) -> Self {
134 let data: Vec<(&'a str, &'a str)> = cx
135 .params()
136 .expect("Extracting params should only be done in route handlers.")
137 .iter()
138 .collect();
139 Param { data }
140 }
141}
142
143/// Represents a query struct.
144pub struct Query<'q> {
145 uri: &'q http::uri::Uri,
146}
147
148impl<'q> Query<'q> {
149 /// Retrieves the value associated with the specified `id` from the query parameters.
150 ///
151 /// ### Arguments
152 ///
153 /// * `id` - The identifier to search for.
154 ///
155 /// ### Returns
156 ///
157 /// * `Some(String)` - The value associated with the `id`, if found.
158 /// * `None` - If no value is associated with the `id`.
159 ///
160 /// ### Examples
161 ///
162 /// ```rust ignore
163 /// use hyper::Uri;
164 ///
165 /// let uri: Uri = "https://example.com/?id=123&name=John".parse().unwrap();
166 /// let query = Query { uri: &uri };
167 ///
168 /// assert_eq!(query.get("id"), Some("123".to_string()));
169 /// assert_eq!(query.get("name"), Some("John".to_string()));
170 /// assert_eq!(query.get("age"), None);
171 /// ```
172 pub fn get<F: FromStr>(&self, id: &str) -> Option<F> {
173 let query = self.uri.query().unwrap_or("");
174 let query = url::form_urlencoded::parse(query.as_bytes());
175 for (key, value) in query {
176 if key == id {
177 if let Ok(value) = value.parse() {
178 return Some(value);
179 }
180 }
181 }
182 None
183 }
184}
185
186impl<'a: 'q, 'q> Transformer<'a> for Query<'q> {
187 /// Transforms the given `NgynContext` into a `Query` instance.
188 ///
189 /// ### Arguments
190 ///
191 /// * `cx` - The mutable reference to the `NgynContext`.
192 ///
193 /// ### Returns
194 ///
195 /// * `Query` - The transformed `Query` instance.
196 ///
197 /// ### Examples
198 ///
199 /// ```rust ignore
200 /// use crate::context::NgynContext;
201 /// use hyper::Uri;
202 ///
203 /// let mut cx = NgynContext::default();
204 ///
205 /// let uri: Uri = "https://example.com/?id=123&name=John".parse().unwrap();
206 /// cx.request.set_uri(uri);
207 ///
208 /// let query: Query = Query::transform(&mut cx);
209 /// ```
210 fn transform(cx: &'a mut NgynContext) -> Self {
211 Query {
212 uri: cx.request().uri(),
213 }
214 }
215}
216
217/// Represents a data transfer object struct.
218pub struct Body<'b> {
219 content_type: Option<&'b HeaderValue>,
220 data: &'b Vec<u8>,
221}
222
223impl<'b> Body<'b> {
224 /// Parses the data into the specified type using serde deserialization.
225 /// Once read, the body data is consumed and cannot be read again.
226 ///
227 /// ### Arguments
228 ///
229 /// * `S` - The type to deserialize the data into.
230 ///
231 /// ### Returns
232 ///
233 /// * `Result<S, serde_json::Error>` - The deserialized result, if successful.
234 ///
235 /// ### Examples
236 ///
237 /// ```rust ignore
238 /// use serde::Deserialize;
239 ///
240 /// let body = Body {
241 /// data: r#"{"name": "John", "age": 30}"#.to_string().into_bytes(),
242 /// };
243 ///
244 /// #[derive(Deserialize)]
245 /// struct Person {
246 /// name: String,
247 /// age: u32,
248 /// }
249 ///
250 /// let result: Result<Person, serde_json::Error> = body.json();
251 /// ```
252 pub fn json<S: for<'a> Deserialize<'a>>(self) -> Result<S, serde_json::Error> {
253 serde_json::from_str(&self.text())
254 }
255
256 /// Reads the body data as a string.
257 /// Once read, the body data is consumed and cannot be read again.
258 ///
259 /// ### Returns
260 ///
261 /// * `String` - The body data as a string.
262 ///
263 /// ### Examples
264 ///
265 /// ```rust ignore
266 /// let body = Body {
267 /// data: r#"{"name": "John", "age": 30}"#.to_string().into_bytes(),
268 /// };
269 ///
270 /// assert_eq!(body.text(), r#"{"name": "John", "age": 30}"#);
271 /// ```
272 pub fn text(self) -> Cow<'b, str> {
273 String::from_utf8_lossy(self.data)
274 }
275
276 /// Parses the data into a `multipart/form-data` stream.
277 /// Once read, the body data is consumed and cannot be read again.
278 ///
279 /// ### Returns
280 ///
281 /// * `Multipart<'static>` - The body data as a `multipart/form-data` stream.
282 ///
283 /// ### Examples
284 ///
285 /// ```rust ignore
286 /// let body = Body {
287 /// data: r#"------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name="file"; filename="example.txt"\r\nContent-Type: text/plain\r\n\r\nHello World\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--\r\n"#.to_string().into_bytes(),
288 /// };
289 ///
290 /// let stream = body.form_data();
291 /// ```
292 pub fn form_data(self) -> Result<Multipart<'b>, multer::Error> {
293 if let Some(content_type) = self.content_type {
294 let boundary = multer::parse_boundary(
295 content_type
296 .to_str()
297 .expect("Content Type header contains invalid ASCII value"),
298 )?;
299 let body: Full<Bytes> = Full::new(Bytes::from(self.data.to_owned()));
300 let stream = BodyStream::new(body).filter_map(|result| async move {
301 result.map(|frame| frame.into_data().ok()).transpose()
302 });
303 Ok(Multipart::new(stream, boundary))
304 } else {
305 Err(multer::Error::NoBoundary)
306 }
307 }
308}
309
310impl<'a: 'b, 'b> Transformer<'a> for Body<'b> {
311 /// Transforms the given `NgynContext` into a `Body` instance.
312 ///
313 /// ### Arguments
314 ///
315 /// * `cx` - The mutable reference to the `NgynContext`.
316 ///
317 /// ### Returns
318 ///
319 /// * `Body` - The transformed `Body` instance.
320 ///
321 /// ### Examples
322 ///
323 /// ```rust ignore
324 /// use crate::context::NgynContext;
325 ///
326 /// let mut cx = NgynContext::default();
327 ///
328 /// let dto: Body = Body::transform(&mut cx);
329 /// ```
330 fn transform(cx: &'a mut NgynContext) -> Self {
331 let data = cx.request().body();
332 let content_type = cx.request().headers().get(CONTENT_TYPE);
333 Body { data, content_type }
334 }
335}