use std::fs;
use std::path::PathBuf;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UploadedFile {
pub name: String,
pub size: u64,
pub content_type: String,
pub content: Vec<u8>,
}
impl UploadedFile {
#[must_use]
pub fn new(name: impl Into<String>, content_type: impl Into<String>, content: Vec<u8>) -> Self {
Self {
name: name.into(),
size: content.len() as u64,
content_type: content_type.into(),
content,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InMemoryUploadedFile {
pub file: UploadedFile,
}
impl InMemoryUploadedFile {
#[must_use]
pub fn new(name: impl Into<String>, content_type: impl Into<String>, content: Vec<u8>) -> Self {
Self {
file: UploadedFile::new(name, content_type, content),
}
}
#[must_use]
pub fn into_uploaded_file(self) -> UploadedFile {
self.file
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TemporaryUploadedFile {
pub path: PathBuf,
pub name: String,
pub size: u64,
pub content_type: String,
}
impl TemporaryUploadedFile {
#[must_use]
pub fn new(
path: PathBuf,
name: impl Into<String>,
content_type: impl Into<String>,
size: u64,
) -> Self {
Self {
path,
name: name.into(),
size,
content_type: content_type.into(),
}
}
pub fn read(&self) -> std::io::Result<Vec<u8>> {
fs::read(&self.path)
}
}
#[cfg(test)]
mod tests {
use std::fs;
use std::path::{Path, PathBuf};
use std::process;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
use super::{InMemoryUploadedFile, TemporaryUploadedFile, UploadedFile};
static UNIQUE_COUNTER: AtomicU64 = AtomicU64::new(0);
struct TestDir {
path: PathBuf,
}
impl TestDir {
fn new() -> Self {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("clock should be after epoch")
.as_nanos();
let counter = UNIQUE_COUNTER.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"rjango-uploaded-file-test-{}-{nanos}-{counter}",
process::id()
));
fs::create_dir_all(&path).expect("create test directory");
Self { path }
}
fn path(&self) -> &Path {
&self.path
}
}
impl Drop for TestDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.path);
}
}
#[test]
fn uploaded_file_new_sets_metadata() {
let uploaded = UploadedFile::new("avatar.png", "image/png", vec![1, 2, 3, 4]);
assert_eq!(uploaded.name, "avatar.png");
assert_eq!(uploaded.size, 4);
assert_eq!(uploaded.content_type, "image/png");
assert_eq!(uploaded.content, vec![1, 2, 3, 4]);
}
#[test]
fn in_memory_uploaded_file_wraps_uploaded_file() {
let uploaded = InMemoryUploadedFile::new("notes.txt", "text/plain", b"hi".to_vec())
.into_uploaded_file();
assert_eq!(uploaded.name, "notes.txt");
assert_eq!(uploaded.size, 2);
assert_eq!(uploaded.content_type, "text/plain");
}
#[test]
fn temporary_uploaded_file_reads_from_disk() {
let dir = TestDir::new();
let path = dir.path().join("upload.bin");
fs::write(&path, [9, 8, 7]).expect("write upload file");
let uploaded =
TemporaryUploadedFile::new(path.clone(), "upload.bin", "application/octet-stream", 3);
assert_eq!(uploaded.path, path);
assert_eq!(uploaded.read().expect("read upload file"), vec![9, 8, 7]);
}
}