use bytes::Bytes;
use reqwest::multipart::{Form, Part as ReqwestPart};
use serde::Serialize;
use crate::error::{Error, Result};
#[derive(Default)]
#[must_use]
pub struct Multipart {
fields: Vec<(String, String)>,
parts: Vec<(String, Part)>,
error: Option<String>,
}
impl Multipart {
pub fn new() -> Self {
Self::default()
}
pub fn field<T>(mut self, name: impl Into<String>, value: &T) -> Self
where
T: Serialize + ?Sized,
{
if self.error.is_none() {
match serde_json::to_string(value) {
Ok(json) => self.fields.push((name.into(), json)),
Err(e) => self.error = Some(format!("field {:?}: {e}", name.into())),
}
}
self
}
pub fn part(mut self, name: impl Into<String>, part: Part) -> Self {
self.parts.push((name.into(), part));
self
}
pub(crate) fn into_form(self) -> Result<Form> {
if let Some(error) = self.error {
return Err(Error::invalid_message(format!(
"invalid multipart body: {error}"
)));
}
let mut form = Form::new();
for (name, value) in self.fields {
form = form.text(name, value);
}
for (name, part) in self.parts {
form = form.part(name, part.into_reqwest()?);
}
Ok(form)
}
}
impl std::fmt::Debug for Multipart {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Multipart")
.field("fields", &self.fields.len())
.field("parts", &self.parts.len())
.field("error", &self.error)
.finish()
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct Part {
bytes: Bytes,
file_name: Option<String>,
mime: Option<String>,
}
impl Part {
pub fn new(bytes: impl Into<Bytes>) -> Self {
Self {
bytes: bytes.into(),
file_name: None,
mime: None,
}
}
pub fn file_name(mut self, file_name: impl Into<String>) -> Self {
self.file_name = Some(file_name.into());
self
}
pub fn mime(mut self, mime: impl Into<String>) -> Self {
self.mime = Some(mime.into());
self
}
fn into_reqwest(self) -> Result<ReqwestPart> {
let mut part = ReqwestPart::bytes(self.bytes.to_vec());
if let Some(file_name) = self.file_name {
part = part.file_name(file_name);
}
if let Some(mime) = self.mime {
part = part
.mime_str(&mime)
.map_err(|e| Error::invalid_request(format!("invalid part mime {mime:?}"), e))?;
}
Ok(part)
}
}