form_fields 0.1.7

Helper crate for working with HTML forms.
Documentation
use axum::body::{Body, to_bytes};

use crate::from_form::FormSpec;

pub(crate) async fn parse_form_urlencoded<Form: FormSpec>(
    form: &mut Form,
    req: Body,
) -> Option<()> {
    let Ok(bytes) = to_bytes(req, u16::MAX as _).await else {
        return None;
    };

    let text = String::from_utf8(bytes.to_vec()).ok()?;
    log::debug!("Parsing form-urlencoded data: {}", text);

    let parsed = form_urlencoded::parse(&bytes);

    for (key, value) in parsed {
        log::debug!("Parsing field: {} = {}", key, value);
        if !form.parse_field(&key, &value) {
            return None;
        }
    }

    Some(())
}

#[cfg(test)]
mod test {
    use super::*;
    use crate as form_fields;
    use axum::body::Body;
    use form_fields_macro::FromForm;

    #[derive(FromForm)]
    struct Mock {
        #[text_field(display_name = "Field 1")]
        field1: String,
        #[text_field(display_name = "Field 2")]
        field2: String,
    }

    #[tokio::test]
    async fn parse_success() {
        let body_string = "field1=value1&field2=value2";
        let body = Body::from(body_string);
        let mut form = MockFormSpec::generate_spec();
        
        assert!(parse_form_urlencoded(&mut form, body).await.is_some());
        assert_eq!(form.field1.intermediate, Some("value1".to_string()));
        assert_eq!(form.field2.intermediate, Some("value2".to_string()));
        
        let inner = form.inner().unwrap();
        assert_eq!(inner.field1, "value1");
        assert_eq!(inner.field2, "value2");
    }

    #[tokio::test]
    async fn parse_failure() {
        let body_string = "field1=value1&field3=value3";
        let body = Body::from(body_string);
        let mut form = MockFormSpec::generate_spec();
        
        assert!(parse_form_urlencoded(&mut form, body).await.is_none());
        assert_eq!(form.field1.intermediate, Some("value1".to_string()));
        assert_eq!(form.field2.intermediate, None);
    
        assert!(form.inner().is_none());
    }

    #[tokio::test]
    async fn parse_partial_failure() {
        let body_string = "field1=value1";
        let body = Body::from(body_string);
        let mut form = MockFormSpec::generate_spec();
        
        assert!(parse_form_urlencoded(&mut form, body).await.is_some());
        assert_eq!(form.field1.intermediate, Some("value1".to_string()));
        assert_eq!(form.field2.intermediate, None);

        assert!(form.inner().is_none());
    }

    #[tokio::test]
    async fn parse_empty() {
        let body_string = "";
        let body = Body::from(body_string);
        let mut form = MockFormSpec::generate_spec();
        
        assert!(parse_form_urlencoded(&mut form, body).await.is_some());
        assert_eq!(form.field1.intermediate, None);
        assert_eq!(form.field2.intermediate, None);

        assert!(form.inner().is_none());
    }

    #[derive(FromForm)]
    struct Mockptional {
        #[text_field(display_name = "Field 1")]
        field1: Option<String>,
        #[text_field(display_name = "Field 2")]
        field2: Option<String>,
        #[text_field(display_name = "Field 3")]
        field3: Option<String>,
    }

    #[tokio::test]
    async fn parse_optional_fields() {
        let body_string = "field1=value1&field2=";
        let body = Body::from(body_string);
        let mut form = MockptionalFormSpec::generate_spec();

        assert!(parse_form_urlencoded(&mut form, body).await.is_some());
        assert_eq!(form.field1.intermediate, Some("value1".to_string()));
        assert_eq!(form.field2.intermediate, None);
        assert_eq!(form.field3.intermediate, None);

        let inner = form.inner().unwrap();
        assert_eq!(inner.field1, Some("value1".to_string()));
        assert_eq!(inner.field2, None);
        assert_eq!(inner.field3, None);
    }

}