use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::error::SendraError;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct MultipartPart {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
}
pub(crate) fn read_body_file(base_dir: &Path, path: &str) -> Result<String, SendraError> {
let full_path = base_dir.join(path);
std::fs::read_to_string(&full_path).map_err(|source| SendraError::BodyFileIo {
path: full_path,
source,
})
}
pub(crate) fn encode_multipart(
parts: &[MultipartPart],
base_dir: &Path,
) -> Result<(String, String), SendraError> {
let boundary = format!(
"----sendra-{:x}",
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
);
let mut body = String::new();
for part in parts {
body.push_str("--");
body.push_str(&boundary);
body.push_str("\r\n");
match (&part.value, &part.path) {
(Some(value), None) => {
body.push_str(&format!(
"Content-Disposition: form-data; name=\"{}\"\r\n\r\n",
part.name
));
body.push_str(value);
}
(None, Some(path)) => {
let filename = Path::new(path)
.file_name()
.and_then(|name| name.to_str())
.unwrap_or(path);
body.push_str(&format!(
"Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n\r\n",
part.name, filename
));
body.push_str(&read_body_file(base_dir, path)?);
}
(Some(_), Some(_)) | (None, None) => {}
}
body.push_str("\r\n");
}
body.push_str("--");
body.push_str(&boundary);
body.push_str("--\r\n");
let content_type = format!("multipart/form-data; boundary={boundary}");
Ok((body, content_type))
}