use crate::internals::ErrorMessage;
use bytes::Bytes;
use http::HeaderName;
use http::HeaderValue;
use mime::Mime;
use std::fmt::Debug;
use std::fmt::Display;
#[derive(Debug, Clone)]
pub struct Part {
pub(crate) bytes: Bytes,
pub(crate) file_name: Option<String>,
pub(crate) mime_type: Mime,
pub(crate) headers: Vec<(HeaderName, HeaderValue)>,
}
impl Part {
pub fn text<T>(text: T) -> Self
where
T: Display,
{
let bytes = text.to_string().into_bytes().into();
Self::new(bytes, mime::TEXT_PLAIN)
}
pub fn bytes<B>(bytes: B) -> Self
where
B: Into<Bytes>,
{
Self::new(bytes.into(), mime::APPLICATION_OCTET_STREAM)
}
fn new(bytes: Bytes, mime_type: Mime) -> Self {
Self {
bytes,
file_name: None,
mime_type,
headers: Default::default(),
}
}
pub fn file_name<T>(mut self, file_name: T) -> Self
where
T: Display,
{
self.file_name = Some(file_name.to_string());
self
}
pub fn mime_type<M>(mut self, mime_type: M) -> Self
where
M: AsRef<str>,
{
let raw_mime_type = mime_type.as_ref();
let parsed_mime_type = raw_mime_type
.parse()
.error_message_fn(|| format!("Failed to parse '{raw_mime_type}' as a Mime type"));
self.mime_type = parsed_mime_type;
self
}
pub fn add_header<N, V>(mut self, name: N, value: V) -> Self
where
N: TryInto<HeaderName>,
N::Error: Debug,
V: TryInto<HeaderValue>,
V::Error: Debug,
{
let header_name: HeaderName = name
.try_into()
.expect("Failed to convert header name to HeaderName");
let header_value: HeaderValue = value
.try_into()
.expect("Failed to convert header vlue to HeaderValue");
self.headers.push((header_name, header_value));
self
}
}
#[cfg(test)]
mod test_text {
use super::*;
#[test]
fn it_should_contain_text_given() {
let part = Part::text("some_text");
let output = String::from_utf8_lossy(&part.bytes);
assert_eq!(output, "some_text");
}
#[test]
fn it_should_use_mime_type_text() {
let part = Part::text("some_text");
assert_eq!(part.mime_type, mime::TEXT_PLAIN);
}
}
#[cfg(test)]
mod test_byes {
use super::*;
#[test]
fn it_should_contain_bytes_given() {
let bytes = "some_text".as_bytes();
let part = Part::bytes(bytes);
let output = String::from_utf8_lossy(&part.bytes);
assert_eq!(output, "some_text");
}
#[test]
fn it_should_use_mime_type_octet_stream() {
let bytes = "some_text".as_bytes();
let part = Part::bytes(bytes);
assert_eq!(part.mime_type, mime::APPLICATION_OCTET_STREAM);
}
}
#[cfg(test)]
mod test_file_name {
use super::*;
#[test]
fn it_should_use_file_name_given() {
let mut part = Part::text("some_text");
assert_eq!(part.file_name, None);
part = part.file_name("my-text.txt");
assert_eq!(part.file_name, Some("my-text.txt".to_string()));
}
}
#[cfg(test)]
mod test_mime_type {
use super::*;
use crate::testing::catch_panic_error_message;
use pretty_assertions::assert_str_eq;
#[test]
fn it_should_use_mime_type_set() {
let mut part = Part::text("some_text");
assert_eq!(part.mime_type, mime::TEXT_PLAIN);
part = part.mime_type("application/json");
assert_eq!(part.mime_type, mime::APPLICATION_JSON);
}
#[test]
fn it_should_error_if_invalid_mime_type() {
let part = Part::text("some_text");
let message = catch_panic_error_message(|| {
part.mime_type("🦊");
});
assert_str_eq!(
"Failed to parse '🦊' as a Mime type,
mime parse error: an invalid token was encountered, F0 at position 0
",
message
);
}
}