use bytes::Bytes;
use mime::Mime;
#[derive(Debug, Clone, Default)]
pub(crate) enum Body {
#[default]
Empty,
Raw {
data: Bytes,
content_type: Option<Mime>,
},
Form(String),
Multipart(Multipart),
}
#[derive(Debug, Clone, Default)]
pub struct Multipart {
pub(crate) parts: Vec<Part>,
}
#[derive(Debug, Clone)]
pub struct Part {
pub(crate) name: String,
pub(crate) data: Bytes,
pub(crate) content_type: Option<Mime>,
pub(crate) filename: Option<String>,
}
impl Multipart {
pub fn new() -> Self {
Self::default()
}
pub fn text(self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.part(Part {
name: name.into(),
data: Bytes::from(value.into()),
content_type: None,
filename: None,
})
}
pub fn bytes(
self,
name: impl Into<String>,
content_type: Mime,
data: impl Into<Bytes>,
) -> Self {
self.part(Part {
name: name.into(),
data: data.into(),
content_type: Some(content_type),
filename: None,
})
}
pub fn file(
self,
name: impl Into<String>,
filename: impl Into<String>,
content_type: Mime,
data: impl Into<Bytes>,
) -> Self {
self.part(Part {
name: name.into(),
data: data.into(),
content_type: Some(content_type),
filename: Some(filename.into()),
})
}
fn part(mut self, part: Part) -> Self {
self.parts.push(part);
self
}
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
pub fn len(&self) -> usize {
self.parts.len()
}
}
pub(crate) fn encode_form<'a>(pairs: impl IntoIterator<Item = (&'a str, &'a str)>) -> String {
let mut out = String::new();
for (name, value) in pairs {
if !out.is_empty() {
out.push('&');
}
out.extend(form_urlencoded::byte_serialize(name.as_bytes()));
if !value.is_empty() {
out.push('=');
out.extend(form_urlencoded::byte_serialize(value.as_bytes()));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn form_encoding() {
assert_eq!(encode_form([("a", "b"), ("c", "d")]), "a=b&c=d");
assert_eq!(
encode_form([("flag", ""), ("x y", "=&")]),
"flag&x+y=%3D%26"
);
assert_eq!(encode_form([]), "");
}
}