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}