Skip to main content

oxihttp_core/
form.rs

1//! URL-encoded form body builder.
2
3use bytes::Bytes;
4use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
5
6/// Builder for URL-encoded form bodies (`application/x-www-form-urlencoded`).
7#[derive(Debug, Clone, Default)]
8pub struct FormBody {
9    fields: Vec<(String, String)>,
10}
11
12impl FormBody {
13    /// Create a new empty form builder.
14    pub fn new() -> Self {
15        Self::default()
16    }
17
18    /// Add a field to the form.
19    pub fn field(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
20        self.fields.push((name.into(), value.into()));
21        self
22    }
23
24    /// Build the form body as raw bytes.
25    pub fn build(self) -> Bytes {
26        let encoded: String = self
27            .fields
28            .iter()
29            .map(|(k, v)| {
30                format!(
31                    "{}={}",
32                    utf8_percent_encode(k, NON_ALPHANUMERIC),
33                    utf8_percent_encode(v, NON_ALPHANUMERIC)
34                )
35            })
36            .collect::<Vec<_>>()
37            .join("&");
38        Bytes::from(encoded)
39    }
40
41    /// Returns the number of fields.
42    pub fn len(&self) -> usize {
43        self.fields.len()
44    }
45
46    /// Returns `true` if no fields have been added.
47    pub fn is_empty(&self) -> bool {
48        self.fields.is_empty()
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn test_empty_form() {
58        let body = FormBody::new().build();
59        assert!(body.is_empty());
60    }
61
62    #[test]
63    fn test_single_field() {
64        let body = FormBody::new().field("key", "value").build();
65        assert_eq!(&body[..], b"key=value");
66    }
67
68    #[test]
69    fn test_multiple_fields() {
70        let body = FormBody::new().field("a", "1").field("b", "2").build();
71        assert_eq!(&body[..], b"a=1&b=2");
72    }
73
74    #[test]
75    fn test_special_characters_encoded() {
76        let body = FormBody::new()
77            .field("query", "hello world")
78            .field("path", "/foo/bar")
79            .build();
80        let s = std::str::from_utf8(&body).expect("valid utf-8");
81        assert!(s.contains("hello%20world"));
82        assert!(s.contains("%2Ffoo%2Fbar"));
83    }
84
85    #[test]
86    fn test_len() {
87        let form = FormBody::new().field("a", "1").field("b", "2");
88        assert_eq!(form.len(), 2);
89        assert!(!form.is_empty());
90    }
91}