use std::path::{Path, PathBuf};
use crate::api::auth_matrix::WireScheme;
use crate::error::{Error, Result};
use super::media::{MediaUploadOutcome, execute_media_upload};
use super::request::Client;
impl Client {
pub fn upload_media(&self, path: impl Into<PathBuf>) -> MediaUpload {
MediaUpload {
client: self.clone(),
path: path.into(),
media_type: None,
category: None,
wait_for_processing: true,
auth_type: String::new(),
username: String::new(),
trace: false,
headers: Vec::new(),
}
}
}
#[must_use = "a MediaUpload does nothing until it is sent"]
pub struct MediaUpload {
client: Client,
path: PathBuf,
media_type: Option<String>,
category: Option<String>,
wait_for_processing: bool,
auth_type: String,
username: String,
trace: bool,
headers: Vec<String>,
}
crate::assert_send_sync!(MediaUpload);
impl std::fmt::Debug for MediaUpload {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MediaUpload")
.field("path", &self.path)
.field("media_type", &self.media_type)
.field("category", &self.category)
.field("wait_for_processing", &self.wait_for_processing)
.finish_non_exhaustive()
}
}
impl MediaUpload {
pub fn media_type(mut self, media_type: impl Into<String>) -> Self {
self.media_type = Some(media_type.into());
self
}
pub fn category(mut self, category: impl Into<String>) -> Self {
self.category = Some(category.into());
self
}
pub fn wait_for_processing(mut self, wait: bool) -> Self {
self.wait_for_processing = wait;
self
}
pub fn auth(mut self, scheme: WireScheme) -> Self {
self.auth_type = scheme.as_wire().to_string();
self
}
pub fn username(mut self, username: impl Into<String>) -> Self {
self.username = username.into();
self
}
pub fn trace(mut self, on: bool) -> Self {
self.trace = on;
self
}
pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
self.headers
.push(format!("{}: {}", name.into(), value.into()));
self
}
pub async fn send(self) -> Result<MediaUploadOutcome> {
let media_type = match self.media_type {
Some(media_type) => media_type,
None => media_type_for(&self.path)?.to_string(),
};
let category = self
.category
.unwrap_or_else(|| category_for(&media_type).to_string());
let path = self.path.to_str().ok_or_else(|| {
Error::validation(format!("{} is not a valid UTF-8 path", self.path.display()))
})?;
execute_media_upload(
path,
&media_type,
&category,
&self.auth_type,
&self.username,
self.trace,
self.wait_for_processing,
&self.headers,
&self.client,
)
.await
}
}
fn media_type_for(path: &Path) -> Result<&'static str> {
let extension = path
.extension()
.and_then(|ext| ext.to_str())
.map(str::to_ascii_lowercase);
match extension.as_deref() {
Some("png") => Ok("image/png"),
Some("jpg" | "jpeg") => Ok("image/jpeg"),
Some("gif") => Ok("image/gif"),
Some("webp") => Ok("image/webp"),
Some("mp4" | "m4v") => Ok("video/mp4"),
Some("mov") => Ok("video/quicktime"),
_ => Err(Error::validation(format!(
"cannot infer the media type of {}; set it with MediaUpload::media_type",
path.display()
))),
}
}
fn category_for(media_type: &str) -> &'static str {
if media_type == "image/gif" {
"tweet_gif"
} else if media_type.starts_with("video/") {
"tweet_video"
} else {
"tweet_image"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn media_type_follows_the_extension_case_insensitively() {
assert_eq!(media_type_for(Path::new("a.PNG")).unwrap(), "image/png");
assert_eq!(media_type_for(Path::new("a.jpeg")).unwrap(), "image/jpeg");
assert_eq!(media_type_for(Path::new("a.jpg")).unwrap(), "image/jpeg");
assert_eq!(media_type_for(Path::new("a.gif")).unwrap(), "image/gif");
assert_eq!(media_type_for(Path::new("a.webp")).unwrap(), "image/webp");
assert_eq!(media_type_for(Path::new("a.mp4")).unwrap(), "video/mp4");
assert_eq!(media_type_for(Path::new("a.m4v")).unwrap(), "video/mp4");
assert_eq!(
media_type_for(Path::new("a.MOV")).unwrap(),
"video/quicktime"
);
}
#[test]
fn an_unknown_or_missing_extension_is_a_validation_error_naming_the_setter() {
for name in ["notes.txt", "archive.tar.gz", "noext", ".hidden"] {
let err = media_type_for(Path::new(name)).unwrap_err();
assert!(err.is_validation(), "{name}: {err:?}");
assert!(
err.to_string().contains("MediaUpload::media_type"),
"{name}: {err}"
);
}
}
#[test]
fn category_follows_the_media_type() {
assert_eq!(category_for("image/gif"), "tweet_gif");
assert_eq!(category_for("video/mp4"), "tweet_video");
assert_eq!(category_for("video/quicktime"), "tweet_video");
assert_eq!(category_for("image/png"), "tweet_image");
assert_eq!(category_for("image/webp"), "tweet_image");
}
}