use bytes::Bytes;
use std::borrow::Cow;
#[derive(Debug, Clone)]
pub enum Body {
Empty,
Bytes {
content: Bytes,
content_type: String,
},
Form {
fields: Vec<(Cow<'static, str>, Cow<'static, str>)>,
},
#[cfg(feature = "multipart")]
Multipart {
parts: Vec<MultipartPart>,
},
#[cfg(feature = "json")]
Json {
value: serde_json::Value,
},
}
#[cfg(feature = "multipart")]
#[derive(Debug, Clone)]
pub struct MultipartPart {
pub name: String,
pub content: Bytes,
pub content_type: Option<String>,
pub filename: Option<String>,
}
impl Body {
pub fn empty() -> Self {
Self::Empty
}
pub fn bytes(content: impl Into<Bytes>, content_type: impl Into<String>) -> Self {
Self::Bytes {
content: content.into(),
content_type: content_type.into(),
}
}
pub fn text(content: impl Into<String>) -> Self {
Self::Bytes {
content: content.into().into(),
content_type: "text/plain; charset=utf-8".to_string(),
}
}
pub fn form(fields: Vec<(impl Into<Cow<'static, str>>, impl Into<Cow<'static, str>>)>) -> Self {
Self::Form {
fields: fields
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
}
}
#[cfg(feature = "json")]
pub fn json(value: impl serde::Serialize) -> Result<Self, crate::Error> {
Ok(Self::Json {
value: serde_json::to_value(value)?,
})
}
#[cfg(feature = "multipart")]
pub fn multipart(parts: Vec<MultipartPart>) -> Self {
Self::Multipart { parts }
}
pub async fn from_file<P: AsRef<std::path::Path>>(
path: P,
content_type: Option<String>,
) -> Result<Self, crate::Error> {
let content = tokio::fs::read(path).await?;
let content_type = content_type.unwrap_or_else(|| "application/octet-stream".to_string());
Ok(Self::Bytes {
content: content.into(),
content_type,
})
}
}
impl From<String> for Body {
fn from(content: String) -> Self {
Self::text(content)
}
}
impl From<&str> for Body {
fn from(content: &str) -> Self {
Self::text(content)
}
}
impl From<Vec<u8>> for Body {
fn from(content: Vec<u8>) -> Self {
Self::bytes(content, "application/octet-stream")
}
}
impl From<&[u8]> for Body {
fn from(content: &[u8]) -> Self {
Self::bytes(content.to_vec(), "application/octet-stream")
}
}
impl From<Bytes> for Body {
fn from(content: Bytes) -> Self {
Self::bytes(content, "application/octet-stream")
}
}
#[cfg(feature = "json")]
impl From<serde_json::Value> for Body {
fn from(value: serde_json::Value) -> Self {
Self::Json { value }
}
}
#[cfg(feature = "multipart")]
impl MultipartPart {
pub fn text(name: impl Into<String>, content: impl Into<String>) -> Self {
Self {
name: name.into(),
content: content.into().into(),
content_type: Some("text/plain; charset=utf-8".to_string()),
filename: None,
}
}
pub fn file(
name: impl Into<String>,
content: impl Into<Bytes>,
filename: impl Into<String>,
content_type: Option<String>,
) -> Self {
Self {
name: name.into(),
content: content.into(),
content_type,
filename: Some(filename.into()),
}
}
pub async fn from_file<P: AsRef<std::path::Path>>(
name: impl Into<String>,
path: P,
content_type: Option<String>,
) -> Result<Self, crate::Error> {
let content = tokio::fs::read(&path).await?;
let filename = path
.as_ref()
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("file")
.to_string();
Ok(Self {
name: name.into(),
content: content.into(),
content_type,
filename: Some(filename),
})
}
}