use super::result::{Result, TelegramError};
use serde_json::{Map, Value};
use std::{
fs::File,
io::{Read, Write},
path::Path,
};
#[derive(Debug, Clone, PartialEq)]
pub struct FormDataFile {
pub bytes: Vec<u8>,
pub name: String,
pub file_name: Option<String>,
pub media_type: Option<String>,
}
impl FormDataFile {
pub fn new(bytes: &[u8], media_type: &str, file_name: &str) -> Self {
Self {
bytes: bytes.to_vec(),
name: file_name
.splitn(2, '.')
.collect::<Vec<&str>>()
.first()
.unwrap_or(&"new_file")
.to_owned()
.to_owned(),
media_type: Some(media_type.to_owned()),
file_name: Some(file_name.to_owned()),
}
}
pub fn new_from_file(file: &mut File, file_name: &str) -> Result<Self> {
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)?;
Ok(Self {
bytes,
name: file_name
.splitn(2, '.')
.collect::<Vec<&str>>()
.first()
.unwrap_or(&"new_file")
.to_owned()
.to_owned(),
file_name: Some(file_name.to_owned()),
media_type: Some(get_media_type(file_name)?.to_owned()),
})
}
}
pub static BOUNDARY: &str = "----------telexide-form-data-boundary";
pub fn encode_multipart_form_data(files: &[FormDataFile]) -> Result<Vec<u8>> {
let mut data = Vec::new();
for file in files {
write!(&mut data, "--{}\r\n", BOUNDARY)?;
if file.file_name.is_some() {
write!(
&mut data,
"Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n",
file.name,
file.file_name.as_ref().unwrap()
)?;
} else {
write!(
&mut data,
"Content-Disposition: form-data; name=\"{}\"\r\n",
file.name
)?;
}
if file.media_type.is_some() {
write!(
&mut data,
"Content-Type: {}\r\n",
file.media_type.as_ref().unwrap()
)?;
}
write!(&mut data, "\r\n")?;
file.bytes.as_slice().read_to_end(&mut data)?;
write!(&mut data, "\r\n")?;
}
write!(&mut data, "--{}--\r\n", BOUNDARY)?;
Ok(data)
}
pub fn encode_file_as_multipart_form_data(mut file: &mut File, file_name: &str) -> Result<Vec<u8>> {
encode_multipart_form_data(&[FormDataFile::new_from_file(&mut file, file_name)?])
}
fn get_media_type(file_name: &str) -> Result<&str> {
let ext: &str = if let Some(ext) = Path::new(file_name).extension() {
ext.to_str().ok_or_else(|| {
TelegramError::InvalidArgument("file name contained invalid characters".to_owned())
})?
} else {
""
};
Ok(match ext {
"png" => "image/png",
"gif" => "image/gif",
"jpg" | "jpeg" => "image/jpeg",
"csv" => "text/csv",
"mp4" => "video/mp4",
"webm" => "video/webm",
"json" => "application/json",
"pdf" => "application/pdf",
"zip" => "application/zip",
"m4a" => "audio/mp4",
"mp3" => "audio/mpeg",
"ogg" => "audio/ogg",
"webp" => "image/webp",
"tgs" => "application/gzip",
_ => "text/plain",
})
}
pub trait AsFormData {
fn as_form_data(&self) -> Result<Vec<FormDataFile>>;
}
impl AsFormData for Value {
fn as_form_data(&self) -> Result<Vec<FormDataFile>> {
let map: Map<String, Value> = self
.as_object()
.ok_or_else(|| {
TelegramError::InvalidArgument(
"Toplevel part of form-data has to be a struct".into(),
)
})?
.clone();
let mut res = Vec::new();
for (key, value) in map {
if value.is_null() {
continue;
}
res.push(FormDataFile {
name: key,
file_name: None,
media_type: None,
bytes: serde_json::to_string(&value)?
.trim_matches('"')
.as_bytes()
.to_vec(),
});
}
Ok(res)
}
}