From 043e569020eb155a909c400eb1bab7e857bbe31c Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 12:24:10 +0100 Subject: [PATCH 01/10] link: move from disabled to common --- src/{disabled => common}/link.rs | 0 src/common/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/{disabled => common}/link.rs (100%) diff --git a/src/disabled/link.rs b/src/common/link.rs similarity index 100% rename from src/disabled/link.rs rename to src/common/link.rs diff --git a/src/common/mod.rs b/src/common/mod.rs index 3a1e9c0f..213cf6a7 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -44,7 +44,7 @@ pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; //pub use self::last_event_id::LastEventId; pub use self::last_modified::LastModified; -//pub use self::link::{Link, LinkValue, RelationType, MediaDesc}; +pub use self::link::{Link, LinkValue, RelationType, MediaDesc}; pub use self::location::Location; pub use self::origin::Origin; pub use self::pragma::Pragma; @@ -163,7 +163,7 @@ mod if_range; mod if_unmodified_since; //mod last_event_id; mod last_modified; -//mod link; +mod link; mod location; mod origin; mod pragma; From 0d5d358cceb0a43311da17f1e6bcf57ec4fbd583 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 14:33:55 +0100 Subject: [PATCH 02/10] link: publish link as a module instead of re-exporting types --- src/common/link.rs | 4 +++- src/common/mod.rs | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/link.rs b/src/common/link.rs index a6d84944..a4808373 100644 --- a/src/common/link.rs +++ b/src/common/link.rs @@ -1,3 +1,5 @@ +//! Link header and types. + use std::fmt; use std::borrow::Cow; use std::str::FromStr; @@ -58,7 +60,7 @@ use {Header, Raw}; /// # Examples /// /// ``` -/// use headers::{Headers, Link, LinkValue, RelationType}; +/// use headers::{Headers, link::{Link, LinkValue, RelationType}}; /// /// let link_value = LinkValue::new("http://example.com/TheBook/chapter2") /// .push_rel(RelationType::Previous) diff --git a/src/common/mod.rs b/src/common/mod.rs index 213cf6a7..4b199163 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -44,7 +44,6 @@ pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; //pub use self::last_event_id::LastEventId; pub use self::last_modified::LastModified; -pub use self::link::{Link, LinkValue, RelationType, MediaDesc}; pub use self::location::Location; pub use self::origin::Origin; pub use self::pragma::Pragma; @@ -163,7 +162,7 @@ mod if_range; mod if_unmodified_since; //mod last_event_id; mod last_modified; -mod link; +pub mod link; mod location; mod origin; mod pragma; From cb637b6dbb03d60f01ad3692275ffcc0e38418fc Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 12:30:36 +0100 Subject: [PATCH 03/10] link: fix error handling code --- src/common/link.rs | 56 +++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/common/link.rs b/src/common/link.rs index a4808373..0459f59f 100644 --- a/src/common/link.rs +++ b/src/common/link.rs @@ -393,7 +393,7 @@ impl Header for Link { NAME } - fn parse_header(raw: &Raw) -> ::Result { + fn parse_header(raw: &Raw) -> Result { // If more that one `Link` headers are present in a request's // headers they are combined in a single `Link` header containing // all the `link-value`s present in each of those `Link` headers. @@ -408,10 +408,10 @@ impl Header for Link { Some(Ok(p)) }, - _ => Some(Err(::Error::Header)), + _ => Some(Err(::Error::invalid())), } }) - .unwrap_or(Err(::Error::Header)) + .unwrap_or(Err(::Error::invalid())) } fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { @@ -463,7 +463,7 @@ impl fmt::Display for LinkValue { impl FromStr for Link { type Err = ::Error; - fn from_str(s: &str) -> ::Result { + fn from_str(s: &str) -> Result { // Create a split iterator with delimiters: `;`, `,` let link_split = SplitAsciiUnquoted::new(s, ";,"); @@ -477,7 +477,7 @@ impl FromStr for Link { if segment.trim().starts_with('<') { link_values.push( match verify_and_trim(segment.trim(), (b'<', b'>')) { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(s) => { LinkValue { link: s.to_owned().into(), @@ -498,12 +498,12 @@ impl FromStr for Link { let mut link_param_split = segment.splitn(2, '='); let link_param_name = match link_param_split.next() { - None => return Err(::Error::Header), + None => return Err(::Error::invalid()), Some(p) => p.trim(), }; let link_header = match link_values.last_mut() { - None => return Err(::Error::Header), + None => return Err(::Error::invalid()), Some(l) => l, }; @@ -512,13 +512,13 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.3 if link_header.rel.is_none() { link_header.rel = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => { s.trim_matches(|c: char| c == '"' || c.is_whitespace()) .split(' ') .map(|t| t.trim().parse()) .collect::, _>>() - .or_else(|_| Err(::Error::Header)) + .or_else(|_| Err(::Error::invalid())) .ok() }, }; @@ -527,9 +527,9 @@ impl FromStr for Link { // Parse the `Context IRI`. // https://tools.ietf.org/html/rfc5988#section-5.2 link_header.anchor = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(a) => Some(String::from(a)), }, }; @@ -538,13 +538,13 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.3 if link_header.rev.is_none() { link_header.rev = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => { s.trim_matches(|c: char| c == '"' || c.is_whitespace()) .split(' ') .map(|t| t.trim().parse()) .collect::, _>>() - .or_else(|_| Err(::Error::Header)) + .or_else(|_| Err(::Error::invalid())) .ok() }, } @@ -556,9 +556,9 @@ impl FromStr for Link { v.push( match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => match s.trim().parse() { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(t) => t, }, } @@ -570,13 +570,13 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.media_desc.is_none() { link_header.media_desc = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => { s.trim_matches(|c: char| c == '"' || c.is_whitespace()) .split(',') .map(|t| t.trim().parse()) .collect::, _>>() - .or_else(|_| Err(::Error::Header)) + .or_else(|_| Err(::Error::invalid())) .ok() }, }; @@ -586,9 +586,9 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.title.is_none() { link_header.title = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(t) => Some(String::from(t)), }, }; @@ -601,7 +601,7 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5987#section-3.2.1 if link_header.title_star.is_none() { link_header.title_star = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => Some(String::from(s.trim())), }; } @@ -610,11 +610,11 @@ impl FromStr for Link { // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.media_type.is_none() { link_header.media_type = match link_param_split.next() { - None | Some("") => return Err(::Error::Header), + None | Some("") => return Err(::Error::invalid()), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(t) => match t.parse() { - Err(_) => return Err(::Error::Header), + Err(_) => return Err(::Error::invalid()), Ok(m) => Some(m), }, }, @@ -622,7 +622,7 @@ impl FromStr for Link { }; } } else { - return Err(::Error::Header); + return Err(::Error::invalid()); } } } @@ -651,7 +651,7 @@ impl fmt::Display for MediaDesc { impl FromStr for MediaDesc { type Err = ::Error; - fn from_str(s: &str) -> ::Result { + fn from_str(s: &str) -> Result { match s { "screen" => Ok(MediaDesc::Screen), "tty" => Ok(MediaDesc::Tty), @@ -718,7 +718,7 @@ impl fmt::Display for RelationType { impl FromStr for RelationType { type Err = ::Error; - fn from_str(s: &str) -> ::Result { + fn from_str(s: &str) -> Result { if "alternate".eq_ignore_ascii_case(s) { Ok(RelationType::Alternate) } else if "appendix".eq_ignore_ascii_case(s) { @@ -872,7 +872,7 @@ fn fmt_delimited(f: &mut fmt::Formatter, p: &[T], d: &str, b: ( Ok(()) } -fn verify_and_trim(s: &str, b: (u8, u8)) -> ::Result<&str> { +fn verify_and_trim(s: &str, b: (u8, u8)) -> Result<&str, ::Error> { let length = s.len(); let byte_array = s.as_bytes(); @@ -883,7 +883,7 @@ fn verify_and_trim(s: &str, b: (u8, u8)) -> ::Result<&str> { |c: char| c == b.0 as char || c == b.1 as char || c.is_whitespace()) ) } else { - Err(::Error::Header) + Err(::Error::invalid()) } } From a52e9ab0865b1e0accad5a7660d102401c825baa Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 12:30:48 +0100 Subject: [PATCH 04/10] link: use ? instead of try! --- src/common/link.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/common/link.rs b/src/common/link.rs index 0459f59f..7406eb38 100644 --- a/src/common/link.rs +++ b/src/common/link.rs @@ -427,33 +427,33 @@ impl fmt::Display for Link { impl fmt::Display for LinkValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(write!(f, "<{}>", self.link)); + write!(f, "<{}>", self.link)?; if let Some(ref rel) = self.rel { - try!(fmt_delimited(f, rel.as_slice(), " ", ("; rel=\"", "\""))); + fmt_delimited(f, rel.as_slice(), " ", ("; rel=\"", "\""))?; } if let Some(ref anchor) = self.anchor { - try!(write!(f, "; anchor=\"{}\"", anchor)); + write!(f, "; anchor=\"{}\"", anchor)?; } if let Some(ref rev) = self.rev { - try!(fmt_delimited(f, rev.as_slice(), " ", ("; rev=\"", "\""))); + fmt_delimited(f, rev.as_slice(), " ", ("; rev=\"", "\""))?; } if let Some(ref href_lang) = self.href_lang { for tag in href_lang { - try!(write!(f, "; hreflang={}", tag)); + write!(f, "; hreflang={}", tag)?; } } if let Some(ref media_desc) = self.media_desc { - try!(fmt_delimited(f, media_desc.as_slice(), ", ", ("; media=\"", "\""))); + fmt_delimited(f, media_desc.as_slice(), ", ", ("; media=\"", "\""))?; } if let Some(ref title) = self.title { - try!(write!(f, "; title=\"{}\"", title)); + write!(f, "; title=\"{}\"", title)?; } if let Some(ref title_star) = self.title_star { - try!(write!(f, "; title*={}", title_star)); + write!(f, "; title*={}", title_star)?; } if let Some(ref media_type) = self.media_type { - try!(write!(f, "; type=\"{}\"", media_type)); + write!(f, "; type=\"{}\"", media_type)?; } Ok(()) @@ -858,15 +858,15 @@ impl<'a> Iterator for SplitAsciiUnquoted<'a> { fn fmt_delimited(f: &mut fmt::Formatter, p: &[T], d: &str, b: (&str, &str)) -> fmt::Result { if p.len() != 0 { // Write a starting string `b.0` before the first element - try!(write!(f, "{}{}", b.0, p[0])); + write!(f, "{}{}", b.0, p[0])?; for i in &p[1..] { // Write the next element preceded by the delimiter `d` - try!(write!(f, "{}{}", d, i)); + write!(f, "{}{}", d, i)?; } // Write a ending string `b.1` before the first element - try!(write!(f, "{}", b.1)); + write!(f, "{}", b.1)?; } Ok(()) From 9ff54e51541f4bc9dfac7bf26beab99a23e6c130 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 12:31:07 +0100 Subject: [PATCH 05/10] link: remove missing bench_header --- src/common/link.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/common/link.rs b/src/common/link.rs index 7406eb38..6d9f3339 100644 --- a/src/common/link.rs +++ b/src/common/link.rs @@ -1103,5 +1103,3 @@ mod tests { assert_eq!(err.is_err(), true); } } - -bench_header!(bench_link, Link, { vec![b"; rel=\"previous\"; rev=next; title=\"previous chapter\"; type=\"text/html\"; media=\"screen, tty\"".to_vec()] }); From 35516b2264ccd3e9e3a2af126f6f26bb624a2d0e Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 13:13:52 +0100 Subject: [PATCH 06/10] link: depend on language_tags --- Cargo.toml | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index ded8fc58..0fa17339 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ bytes = "0.5" mime = "0.3.14" sha-1 = "0.8" time = "0.1.34" +language-tags = "0.2.2" [features] nightly = [] diff --git a/src/lib.rs b/src/lib.rs index 91d29a40..57ba4559 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ extern crate bitflags; extern crate bytes; extern crate headers_core; extern crate http; +extern crate language_tags; extern crate mime; extern crate sha1; #[cfg(all(test, feature = "nightly"))] From feb879d0e7ca267c41e2851db47c14ac5fe5c8f4 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 13:29:43 +0100 Subject: [PATCH 07/10] link: update tests to the new headers API --- src/common/link.rs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/common/link.rs b/src/common/link.rs index 6d9f3339..ee0eaca3 100644 --- a/src/common/link.rs +++ b/src/common/link.rs @@ -60,14 +60,14 @@ use {Header, Raw}; /// # Examples /// /// ``` -/// use headers::{Headers, link::{Link, LinkValue, RelationType}}; +/// use headers::{HeaderMap, HeaderMapExt, link::{Link, LinkValue, RelationType}}; /// /// let link_value = LinkValue::new("http://example.com/TheBook/chapter2") /// .push_rel(RelationType::Previous) /// .set_title("previous chapter"); /// -/// let mut headers = Headers::new(); -/// headers.set( +/// let mut headers = HeaderMap::new(); +/// headers.typed_insert( /// Link::new(vec![link_value]) /// ); /// ``` @@ -901,11 +901,15 @@ mod tests { use Header; - // use proto::ServerTransaction; - use bytes::BytesMut; - use mime; + fn parse_header(values: &[&[u8]]) -> Result { + let values = values.iter() + .map(|val| ::HeaderValue::from_bytes(val).expect("invalid header value")) + .collect::>(); + Link::decode(&mut values.iter()) + } + #[test] fn test_link() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") @@ -918,7 +922,7 @@ mod tests { let expected_link = Link::new(vec![link_value]); - let link = Header::parse_header(&vec![link_header.to_vec()].into()); + let link = parse_header(&[link_header]); assert_eq!(link.ok(), Some(expected_link)); } @@ -939,7 +943,7 @@ mod tests { let expected_link = Link::new(vec![first_link, second_link]); - let link = Header::parse_header(&vec![link_header.to_vec()].into()); + let link = parse_header(&[link_header]); assert_eq!(link.ok(), Some(expected_link)); } @@ -963,7 +967,7 @@ mod tests { let expected_link = Link::new(vec![link_value]); - let link = Header::parse_header(&vec![link_header.to_vec()].into()); + let link = parse_header(&[link_header]); assert_eq!(link.ok(), Some(expected_link)); } @@ -1034,31 +1038,31 @@ mod tests { let link_a = b"http://example.com/TheBook/chapter2; \ rel=\"previous\"; rev=next; title=\"previous chapter\""; - let mut err: Result = Header::parse_header(&vec![link_a.to_vec()].into()); + let mut err: Result = parse_header(&[link_a]); assert_eq!(err.is_err(), true); let link_b = b"; \ =\"previous\"; rev=next; title=\"previous chapter\""; - err = Header::parse_header(&vec![link_b.to_vec()].into()); + err = parse_header(&[link_b]); assert_eq!(err.is_err(), true); let link_c = b"; \ rel=; rev=next; title=\"previous chapter\""; - err = Header::parse_header(&vec![link_c.to_vec()].into()); + err = parse_header(&[link_c]); assert_eq!(err.is_err(), true); let link_d = b"; \ rel=\"previous\"; rev=next; title="; - err = Header::parse_header(&vec![link_d.to_vec()].into()); + err = parse_header(&[link_d]); assert_eq!(err.is_err(), true); let link_e = b"; \ rel=\"previous\"; rev=next; attr=unknown"; - err = Header::parse_header(&vec![link_e.to_vec()].into()); + err = parse_header(&[link_e]); assert_eq!(err.is_err(), true); } From ae37333242b9fc1590d409503840d47297e9df1c Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 14:42:47 +0100 Subject: [PATCH 08/10] link: enable and update to the new API a previousy-commented test --- src/common/link.rs | 61 ++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/src/common/link.rs b/src/common/link.rs index ee0eaca3..65c7c3bc 100644 --- a/src/common/link.rs +++ b/src/common/link.rs @@ -971,41 +971,32 @@ mod tests { assert_eq!(link.ok(), Some(expected_link)); } - // TODO - // #[test] - // fn test_link_multiple_link_headers() { - // let first_link = LinkValue::new("/TheBook/chapter2") - // .push_rel(RelationType::Previous) - // .set_title_star("UTF-8'de'letztes%20Kapitel"); - - // let second_link = LinkValue::new("/TheBook/chapter4") - // .push_rel(RelationType::Next) - // .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); - - // let third_link = LinkValue::new("http://example.com/TheBook/chapter2") - // .push_rel(RelationType::Previous) - // .push_rev(RelationType::Next) - // .set_title("previous chapter"); - - // let expected_link = Link::new(vec![first_link, second_link, third_link]); - - // let mut raw = BytesMut::from(b"GET /super_short_uri/and_whatever HTTP/1.1\r\nHost: \ - // hyper.rs\r\nAccept: a lot of things\r\nAccept-Charset: \ - // utf8\r\nAccept-Encoding: *\r\nLink: ; \ - // rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ - // ; rel=\"next\"; title*=\ - // UTF-8'de'n%c3%a4chstes%20Kapitel\r\n\ - // Access-Control-Allow-Credentials: None\r\nLink: \ - // ; \ - // rel=\"previous\"; rev=next; title=\"previous chapter\"\ - // \r\n\r\n".to_vec()); - - // let (mut res, _) = ServerTransaction::parse(&mut raw).unwrap().unwrap(); - - // let link = res.headers.remove::().unwrap(); - - // assert_eq!(link, expected_link); - // } + #[test] + fn test_link_multiple_link_headers() { + let first_link = LinkValue::new("/TheBook/chapter2") + .push_rel(RelationType::Previous) + .set_title_star("UTF-8'de'letztes%20Kapitel"); + + let second_link = LinkValue::new("/TheBook/chapter4") + .push_rel(RelationType::Next) + .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); + + let third_link = LinkValue::new("http://example.com/TheBook/chapter2") + .push_rel(RelationType::Previous) + .push_rev(RelationType::Next) + .set_title("previous chapter"); + + let expected_link = Link::new(vec![first_link, second_link, third_link]); + + let link = parse_header(&[ + b"; rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ + ; rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel", + b"; rel=\"previous\"; rev=next; \ + title=\"previous chapter\"", + ]).unwrap(); + + assert_eq!(link, expected_link); + } #[test] fn test_link_display() { From 8154c07151902805896c8e5935f313cc4df16a9b Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sat, 7 Mar 2020 13:11:26 +0100 Subject: [PATCH 09/10] links: update logic to the new headers API --- src/common/link.rs | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/common/link.rs b/src/common/link.rs index 65c7c3bc..9722a33a 100644 --- a/src/common/link.rs +++ b/src/common/link.rs @@ -9,9 +9,6 @@ use std::ascii::AsciiExt; use mime::Mime; use language_tags::LanguageTag; -use parsing; -use {Header, Raw}; - /// The `Link` header, defined in /// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) /// @@ -387,18 +384,24 @@ impl LinkValue { // Trait implementations //////////////////////////////////////////////////////////////////////////////// -impl Header for Link { - fn header_name() -> &'static str { - static NAME: &'static str = "Link"; - NAME +impl ::Header for Link { + fn name() -> &'static ::HeaderName { + &::http::header::LINK } - fn parse_header(raw: &Raw) -> Result { + fn decode<'i, I>(values: &mut I) -> Result + where + I: Iterator, + { // If more that one `Link` headers are present in a request's // headers they are combined in a single `Link` header containing // all the `link-value`s present in each of those `Link` headers. - raw.iter() - .map(parsing::from_raw_str::) + values + .map(|line| { + line.to_str() + .map_err(|_| ::Error::invalid()) + .and_then(|line| Link::from_str(line)) + }) .fold(None, |p, c| { match (p, c) { (None, c) => Some(c), @@ -414,14 +417,23 @@ impl Header for Link { .unwrap_or(Err(::Error::invalid())) } - fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { - f.fmt_line(self) + fn encode>(&self, values: &mut E) { + values.extend(std::iter::once(::HeaderValue::from_str(&self.to_string()).unwrap())); } } impl fmt::Display for Link { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_delimited(f, self.values.as_slice(), ", ", ("", "")) + let mut first = true; + for value in &self.values { + if !first { + write!(f, ", ")?; + } + first = false; + + write!(f, "{}", value)?; + } + Ok(()) } } From 3bbec6e2ef3c6064bbe5e1d261c6d0c29dfb5793 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Sun, 15 Mar 2020 16:44:53 +0100 Subject: [PATCH 10/10] link: wrap enums into structs and expose variants as consts --- src/common/link.rs | 442 +++++++++++++++------------------------------ 1 file changed, 141 insertions(+), 301 deletions(-) diff --git a/src/common/link.rs b/src/common/link.rs index 9722a33a..c7905e2b 100644 --- a/src/common/link.rs +++ b/src/common/link.rs @@ -60,7 +60,7 @@ use language_tags::LanguageTag; /// use headers::{HeaderMap, HeaderMapExt, link::{Link, LinkValue, RelationType}}; /// /// let link_value = LinkValue::new("http://example.com/TheBook/chapter2") -/// .push_rel(RelationType::Previous) +/// .push_rel(RelationType::PREVIOUS) /// .set_title("previous chapter"); /// /// let mut headers = HeaderMap::new(); @@ -108,120 +108,134 @@ pub struct LinkValue { media_type: Option, } -/// A Media Descriptors Enum based on: -/// [https://www.w3.org/TR/html401/types.html#h-6.13][url] -/// -/// [url]: https://www.w3.org/TR/html401/types.html#h-6.13 -#[derive(Clone, PartialEq, Debug)] -pub enum MediaDesc { - /// screen. - Screen, - /// tty. - Tty, - /// tv. - Tv, - /// projection. - Projection, - /// handheld. - Handheld, - /// print. - Print, - /// braille. - Braille, - /// aural. - Aural, - /// all. - All, - /// Unrecognized media descriptor extension. - Extension(String) +//////////////////////////////////////////////////////////////////////////////// +// Typed variants +//////////////////////////////////////////////////////////////////////////////// + +macro_rules! impl_variants { + ( + $(#[$attrs:meta])* + name: $name:ident, + mod_name: $mod_name:ident, + $($typed:ident => $string:expr,)* + ) => { + mod $mod_name { + use std::{fmt, str::FromStr}; + + $(#[$attrs])* + #[derive(Clone, PartialEq, Debug)] + pub struct $name(Inner); + + impl $name { + $( + #[doc = $string] + pub const $typed: $name = $name(Inner::$typed); + )* + } + + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self.0 { + $(Inner::$typed => fmt::Display::fmt($string, f),)* + Inner::Custom(custom) => fmt::Display::fmt(&custom, f), + } + } + } + + impl FromStr for $name { + type Err = ::Error; + + fn from_str(input: &str) -> Result<$name, ::Error> { + if false { + unreachable!(); + } + $(else if $string.eq_ignore_ascii_case(input) { + Ok(Self(Inner::$typed)) + })* + else { + Ok(Self(Inner::Custom(input.into()))) + } + } + } + + #[derive(Clone, PartialEq, Debug)] + #[allow(nonstandard_style)] + enum Inner { + $($typed,)* + Custom(String), + } + } + + pub use self::$mod_name::$name; + } } -/// A Link Relation Type Enum based on: -/// [RFC5988](https://tools.ietf.org/html/rfc5988#section-6.2.2) -#[derive(Clone, PartialEq, Debug)] -pub enum RelationType { - /// alternate. - Alternate, - /// appendix. - Appendix, - /// bookmark. - Bookmark, - /// chapter. - Chapter, - /// contents. - Contents, - /// copyright. - Copyright, - /// current. - Current, - /// describedby. - DescribedBy, - /// edit. - Edit, - /// edit-media. - EditMedia, - /// enclosure. - Enclosure, - /// first. - First, - /// glossary. - Glossary, - /// help. - Help, - /// hub. - Hub, - /// index. - Index, - /// last. - Last, - /// latest-version. - LatestVersion, - /// license. - License, - /// next. - Next, - /// next-archive. - NextArchive, - /// payment. - Payment, - /// prev. - Prev, - /// predecessor-version. - PredecessorVersion, - /// previous. - Previous, - /// prev-archive. - PrevArchive, - /// related. - Related, - /// replies. - Replies, - /// section. - Section, - /// self. - RelationTypeSelf, - /// service. - Service, - /// start. - Start, - /// stylesheet. - Stylesheet, - /// subsection. - Subsection, - /// successor-version. - SuccessorVersion, - /// up. - Up, - /// versionHistory. - VersionHistory, - /// via. - Via, - /// working-copy. - WorkingCopy, - /// working-copy-of. - WorkingCopyOf, - /// ext-rel-type. - ExtRelType(String) + +impl_variants! { + /// A Media Descriptor, based on: + /// [https://www.w3.org/TR/html401/types.html#h-6.13][url] + /// + /// [url]: https://www.w3.org/TR/html401/types.html#h-6.13 + name: MediaDesc, + mod_name: media_desc, + + SCREEN => "screen", + TTY => "tty", + TV => "tv", + PROJECTION => "projection", + HANDHELD => "handheld", + PRINT => "print", + BRAILLE => "braille", + AURAL => "aural", + ALL => "all", +} + +impl_variants! { + /// A Link Relation Type, based on: + /// [RFC5988](https://tools.ietf.org/html/rfc5988#section-6.2.2) + name: RelationType, + mod_name: relation_type, + + ALTERNATE => "alternate", + APPENDIX => "appendix", + BOOKMARK => "bookmark", + CHAPTER => "chapter", + CONTENTS => "contents", + COPYRIGHT => "copyright", + CURRENT => "current", + DESCRIBED_BY => "described-by", + EDIT => "edit", + EDIT_MEDIA => "edit-media", + ENCLOSURE => "enclosure", + FIRST => "first", + GLOSSARY => "glossary", + HELP => "help", + HUB => "hub", + INDEX => "index", + LAST => "last", + LATEST_VERSION => "latest-version", + LICENSE => "license", + NEXT => "next", + NEXT_ARCHIVE => "next-archive", + PAYMENT => "payment", + PREV => "prev", + PREDECESSOR_VERSION => "predecessor-version", + PREVIOUS => "previous", + PREV_ARCHIVE => "prev-archive", + RELATED => "related", + REPLIES => "replies", + SECTION => "section", + SELF => "self", + SERVICE => "service", + START => "start", + STYLESHEET => "stylesheet", + SUBSECTION => "subsection", + SUCCESSOR_VERSION => "successor-version", + UP => "up", + VERSION_HISTORY => "version-history", + VIA => "via", + WORKING_COPY => "working-copy", + WORKING_COPY_OF => "working-copy-of", } //////////////////////////////////////////////////////////////////////////////// @@ -643,180 +657,6 @@ impl FromStr for Link { } } -impl fmt::Display for MediaDesc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - MediaDesc::Screen => write!(f, "screen"), - MediaDesc::Tty => write!(f, "tty"), - MediaDesc::Tv => write!(f, "tv"), - MediaDesc::Projection => write!(f, "projection"), - MediaDesc::Handheld => write!(f, "handheld"), - MediaDesc::Print => write!(f, "print"), - MediaDesc::Braille => write!(f, "braille"), - MediaDesc::Aural => write!(f, "aural"), - MediaDesc::All => write!(f, "all"), - MediaDesc::Extension(ref other) => write!(f, "{}", other), - } - } -} - -impl FromStr for MediaDesc { - type Err = ::Error; - - fn from_str(s: &str) -> Result { - match s { - "screen" => Ok(MediaDesc::Screen), - "tty" => Ok(MediaDesc::Tty), - "tv" => Ok(MediaDesc::Tv), - "projection" => Ok(MediaDesc::Projection), - "handheld" => Ok(MediaDesc::Handheld), - "print" => Ok(MediaDesc::Print), - "braille" => Ok(MediaDesc::Braille), - "aural" => Ok(MediaDesc::Aural), - "all" => Ok(MediaDesc::All), - _ => Ok(MediaDesc::Extension(String::from(s))), - } - } -} - -impl fmt::Display for RelationType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - RelationType::Alternate => write!(f, "alternate"), - RelationType::Appendix => write!(f, "appendix"), - RelationType::Bookmark => write!(f, "bookmark"), - RelationType::Chapter => write!(f, "chapter"), - RelationType::Contents => write!(f, "contents"), - RelationType::Copyright => write!(f, "copyright"), - RelationType::Current => write!(f, "current"), - RelationType::DescribedBy => write!(f, "describedby"), - RelationType::Edit => write!(f, "edit"), - RelationType::EditMedia => write!(f, "edit-media"), - RelationType::Enclosure => write!(f, "enclosure"), - RelationType::First => write!(f, "first"), - RelationType::Glossary => write!(f, "glossary"), - RelationType::Help => write!(f, "help"), - RelationType::Hub => write!(f, "hub"), - RelationType::Index => write!(f, "index"), - RelationType::Last => write!(f, "last"), - RelationType::LatestVersion => write!(f, "latest-version"), - RelationType::License => write!(f, "license"), - RelationType::Next => write!(f, "next"), - RelationType::NextArchive => write!(f, "next-archive"), - RelationType::Payment => write!(f, "payment"), - RelationType::Prev => write!(f, "prev"), - RelationType::PredecessorVersion => write!(f, "predecessor-version"), - RelationType::Previous => write!(f, "previous"), - RelationType::PrevArchive => write!(f, "prev-archive"), - RelationType::Related => write!(f, "related"), - RelationType::Replies => write!(f, "replies"), - RelationType::Section => write!(f, "section"), - RelationType::RelationTypeSelf => write!(f, "self"), - RelationType::Service => write!(f, "service"), - RelationType::Start => write!(f, "start"), - RelationType::Stylesheet => write!(f, "stylesheet"), - RelationType::Subsection => write!(f, "subsection"), - RelationType::SuccessorVersion => write!(f, "successor-version"), - RelationType::Up => write!(f, "up"), - RelationType::VersionHistory => write!(f, "version-history"), - RelationType::Via => write!(f, "via"), - RelationType::WorkingCopy => write!(f, "working-copy"), - RelationType::WorkingCopyOf => write!(f, "working-copy-of"), - RelationType::ExtRelType(ref uri) => write!(f, "{}", uri), - } - } -} - -impl FromStr for RelationType { - type Err = ::Error; - - fn from_str(s: &str) -> Result { - if "alternate".eq_ignore_ascii_case(s) { - Ok(RelationType::Alternate) - } else if "appendix".eq_ignore_ascii_case(s) { - Ok(RelationType::Appendix) - } else if "bookmark".eq_ignore_ascii_case(s) { - Ok(RelationType::Bookmark) - } else if "chapter".eq_ignore_ascii_case(s) { - Ok(RelationType::Chapter) - } else if "contents".eq_ignore_ascii_case(s) { - Ok(RelationType::Contents) - } else if "copyright".eq_ignore_ascii_case(s) { - Ok(RelationType::Copyright) - } else if "current".eq_ignore_ascii_case(s) { - Ok(RelationType::Current) - } else if "describedby".eq_ignore_ascii_case(s) { - Ok(RelationType::DescribedBy) - } else if "edit".eq_ignore_ascii_case(s) { - Ok(RelationType::Edit) - } else if "edit-media".eq_ignore_ascii_case(s) { - Ok(RelationType::EditMedia) - } else if "enclosure".eq_ignore_ascii_case(s) { - Ok(RelationType::Enclosure) - } else if "first".eq_ignore_ascii_case(s) { - Ok(RelationType::First) - } else if "glossary".eq_ignore_ascii_case(s) { - Ok(RelationType::Glossary) - } else if "help".eq_ignore_ascii_case(s) { - Ok(RelationType::Help) - } else if "hub".eq_ignore_ascii_case(s) { - Ok(RelationType::Hub) - } else if "index".eq_ignore_ascii_case(s) { - Ok(RelationType::Index) - } else if "last".eq_ignore_ascii_case(s) { - Ok(RelationType::Last) - } else if "latest-version".eq_ignore_ascii_case(s) { - Ok(RelationType::LatestVersion) - } else if "license".eq_ignore_ascii_case(s) { - Ok(RelationType::License) - } else if "next".eq_ignore_ascii_case(s) { - Ok(RelationType::Next) - } else if "next-archive".eq_ignore_ascii_case(s) { - Ok(RelationType::NextArchive) - } else if "payment".eq_ignore_ascii_case(s) { - Ok(RelationType::Payment) - } else if "prev".eq_ignore_ascii_case(s) { - Ok(RelationType::Prev) - } else if "predecessor-version".eq_ignore_ascii_case(s) { - Ok(RelationType::PredecessorVersion) - } else if "previous".eq_ignore_ascii_case(s) { - Ok(RelationType::Previous) - } else if "prev-archive".eq_ignore_ascii_case(s) { - Ok(RelationType::PrevArchive) - } else if "related".eq_ignore_ascii_case(s) { - Ok(RelationType::Related) - } else if "replies".eq_ignore_ascii_case(s) { - Ok(RelationType::Replies) - } else if "section".eq_ignore_ascii_case(s) { - Ok(RelationType::Section) - } else if "self".eq_ignore_ascii_case(s) { - Ok(RelationType::RelationTypeSelf) - } else if "service".eq_ignore_ascii_case(s) { - Ok(RelationType::Service) - } else if "start".eq_ignore_ascii_case(s) { - Ok(RelationType::Start) - } else if "stylesheet".eq_ignore_ascii_case(s) { - Ok(RelationType::Stylesheet) - } else if "subsection".eq_ignore_ascii_case(s) { - Ok(RelationType::Subsection) - } else if "successor-version".eq_ignore_ascii_case(s) { - Ok(RelationType::SuccessorVersion) - } else if "up".eq_ignore_ascii_case(s) { - Ok(RelationType::Up) - } else if "version-history".eq_ignore_ascii_case(s) { - Ok(RelationType::VersionHistory) - } else if "via".eq_ignore_ascii_case(s) { - Ok(RelationType::Via) - } else if "working-copy".eq_ignore_ascii_case(s) { - Ok(RelationType::WorkingCopy) - } else if "working-copy-of".eq_ignore_ascii_case(s) { - Ok(RelationType::WorkingCopyOf) - } else { - Ok(RelationType::ExtRelType(String::from(s))) - } - } -} - //////////////////////////////////////////////////////////////////////////////// // Utilities //////////////////////////////////////////////////////////////////////////////// @@ -925,8 +765,8 @@ mod tests { #[test] fn test_link() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .push_rev(RelationType::Next) + .push_rel(RelationType::PREVIOUS) + .push_rev(RelationType::NEXT) .set_title("previous chapter"); let link_header = b"; \ @@ -941,11 +781,11 @@ mod tests { #[test] fn test_link_multiple_values() { let first_link = LinkValue::new("/TheBook/chapter2") - .push_rel(RelationType::Previous) + .push_rel(RelationType::PREVIOUS) .set_title_star("UTF-8'de'letztes%20Kapitel"); let second_link = LinkValue::new("/TheBook/chapter4") - .push_rel(RelationType::Next) + .push_rel(RelationType::NEXT) .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); let link_header = b"; \ @@ -962,11 +802,11 @@ mod tests { #[test] fn test_link_all_attributes() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) + .push_rel(RelationType::PREVIOUS) .set_anchor("../anchor/example/") - .push_rev(RelationType::Next) + .push_rev(RelationType::NEXT) .push_href_lang("de".parse().unwrap()) - .push_media_desc(MediaDesc::Screen) + .push_media_desc(MediaDesc::SCREEN) .set_title("previous chapter") .set_title_star("title* unparsed") .set_media_type(mime::TEXT_PLAIN); @@ -986,16 +826,16 @@ mod tests { #[test] fn test_link_multiple_link_headers() { let first_link = LinkValue::new("/TheBook/chapter2") - .push_rel(RelationType::Previous) + .push_rel(RelationType::PREVIOUS) .set_title_star("UTF-8'de'letztes%20Kapitel"); let second_link = LinkValue::new("/TheBook/chapter4") - .push_rel(RelationType::Next) + .push_rel(RelationType::NEXT) .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); let third_link = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) - .push_rev(RelationType::Next) + .push_rel(RelationType::PREVIOUS) + .push_rev(RelationType::NEXT) .set_title("previous chapter"); let expected_link = Link::new(vec![first_link, second_link, third_link]); @@ -1013,11 +853,11 @@ mod tests { #[test] fn test_link_display() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") - .push_rel(RelationType::Previous) + .push_rel(RelationType::PREVIOUS) .set_anchor("/anchor/example/") - .push_rev(RelationType::Next) + .push_rev(RelationType::NEXT) .push_href_lang("de".parse().unwrap()) - .push_media_desc(MediaDesc::Screen) + .push_media_desc(MediaDesc::SCREEN) .set_title("previous chapter") .set_title_star("title* unparsed") .set_media_type(mime::TEXT_PLAIN);