1use bytes::Bytes;
4use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
5
6#[derive(Debug, Clone, Default)]
8pub struct FormBody {
9 fields: Vec<(String, String)>,
10}
11
12impl FormBody {
13 pub fn new() -> Self {
15 Self::default()
16 }
17
18 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 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 pub fn len(&self) -> usize {
43 self.fields.len()
44 }
45
46 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}