Skip to content

Commit 8e20c54

Browse files
authored
feat(transformer): add FormData struct for handling multipart form data (#258)
* feat(transformer): add FormData struct for handling multipart form data * chore: cleanupo * refactor: simplify generics and remove redundant lifetime annotations
1 parent fa3153b commit 8e20c54

File tree

3 files changed

+112
-6
lines changed

3 files changed

+112
-6
lines changed

crates/shared/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ pub(crate) trait Middleware: Send + Sync {
125125
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>>;
126126
}
127127

128-
impl<'b, T: NgynMiddleware + Send + 'b> Middleware for T {
128+
impl<T: NgynMiddleware + Send> Middleware for T {
129129
fn run<'a>(
130130
&'a self,
131131
cx: &'a mut NgynContext<'_>,

crates/shared/src/server/context.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ impl<V> NgynContextValue<V> {
1818
}
1919

2020
/// Represents the state of an application in Ngyn
21-
2221
pub trait AppState: Any + Send + Sync + 'static {
2322
fn as_any(&self) -> &dyn Any;
2423
fn as_any_mut(&mut self) -> &mut dyn Any;

crates/shared/src/server/transformer.rs

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub struct Param<'a> {
7373
data: Vec<(&'a str, &'a str)>,
7474
}
7575

76-
impl<'a> Param<'a> {
76+
impl Param<'_> {
7777
/// Retrieves the value associated with the specified `id` from the parameter data.
7878
///
7979
/// ### Arguments
@@ -145,7 +145,7 @@ pub struct Query<'q> {
145145
uri: &'q http::uri::Uri,
146146
}
147147

148-
impl<'q> Query<'q> {
148+
impl Query<'_> {
149149
/// Retrieves the value associated with the specified `id` from the query parameters.
150150
///
151151
/// ### Arguments
@@ -287,9 +287,9 @@ impl<'b> Body<'b> {
287287
/// 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(),
288288
/// };
289289
///
290-
/// let stream = body.form_data();
290+
/// let stream = body.to_multipart();
291291
/// ```
292-
pub fn form_data(self) -> Result<Multipart<'b>, multer::Error> {
292+
pub fn to_multipart(self) -> Result<Multipart<'b>, multer::Error> {
293293
if let Some(content_type) = self.content_type {
294294
let boundary = multer::parse_boundary(
295295
content_type
@@ -333,3 +333,110 @@ impl<'a: 'b, 'b> Transformer<'a> for Body<'b> {
333333
Body { data, content_type }
334334
}
335335
}
336+
337+
/// Represents a form data struct for handling multipart form data.
338+
pub struct FormData {
339+
fields: std::collections::HashMap<String, String>,
340+
}
341+
342+
impl FormData {
343+
/// Creates a new FormData instance from a multipart form.
344+
///
345+
/// ### Arguments
346+
///
347+
/// * `multipart` - The multipart form data.
348+
///
349+
/// ### Returns
350+
///
351+
/// * `FormData` - A new FormData instance.
352+
pub async fn from_multipart(mut multipart: Multipart<'_>) -> Result<Self, multer::Error> {
353+
let mut fields = std::collections::HashMap::new();
354+
355+
// Process all fields in the multipart form
356+
while let Some(field) = multipart.next_field().await? {
357+
let name = field.name().unwrap_or("");
358+
let name = name.to_string();
359+
if let Ok(value) = field.text().await {
360+
fields.insert(name, value);
361+
}
362+
}
363+
364+
Ok(FormData { fields })
365+
}
366+
367+
/// Creates a new empty FormData instance.
368+
pub fn new() -> Self {
369+
FormData {
370+
fields: std::collections::HashMap::new(),
371+
}
372+
}
373+
374+
/// Retrieves the value associated with the specified field name from the form data.
375+
///
376+
/// ### Arguments
377+
///
378+
/// * `name` - The field name to search for.
379+
///
380+
/// ### Returns
381+
///
382+
/// * `Option<F>` - The parsed value associated with the field name, if found and parseable.
383+
///
384+
/// ### Examples
385+
///
386+
/// ```rust ignore
387+
/// let form_data = FormData::new();
388+
///
389+
/// let name: Option<String> = form_data.get("name");
390+
/// let age: Option<u32> = form_data.get("age");
391+
/// ```
392+
pub fn get<F: FromStr>(&self, name: &str) -> Option<F> {
393+
self.fields
394+
.get(name)
395+
.and_then(|value| value.parse::<F>().ok())
396+
}
397+
398+
/// Sets a field value in the form data.
399+
///
400+
/// ### Arguments
401+
///
402+
/// * `name` - The field name.
403+
/// * `value` - The field value.
404+
pub fn set<T: ToString>(&mut self, name: &str, value: T) {
405+
self.fields.insert(name.to_string(), value.to_string());
406+
}
407+
408+
/// Checks if the form data contains a field with the specified name.
409+
///
410+
/// ### Arguments
411+
///
412+
/// * `name` - The field name to check for.
413+
///
414+
/// ### Returns
415+
///
416+
/// * `bool` - True if the field exists, false otherwise.
417+
pub fn has(&self, name: &str) -> bool {
418+
self.fields.contains_key(name)
419+
}
420+
421+
/// Returns all field names in the form data.
422+
///
423+
/// ### Returns
424+
///
425+
/// * `Vec<String>` - A vector of all field names.
426+
pub fn field_names(&self) -> Vec<String> {
427+
self.fields.keys().cloned().collect()
428+
}
429+
430+
/// Helper method for the derive macro to set a field value by name.
431+
///
432+
/// This is used by the FormData derive macro to set field values.
433+
pub fn get_field_mut(&mut self, name: &str) -> Option<&mut String> {
434+
self.fields.get_mut(name)
435+
}
436+
}
437+
438+
impl Default for FormData {
439+
fn default() -> Self {
440+
Self::new()
441+
}
442+
}

0 commit comments

Comments
 (0)