use std::fmt;
use std::str::FromStr;
use crate::error::{OssError, OssErrorKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HttpMethod {
Get,
Put,
Post,
Delete,
Head,
Options,
}
impl HttpMethod {
pub fn as_str(self) -> &'static str {
match self {
Self::Get => "GET",
Self::Put => "PUT",
Self::Post => "POST",
Self::Delete => "DELETE",
Self::Head => "HEAD",
Self::Options => "OPTIONS",
}
}
}
impl FromStr for HttpMethod {
type Err = OssError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"GET" => Ok(Self::Get),
"PUT" => Ok(Self::Put),
"POST" => Ok(Self::Post),
"DELETE" => Ok(Self::Delete),
"HEAD" => Ok(Self::Head),
"OPTIONS" => Ok(Self::Options),
other => Err(OssError {
kind: OssErrorKind::ValidationError,
context: Box::new(crate::error::ErrorContext {
operation: Some(format!("parse HttpMethod from '{}'", other)),
..Default::default()
}),
source: None,
}),
}
}
}
impl fmt::Display for HttpMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub struct HttpHeaders {
pub(crate) inner: http::HeaderMap,
}
impl HttpHeaders {
pub fn new() -> Self {
Self {
inner: http::HeaderMap::new(),
}
}
pub fn insert<K, V>(&mut self, key: K, value: V) -> Option<http::HeaderValue>
where
K: TryInto<http::HeaderName>,
K::Error: fmt::Debug,
V: TryInto<http::HeaderValue>,
V::Error: fmt::Debug,
{
let name = key.try_into().expect("valid header name");
let val = value.try_into().expect("valid header value");
self.inner.insert(name, val)
}
pub fn get(&self, key: impl AsRef<str>) -> Option<&http::HeaderValue> {
self.inner.get(key.as_ref())
}
pub fn contains_key(&self, key: impl AsRef<str>) -> bool {
self.inner.contains_key(key.as_ref())
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl Default for HttpHeaders {
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for HttpHeaders {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.inner.iter().map(|(k, v)| (k.as_str(), v)))
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContentType {
inner: String,
}
impl ContentType {
pub const TEXT_PLAIN: &'static str = "text/plain";
pub const TEXT_HTML: &'static str = "text/html";
pub const TEXT_CSS: &'static str = "text/css";
pub const TEXT_JAVASCRIPT: &'static str = "text/javascript";
pub const APPLICATION_JSON: &'static str = "application/json";
pub const APPLICATION_XML: &'static str = "application/xml";
pub const APPLICATION_OCTET_STREAM: &'static str = "application/octet-stream";
pub const APPLICATION_PDF: &'static str = "application/pdf";
pub const IMAGE_JPEG: &'static str = "image/jpeg";
pub const IMAGE_PNG: &'static str = "image/png";
pub const IMAGE_GIF: &'static str = "image/gif";
pub const IMAGE_WEBP: &'static str = "image/webp";
pub const IMAGE_SVG: &'static str = "image/svg+xml";
pub const VIDEO_MP4: &'static str = "video/mp4";
pub const AUDIO_MPEG: &'static str = "audio/mpeg";
pub fn from_extension(ext: &str) -> Self {
let mime = match ext.to_lowercase().as_str() {
"html" | "htm" => Self::TEXT_HTML,
"css" => Self::TEXT_CSS,
"js" | "mjs" => Self::TEXT_JAVASCRIPT,
"json" => Self::APPLICATION_JSON,
"xml" => Self::APPLICATION_XML,
"pdf" => Self::APPLICATION_PDF,
"jpg" | "jpeg" => Self::IMAGE_JPEG,
"png" => Self::IMAGE_PNG,
"gif" => Self::IMAGE_GIF,
"webp" => Self::IMAGE_WEBP,
"svg" => Self::IMAGE_SVG,
"mp4" => Self::VIDEO_MP4,
"mp3" => Self::AUDIO_MPEG,
"txt" | "text" => Self::TEXT_PLAIN,
_ => Self::APPLICATION_OCTET_STREAM,
};
ContentType {
inner: mime.to_string(),
}
}
pub fn as_str(&self) -> &str {
&self.inner
}
}
impl Default for ContentType {
fn default() -> Self {
ContentType {
inner: Self::APPLICATION_OCTET_STREAM.to_string(),
}
}
}
impl From<&str> for ContentType {
fn from(s: &str) -> Self {
ContentType {
inner: s.to_string(),
}
}
}
impl From<String> for ContentType {
fn from(s: String) -> Self {
ContentType { inner: s }
}
}
impl fmt::Display for ContentType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn http_method_converts_to_str_correctly() {
assert_eq!(HttpMethod::Get.as_str(), "GET");
assert_eq!(HttpMethod::Put.as_str(), "PUT");
assert_eq!(HttpMethod::Post.as_str(), "POST");
assert_eq!(HttpMethod::Delete.as_str(), "DELETE");
assert_eq!(HttpMethod::Head.as_str(), "HEAD");
assert_eq!(HttpMethod::Options.as_str(), "OPTIONS");
}
#[test]
fn http_method_from_str_case_sensitive() {
assert_eq!("GET".parse::<HttpMethod>().unwrap(), HttpMethod::Get);
assert!("get".parse::<HttpMethod>().is_err());
assert!("Put".parse::<HttpMethod>().is_err());
}
#[test]
fn http_method_display_matches_as_str() {
assert_eq!(HttpMethod::Put.to_string(), "PUT");
assert_eq!(HttpMethod::Head.to_string(), "HEAD");
}
#[test]
fn http_headers_case_insensitive_key() {
let mut headers = HttpHeaders::new();
headers.insert("content-type", "text/plain");
assert_eq!(
headers.get("Content-Type").unwrap().to_str().unwrap(),
"text/plain"
);
assert_eq!(
headers.get("CONTENT-TYPE").unwrap().to_str().unwrap(),
"text/plain"
);
assert_eq!(
headers.get("content-type").unwrap().to_str().unwrap(),
"text/plain"
);
}
#[test]
fn http_headers_insert_and_get() {
let mut headers = HttpHeaders::new();
headers.insert("x-oss-request-id", "abc123");
assert_eq!(
headers.get("x-oss-request-id").unwrap().to_str().unwrap(),
"abc123"
);
assert!(headers.contains_key("x-oss-request-id"));
assert!(!headers.contains_key("x-oss-nonexistent"));
}
#[test]
fn content_type_from_file_extension() {
assert_eq!(
ContentType::from_extension("json").as_str(),
"application/json"
);
assert_eq!(
ContentType::from_extension("xml").as_str(),
"application/xml"
);
assert_eq!(ContentType::from_extension("jpg").as_str(), "image/jpeg");
assert_eq!(ContentType::from_extension("jpeg").as_str(), "image/jpeg");
assert_eq!(ContentType::from_extension("png").as_str(), "image/png");
assert_eq!(ContentType::from_extension("html").as_str(), "text/html");
assert_eq!(ContentType::from_extension("txt").as_str(), "text/plain");
}
#[test]
fn content_type_from_unknown_extension_defaults_to_octet_stream() {
assert_eq!(
ContentType::from_extension("xyz").as_str(),
"application/octet-stream"
);
}
#[test]
fn content_type_default_is_application_octet_stream() {
assert_eq!(ContentType::default().as_str(), "application/octet-stream");
}
#[test]
fn content_type_from_str() {
let ct: ContentType = "image/webp".into();
assert_eq!(ct.as_str(), "image/webp");
}
#[test]
fn content_type_case_insensitive_extension() {
assert_eq!(
ContentType::from_extension("JSON").as_str(),
"application/json"
);
assert_eq!(ContentType::from_extension("Jpg").as_str(), "image/jpeg");
}
}