Skip to content

Commit b58b360

Browse files
authored
Merge pull request #310 from http-rs/refactor-media-type-internals
Refactor media type internals
2 parents f955922 + 5b546b3 commit b58b360

File tree

4 files changed

+91
-143
lines changed

4 files changed

+91
-143
lines changed

src/mime/constants.rs

+11-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use super::ParamKind;
21
use crate::Mime;
2+
use std::borrow::Cow;
33

44
macro_rules! utf8_mime_const {
55
($name:ident, $desc:expr, $base:expr, $sub:expr) => {
@@ -9,18 +9,18 @@ macro_rules! utf8_mime_const {
99
$desc,
1010
$base,
1111
$sub,
12-
Some(ParamKind::Utf8),
12+
true,
1313
";charset=utf-8"
1414
);
1515
};
1616
}
1717
macro_rules! mime_const {
1818
($name:ident, $desc:expr, $base:expr, $sub:expr) => {
19-
mime_const!(with_params, $name, $desc, $base, $sub, None, "");
19+
mime_const!(with_params, $name, $desc, $base, $sub, false, "");
2020
};
2121

22-
(with_params, $name:ident, $desc:expr, $base:expr, $sub:expr, $params:expr, $doccomment:expr) => {
23-
mime_const!(doc_expanded, $name, $desc, $base, $sub, $params,
22+
(with_params, $name:ident, $desc:expr, $base:expr, $sub:expr, $is_utf8:expr, $doccomment:expr) => {
23+
mime_const!(doc_expanded, $name, $desc, $base, $sub, $is_utf8,
2424
concat!(
2525
"Content-Type for ",
2626
$desc,
@@ -29,16 +29,14 @@ macro_rules! mime_const {
2929
);
3030
};
3131

32-
(doc_expanded, $name:ident, $desc:expr, $base:expr, $sub:expr, $params:expr, $doccomment:expr) => {
32+
(doc_expanded, $name:ident, $desc:expr, $base:expr, $sub:expr, $is_utf8:expr, $doccomment:expr) => {
3333
#[doc = $doccomment]
3434
pub const $name: Mime = Mime {
35-
essence: String::new(),
36-
basetype: String::new(),
37-
subtype: String::new(),
38-
params: $params,
39-
static_essence: Some(concat!($base, "/", $sub)),
40-
static_basetype: Some($base),
41-
static_subtype: Some($sub),
35+
essence: Cow::Borrowed(concat!($base, "/", $sub)),
36+
basetype: Cow::Borrowed($base),
37+
subtype: Cow::Borrowed($sub),
38+
is_utf8: $is_utf8,
39+
params: vec![],
4240
};
4341
};
4442
}

src/mime/mod.rs

+29-87
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,15 @@ use infer::Infer;
2828
/// ```
2929
// NOTE: we cannot statically initialize Strings with values yet, so we keep dedicated static
3030
// fields for the static strings.
31-
#[derive(Clone)]
31+
#[derive(Clone, PartialEq, Eq, Debug)]
3232
pub struct Mime {
33-
pub(crate) essence: String,
34-
pub(crate) basetype: String,
35-
pub(crate) subtype: String,
36-
pub(crate) static_essence: Option<&'static str>,
37-
pub(crate) static_basetype: Option<&'static str>,
38-
pub(crate) static_subtype: Option<&'static str>,
39-
pub(crate) params: Option<ParamKind>,
33+
pub(crate) essence: Cow<'static, str>,
34+
pub(crate) basetype: Cow<'static, str>,
35+
pub(crate) subtype: Cow<'static, str>,
36+
// NOTE(yosh): this is a hack because we can't populate vecs in const yet.
37+
// This enables us to encode media types as utf-8 at compilation.
38+
pub(crate) is_utf8: bool,
39+
pub(crate) params: Vec<(ParamName, ParamValue)>,
4040
}
4141

4242
impl Mime {
@@ -68,87 +68,40 @@ impl Mime {
6868
/// According to the spec this method should be named `type`, but that's a reserved keyword in
6969
/// Rust so hence prefix with `base` instead.
7070
pub fn basetype(&self) -> &str {
71-
if let Some(basetype) = self.static_basetype {
72-
&basetype
73-
} else {
74-
&self.basetype
75-
}
71+
&self.basetype
7672
}
7773

7874
/// Access the Mime's `subtype` value.
7975
pub fn subtype(&self) -> &str {
80-
if let Some(subtype) = self.static_subtype {
81-
&subtype
82-
} else {
83-
&self.subtype
84-
}
76+
&self.subtype
8577
}
8678

8779
/// Access the Mime's `essence` value.
8880
pub fn essence(&self) -> &str {
89-
if let Some(essence) = self.static_essence {
90-
&essence
91-
} else {
92-
&self.essence
93-
}
81+
&self.essence
9482
}
9583

9684
/// Get a reference to a param.
9785
pub fn param(&self, name: impl Into<ParamName>) -> Option<&ParamValue> {
9886
let name: ParamName = name.into();
99-
self.params
100-
.as_ref()
101-
.map(|inner| match inner {
102-
ParamKind::Vec(v) => v
103-
.iter()
104-
.find_map(|(k, v)| if k == &name { Some(v) } else { None }),
105-
ParamKind::Utf8 => match name {
106-
ParamName(Cow::Borrowed("charset")) => Some(&ParamValue(Cow::Borrowed("utf8"))),
107-
_ => None,
108-
},
109-
})
110-
.flatten()
87+
if name.as_str() == "charset" && self.is_utf8 {
88+
return Some(&ParamValue(Cow::Borrowed("utf-8")));
89+
}
90+
91+
self.params.iter().find(|(k, _)| k == &name).map(|(_, v)| v)
11192
}
11293

11394
/// Remove a param from the set. Returns the `ParamValue` if it was contained within the set.
11495
pub fn remove_param(&mut self, name: impl Into<ParamName>) -> Option<ParamValue> {
11596
let name: ParamName = name.into();
116-
let mut unset_params = false;
117-
let ret = self
118-
.params
119-
.as_mut()
120-
.map(|inner| match inner {
121-
ParamKind::Vec(v) => match v.iter().position(|(k, _)| k == &name) {
122-
Some(index) => Some(v.remove(index).1),
123-
None => None,
124-
},
125-
ParamKind::Utf8 => match name {
126-
ParamName(Cow::Borrowed("charset")) => {
127-
unset_params = true;
128-
Some(ParamValue(Cow::Borrowed("utf8")))
129-
}
130-
_ => None,
131-
},
132-
})
133-
.flatten();
134-
if unset_params {
135-
self.params = None;
97+
if name.as_str() == "charset" && self.is_utf8 {
98+
self.is_utf8 = false;
99+
return Some(ParamValue(Cow::Borrowed("utf-8")));
136100
}
137-
ret
138-
}
139-
}
140-
141-
impl PartialEq<Mime> for Mime {
142-
fn eq(&self, other: &Mime) -> bool {
143-
let left = match self.static_essence {
144-
Some(essence) => essence,
145-
None => &self.essence,
146-
};
147-
let right = match other.static_essence {
148-
Some(essence) => essence,
149-
None => &other.essence,
150-
};
151-
left == right
101+
self.params
102+
.iter()
103+
.position(|(k, _)| k == &name)
104+
.map(|pos| self.params.remove(pos).1)
152105
}
153106
}
154107

@@ -158,15 +111,11 @@ impl Display for Mime {
158111
}
159112
}
160113

161-
impl Debug for Mime {
162-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163-
if let Some(essence) = self.static_essence {
164-
Debug::fmt(essence, f)
165-
} else {
166-
Debug::fmt(&self.essence, f)
167-
}
168-
}
169-
}
114+
// impl Debug for Mime {
115+
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116+
// Debug::fmt(&self.essence, f)
117+
// }
118+
// }
170119

171120
impl FromStr for Mime {
172121
type Err = crate::Error;
@@ -196,6 +145,7 @@ impl ToHeaderValues for Mime {
196145
Ok(header.to_header_values().unwrap())
197146
}
198147
}
148+
199149
/// A parameter name.
200150
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
201151
pub struct ParamName(Cow<'static, str>);
@@ -259,11 +209,3 @@ impl PartialEq<str> for ParamValue {
259209
self.0 == other
260210
}
261211
}
262-
263-
/// This is a hack that allows us to mark a trait as utf8 during compilation. We
264-
/// can remove this once we can construct HashMap during compilation.
265-
#[derive(Debug, Clone, PartialEq, Eq)]
266-
pub(crate) enum ParamKind {
267-
Utf8,
268-
Vec(Vec<(ParamName, ParamValue)>),
269-
}

src/mime/parse.rs

+42-43
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
use std::borrow::Cow;
12
use std::fmt;
23

3-
use super::{Mime, ParamKind, ParamName, ParamValue};
4+
use super::{Mime, ParamName, ParamValue};
45

56
/// Parse a string into a mime type.
67
/// Follows the [WHATWG MIME parsing algorithm](https://mimesniff.spec.whatwg.org/#parsing-a-mime-type)
@@ -40,7 +41,8 @@ pub(crate) fn parse(input: &str) -> crate::Result<Mime> {
4041
// 10.
4142
let basetype = basetype.to_ascii_lowercase();
4243
let subtype = subtype.to_ascii_lowercase();
43-
let mut params = None;
44+
let mut params = vec![];
45+
let mut is_utf8 = false;
4446

4547
// 11.
4648
let mut input = input;
@@ -91,13 +93,14 @@ pub(crate) fn parse(input: &str) -> crate::Result<Mime> {
9193
};
9294

9395
// 10.
94-
if !parameter_name.is_empty()
96+
if parameter_name == "charset" && parameter_value == "utf-8" {
97+
is_utf8 = true;
98+
} else if !parameter_name.is_empty()
9599
&& parameter_name.chars().all(is_http_token_code_point)
96100
&& parameter_value
97101
.chars()
98102
.all(is_http_quoted_string_token_code_point)
99103
{
100-
let params = params.get_or_insert_with(Vec::new);
101104
let name = ParamName(parameter_name.into());
102105
let value = ParamValue(parameter_value.into());
103106
if !params.iter().any(|(k, _)| k == &name) {
@@ -107,13 +110,11 @@ pub(crate) fn parse(input: &str) -> crate::Result<Mime> {
107110
}
108111

109112
Ok(Mime {
110-
essence: format!("{}/{}", &basetype, &subtype),
111-
basetype,
112-
subtype,
113-
params: params.map(ParamKind::Vec),
114-
static_essence: None,
115-
static_basetype: None,
116-
static_subtype: None,
113+
essence: Cow::Owned(format!("{}/{}", &basetype, &subtype)),
114+
basetype: basetype.into(),
115+
subtype: subtype.into(),
116+
params,
117+
is_utf8,
117118
})
118119
}
119120

@@ -205,39 +206,23 @@ fn collect_http_quoted_string(mut input: &str) -> (String, &str) {
205206

206207
/// Implementation of [WHATWG MIME serialization algorithm](https://mimesniff.spec.whatwg.org/#serializing-a-mime-type)
207208
pub(crate) fn format(mime_type: &Mime, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208-
if let Some(essence) = mime_type.static_essence {
209-
write!(f, "{}", essence)?
210-
} else {
211-
write!(f, "{}", &mime_type.essence)?
209+
write!(f, "{}", &mime_type.essence)?;
210+
if mime_type.is_utf8 {
211+
write!(f, ";charset=utf-8")?;
212212
}
213-
if let Some(params) = &mime_type.params {
214-
match params {
215-
ParamKind::Utf8 => write!(f, ";charset=utf-8")?,
216-
ParamKind::Vec(params) => {
217-
for (name, value) in params {
218-
if value.0.chars().all(is_http_token_code_point) && !value.0.is_empty() {
219-
write!(f, ";{}={}", name, value)?;
220-
} else {
221-
write!(
222-
f,
223-
";{}=\"{}\"",
224-
name,
225-
value
226-
.0
227-
.chars()
228-
.flat_map(|c| match c {
229-
'"' | '\\' => EscapeMimeValue {
230-
state: EscapeMimeValueState::Backslash(c)
231-
},
232-
c => EscapeMimeValue {
233-
state: EscapeMimeValueState::Char(c)
234-
},
235-
})
236-
.collect::<String>()
237-
)?;
238-
}
239-
}
240-
}
213+
for (name, value) in mime_type.params.iter() {
214+
if value.0.chars().all(is_http_token_code_point) && !value.0.is_empty() {
215+
write!(f, ";{}={}", name, value)?;
216+
} else {
217+
let value = value
218+
.0
219+
.chars()
220+
.flat_map(|c| match c {
221+
'"' | '\\' => EscapeMimeValue::backslash(c),
222+
c => EscapeMimeValue::char(c),
223+
})
224+
.collect::<String>();
225+
write!(f, ";{}=\"{}\"", name, value)?;
241226
}
242227
}
243228
Ok(())
@@ -247,6 +232,20 @@ struct EscapeMimeValue {
247232
state: EscapeMimeValueState,
248233
}
249234

235+
impl EscapeMimeValue {
236+
fn backslash(c: char) -> Self {
237+
EscapeMimeValue {
238+
state: EscapeMimeValueState::Backslash(c),
239+
}
240+
}
241+
242+
fn char(c: char) -> Self {
243+
EscapeMimeValue {
244+
state: EscapeMimeValueState::Char(c),
245+
}
246+
}
247+
}
248+
250249
#[derive(Clone, Debug)]
251250
enum EscapeMimeValueState {
252251
Done,

tests/mime.rs

+9
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,13 @@ mod tests {
4444
assert_eq!(res.content_type(), Some(mime::BYTE_STREAM));
4545
Ok(())
4646
}
47+
48+
// #[test]
49+
// fn match_mime_types() {
50+
// let req = Request::get("https://example.com");
51+
// match req.content_type() {
52+
// Some(mime::JSON) => {}
53+
// _ => {}
54+
// }
55+
// }
4756
}

0 commit comments

Comments
 (0)