use std::path::Path;
use bytes::Bytes;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UploadFile {
filename: String,
data: Bytes,
content_type: Option<String>,
}
impl UploadFile {
pub fn new(filename: impl Into<String>, data: impl Into<Bytes>) -> Self {
UploadFile {
filename: filename.into(),
data: data.into(),
content_type: None,
}
}
pub fn from_path(path: impl AsRef<Path>) -> std::io::Result<Self> {
let path = path.as_ref();
let data = std::fs::read(path)?;
Ok(UploadFile::new(file_name(path), data))
}
pub async fn from_path_async(path: impl AsRef<Path>) -> std::io::Result<Self> {
let path = path.as_ref();
let data = tokio::fs::read(path).await?;
Ok(UploadFile::new(file_name(path), data))
}
pub fn with_content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = Some(content_type.into());
self
}
pub fn filename(&self) -> &str {
&self.filename
}
pub fn data(&self) -> &Bytes {
&self.data
}
pub fn content_type(&self) -> &str {
if let Some(ct) = &self.content_type {
return ct;
}
let ext = self
.filename
.rsplit_once('.')
.map(|(_, ext)| ext.to_ascii_lowercase());
match ext.as_deref() {
Some("pdf") => "application/pdf",
Some("png") => "image/png",
Some("jpg" | "jpeg") => "image/jpeg",
_ => "application/octet-stream",
}
}
}
fn file_name(path: &Path) -> String {
path.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "upload".to_string())
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct FileDownload {
pub bytes: Bytes,
pub content_type: Option<String>,
}
impl FileDownload {
pub fn into_bytes(self) -> Bytes {
self.bytes
}
}