1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use mime::Mime;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};

/// Body of the request, it contains all the currently supported options
#[derive(Debug, Default, PartialEq)]
pub enum Body {
    /// The request does not have body (common for GET requests)
    #[default]
    None,
    /// The body of the request is in text format. This body can be bundled with a Content-Type
    /// like application/json to send JSONs with full meaning.
    Raw { content: String, mime: Mime },
    /// The body of the request contains a file located at the given path.
    /// This body can be bundled with Content-Type headers like application/pdf.
    Binary { path: String, mime: Mime },
    /// The body is an HTTP form.
    FormData(HashMap<String, FormDataValue>),
    /// The body of the request is an HTTP form encoded in the URL.
    XFormUrlEncoded(HashMap<String, String>),
}

/// Types of values for form data, can be text or binaries
#[derive(Debug, PartialEq)]
pub enum FormDataValue {
    Text(String),
    File(String),
}

impl Body {
    /// Returns the MIME type associated with the body
    ///
    /// ```
    /// # fn main() {
    /// # use thiserror::__private::AsDisplay;
    /// # use rede_parser::body::Body;
    /// # let body = Body::Binary { path: "path".to_string(), mime: mime::APPLICATION_OCTET_STREAM};
    /// assert_eq!(body.mime().map(|m| m.to_string()), Some("application/octet-stream".to_string()));
    /// # }
    /// ```
    ///
    #[must_use]
    pub fn mime(&self) -> Option<&Mime> {
        match self {
            Body::None => None,
            Body::Raw { mime, .. } | Body::Binary { mime, .. } => Some(mime),
            Body::FormData(_) => Some(&mime::MULTIPART_FORM_DATA),
            Body::XFormUrlEncoded(_) => Some(&mime::APPLICATION_WWW_FORM_URLENCODED),
        }
    }
}

impl Display for Body {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        use Body::{Binary, FormData, None, Raw, XFormUrlEncoded};
        match self {
            None => Ok(()),
            Raw { content, .. } => f.write_str(content),
            Binary { path, .. } => writeln!(f, "@{path}"),
            FormData(map) => {
                for (k, v) in map {
                    writeln!(f, "{k}: {v}")?;
                }
                Ok(())
            }
            XFormUrlEncoded(map) => {
                for (k, v) in map {
                    writeln!(f, "{k}: {v}")?;
                }
                Ok(())
            }
        }
    }
}

impl Display for FormDataValue {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            FormDataValue::Text(value) => f.write_str(value),
            FormDataValue::File(path) => writeln!(f, "@{path}"),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    #[ignore]
    fn display() {
        let content = r#"
        {
          "key": "value"
        }
        "#
        .to_string();
        let body = Body::Raw {
            content,
            mime: mime::TEXT_PLAIN,
        };
        println!("{body}");

        let body = Body::Binary {
            path: "path/to/file".to_string(),
            mime: mime::STAR_STAR,
        };
        println!("{body}");

        let mut map = HashMap::new();
        map.insert("text".to_string(), FormDataValue::Text("value".to_string()));
        map.insert("file".to_string(), FormDataValue::File("path".to_string()));
        let body = Body::FormData(map);
        println!("{body}");

        let mut map = HashMap::new();
        map.insert("key".to_string(), "val".to_string());
        map.insert("other".to_string(), "val".to_string());
        let body = Body::XFormUrlEncoded(map);
        println!("{body}");
    }
}