Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

const utf8 mime #105

Merged
merged 2 commits into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions src/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ impl From<String> for Body {
Self {
length: Some(s.len()),
reader: Box::new(io::Cursor::new(s.into_bytes())),
mime: string_mime(),
mime: mime::PLAIN,
}
}
}
Expand All @@ -200,19 +200,11 @@ impl<'a> From<&'a str> for Body {
Self {
length: Some(s.len()),
reader: Box::new(io::Cursor::new(s.to_owned().into_bytes())),
mime: string_mime(),
mime: mime::PLAIN,
}
}
}

fn string_mime() -> mime::Mime {
let mut mime = mime::PLAIN;
let mut parameters = std::collections::HashMap::new();
parameters.insert("charset".to_owned(), "utf-8".to_owned());
mime.parameters = Some(parameters);
mime
}

impl From<Vec<u8>> for Body {
fn from(b: Vec<u8>) -> Self {
Self {
Expand Down
29 changes: 15 additions & 14 deletions src/mime/constants.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::ParamKind;
use crate::Mime;

/// Content-Type that matches anything.
Expand All @@ -11,7 +12,7 @@ pub const ANY: Mime = Mime {
essence: String::new(),
basetype: String::new(),
subtype: String::new(),
parameters: None,
params: None,
static_essence: Some("*/*"),
static_basetype: Some("*"),
static_subtype: Some("*"),
Expand All @@ -22,16 +23,16 @@ pub const ANY: Mime = Mime {
/// # Mime Type
///
/// ```txt
/// application/javascript
/// application/javascript; charset=utf-8
/// ```
pub const JAVASCRIPT: Mime = Mime {
static_essence: Some("application/javascript"),
essence: String::new(),
basetype: String::new(),
subtype: String::new(),
params: Some(ParamKind::Utf8),
static_basetype: Some("application"),
static_subtype: Some("javascript"),
parameters: None,
};

/// Content-Type for JSON.
Expand All @@ -46,43 +47,43 @@ pub const JSON: Mime = Mime {
essence: String::new(),
basetype: String::new(),
subtype: String::new(),
params: None,
static_basetype: Some("application"),
static_subtype: Some("json"),
parameters: None,
};

/// Content-Type for CSS.
///
/// # Mime Type
///
/// ```txt
/// text/css
/// text/css; charset=utf-8
/// ```
pub const CSS: Mime = Mime {
static_essence: Some("text/css"),
essence: String::new(),
basetype: String::new(),
subtype: String::new(),
params: Some(ParamKind::Utf8),
static_basetype: Some("text"),
static_subtype: Some("css"),
parameters: None,
};

/// Content-Type for HTML.
///
/// # Mime Type
///
/// ```txt
/// text/html
/// text/html; charset=utf-8
/// ```
pub const HTML: Mime = Mime {
static_essence: Some("text/html"),
essence: String::new(),
basetype: String::new(),
subtype: String::new(),
params: Some(ParamKind::Utf8),
static_basetype: Some("text"),
static_subtype: Some("html"),
parameters: None,
};

/// Content-Type for Server Sent Events
Expand All @@ -99,24 +100,24 @@ pub const SSE: Mime = Mime {
subtype: String::new(),
static_basetype: Some("text"),
static_subtype: Some("event-stream"),
parameters: None,
params: None,
};

/// Content-Type for plain text.
///
/// # Mime Type
///
/// ```txt
/// text/plain
/// text/plain; charset=utf-8
/// ```
pub const PLAIN: Mime = Mime {
static_essence: Some("text/plain"),
essence: String::new(),
basetype: String::new(),
subtype: String::new(),
params: Some(ParamKind::Utf8),
static_basetype: Some("text"),
static_subtype: Some("plain"),
parameters: None,
};

/// Content-Type for byte streams.
Expand All @@ -133,7 +134,7 @@ pub const BYTE_STREAM: Mime = Mime {
subtype: String::new(),
static_basetype: Some("application"),
static_subtype: Some("octet-stream"),
parameters: None,
params: None,
};

/// Content-Type for form.
Expand All @@ -150,7 +151,7 @@ pub const FORM: Mime = Mime {
subtype: String::new(),
static_basetype: Some("application"),
static_subtype: Some("x-www-form-urlencoded"),
parameters: None,
params: None,
};

/// Content-Type for a multipart form.
Expand All @@ -167,5 +168,5 @@ pub const MULTIPART_FORM: Mime = Mime {
subtype: String::new(),
static_basetype: Some("multipart"),
static_subtype: Some("form-data"),
parameters: None,
params: None,
};
111 changes: 94 additions & 17 deletions src/mime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod parse;

pub use constants::*;

use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{self, Debug, Display};
use std::option;
Expand All @@ -27,7 +28,7 @@ pub struct Mime {
pub(crate) static_essence: Option<&'static str>,
pub(crate) static_basetype: Option<&'static str>,
pub(crate) static_subtype: Option<&'static str>,
pub(crate) parameters: Option<HashMap<String, String>>,
pub(crate) params: Option<ParamKind>,
}

impl Mime {
Expand All @@ -46,7 +47,7 @@ impl Mime {
subtype: String::new(), // TODO: fill in.
static_basetype: None, // TODO: fill in
static_subtype: None,
parameters: None, // TODO: fill in.
params: None, // TODO: fill in.
})
}

Expand Down Expand Up @@ -81,13 +82,18 @@ impl Mime {
}

/// Get a reference to a param.
pub fn param(&self, s: &str) -> Option<&String> {
self.parameters.as_ref().map(|hm| hm.get(s)).flatten()
}

/// Get a mutable reference to a param.
pub fn param_mut(&mut self, s: &str) -> Option<&mut String> {
self.parameters.as_mut().map(|hm| hm.get_mut(s)).flatten()
pub fn param(&self, name: impl Into<ParamName>) -> Option<&ParamValue> {
let name: ParamName = name.into();
self.params
.as_ref()
.map(|inner| match inner {
ParamKind::Map(hm) => hm.get(&name),
ParamKind::Utf8 => match name {
ParamName(Cow::Borrowed("charset")) => Some(&ParamValue(Cow::Borrowed("utf8"))),
_ => None,
},
})
.flatten()
}
}

Expand All @@ -98,13 +104,18 @@ impl Display for Mime {
} else {
write!(f, "{}", &self.essence)?
}
if let Some(parameters) = &self.parameters {
assert!(!parameters.is_empty());
write!(f, "; ")?;
for (i, (key, value)) in parameters.iter().enumerate() {
write!(f, "{}={}", key, value)?;
if i != parameters.len() - 1 {
write!(f, ",")?;
if let Some(params) = &self.params {
match params {
ParamKind::Utf8 => write!(f, "; charset=utf-8")?,
ParamKind::Map(params) => {
assert!(!params.is_empty());
write!(f, "; ")?;
for (i, (key, value)) in params.iter().enumerate() {
write!(f, "{}={}", key, value)?;
if i != params.len() - 1 {
write!(f, ",")?;
}
}
}
}
}
Expand Down Expand Up @@ -137,7 +148,7 @@ impl FromStr for Mime {
subtype: String::new(), // TODO: fill in.
static_basetype: None, // TODO: fill in
static_subtype: None, // TODO: fill in
parameters: None, // TODO: fill in.
params: None, // TODO: fill in.
})
}
}
Expand All @@ -153,3 +164,69 @@ impl ToHeaderValues for Mime {
Ok(header.to_header_values().unwrap())
}
}
/// A parameter name.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParamName(Cow<'static, str>);

impl Display for ParamName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}

impl FromStr for ParamName {
type Err = crate::Error;

/// Create a new `HeaderName`.
///
/// This checks it's valid ASCII, and lowercases it.
fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::ensure!(s.is_ascii(), "String slice should be valid ASCII");
Ok(ParamName(Cow::Owned(s.to_ascii_lowercase())))
}
}

impl<'a> From<&'a str> for ParamName {
fn from(value: &'a str) -> Self {
Self::from_str(value).unwrap()
}
}

/// A parameter value.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ParamValue(Cow<'static, str>);

impl Display for ParamValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}

impl<'a> PartialEq<&'a str> for ParamValue {
fn eq(&self, other: &&'a str) -> bool {
&self.0 == other
}
}

impl PartialEq<str> for ParamValue {
fn eq(&self, other: &str) -> bool {
&self.0 == other
}
}

/// This is a hack that allows us to mark a trait as utf8 during compilation. We
/// can remove this once we can construct HashMap during compilation.
#[derive(Debug, Clone)]
pub(crate) enum ParamKind {
Utf8,
Map(HashMap<ParamName, ParamValue>),
}

impl ParamKind {
pub(crate) fn unwrap(&mut self) -> &mut HashMap<ParamName, ParamValue> {
match self {
Self::Map(t) => t,
_ => panic!("Unwrapped a ParamKind::utf8"),
}
}
}
18 changes: 10 additions & 8 deletions src/mime/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::collections::HashMap;
use std::io::prelude::*;
use std::io::Cursor;

use super::Mime;
use super::{Mime, ParamKind, ParamName, ParamValue};

/// Parse a string into a mime type.
#[allow(dead_code)]
Expand Down Expand Up @@ -54,13 +54,13 @@ pub(crate) fn parse(s: &str) -> crate::Result<Mime> {
essence: format!("{}/{}", &basetype, &subtype),
basetype,
subtype,
parameters: None,
params: None,
static_essence: None,
static_basetype: None,
static_subtype: None,
};

// parse parameters into a hashmap
// parse params into a hashmap
//
// ```txt
// text/html; charset=utf-8;
Expand Down Expand Up @@ -121,12 +121,14 @@ pub(crate) fn parse(s: &str) -> crate::Result<Mime> {
param_value.make_ascii_lowercase();

// Insert attribute pair into hashmap.
mime.parameters.get_or_insert_with(HashMap::new);
mime.params
.get_or_insert_with(|| ParamKind::Map(HashMap::new()));

mime.parameters
mime.params
.as_mut()
.unwrap()
.insert(param_name, param_value);
.unwrap()
.insert(ParamName(param_name.into()), ParamValue(param_value.into()));
}

Ok(mime)
Expand Down Expand Up @@ -171,12 +173,12 @@ fn test() {
let mime = parse("text/html; charset=utf-8").unwrap();
assert_eq!(mime.basetype(), "text");
assert_eq!(mime.subtype(), "html");
assert_eq!(mime.param("charset"), Some(&"utf-8".to_string()));
assert_eq!(mime.param("charset").unwrap(), "utf-8");

let mime = parse("text/html; charset=utf-8;").unwrap();
assert_eq!(mime.basetype(), "text");
assert_eq!(mime.subtype(), "html");
assert_eq!(mime.param("charset"), Some(&"utf-8".to_string()));
assert_eq!(mime.param("charset").unwrap(), "utf-8");

assert!(parse("text").is_err());
assert!(parse("text/").is_err());
Expand Down