diff --git a/README.md b/README.md index 4232a6c..5429fd8 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,12 @@ A library for parse http link header. ### Note for version 0.1.x -The version 0.1 can't correctly handle the `relative ref` which described in +The version 0.1 can't correctly handle the `relative ref` which described in + -The parsed value of version 0.1 refers to the return value of , which is a `HashMap` with the same structure. +The parsed value of version 0.1 refers to the return value of +, which is a `HashMap` with the +same structure. **So if you want to parse `relative ref`, please use version `0.2`.** @@ -53,7 +56,8 @@ assert_eq!(val.get(&Some("next".to_string())).unwrap().raw_uri, "https://api.git assert_eq!(val.get(&Some("last".to_string())).unwrap().raw_uri, "https://api.github.com/repositories/41986369/contributors?page=14"); ``` -The parsed value is a `Result, Link>, Error>`, which `Rel` and `Link` is: +The parsed value is a `Result, Link>, Error>` (aka a +`LinkMap`, which `Rel` and `Link` is: ```rust use std::collections::HashMap; @@ -71,11 +75,28 @@ pub struct Link { type Rel = String; ``` -You can see why the key of `HashMap` is `Option` because if you won't provide a `rel` type, the key will be an empty string. +You can see why the key of `HashMap` is `Option` because if you won't +provide a `rel` type, the key will be an empty string. -Refer to (October 2017), **the rel parameter MUST be present**. +Refer to (October 2017), +**the rel parameter MUST be present**. -Therefore, if you find that key is `None`, please check if you provide the `rel` type. +Therefore, if you find that key is `None`, please check if you provide the +`rel` type. + +Alternatively, use the `parse_with_rel()` function to get a `HashMap` (aka a [`RelLinkMap`](type.RelLinkMap.html)), as in: + +```rust +let link_header = r#"; rel="next", ; rel="last""#; + +let res = parse_link_header::parse_with_rel(link_header); +assert!(res.is_ok()); + +let val = res.unwrap(); +assert_eq!(val.len(), 2); +assert_eq!(val.get("next").unwrap().raw_uri, "https://api.github.com/repositories/41986369/contributors?page=2"); +assert_eq!(val.get("last").unwrap().raw_uri, "https://api.github.com/repositories/41986369/contributors?page=14"); +``` ## Feature: `url` diff --git a/src/lib.rs b/src/lib.rs index c186f7a..88e1eb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,7 +35,8 @@ //! assert_eq!(val.get(&Some("last".to_string())).unwrap().raw_uri, "https://api.github.com/repositories/41986369/contributors?page=14"); //! ``` //! -//! The parsed value is a `Result, Link>, Error>`, which `Rel` and `Link` is: +//! The parsed value is a `Result, Link>, Error>` (aka a +//! [`LinkMap`](type.LinkMap.html)), which `Rel` and `Link` is: //! //! ```rust //! use std::collections::HashMap; @@ -59,6 +60,21 @@ //! //! Therefore, if you find that key is `None`, please check if you provide the `rel` type. //! +//! Alternatively, use the `parse_with_rel()` function to get a `HashMap` (aka a +//! [`RelLinkMap`](type.RelLinkMap.html)), as in: +//! +//! ```rust +//! let link_header = r#"; rel="next", ; rel="last""#; +//! +//! let res = parse_link_header::parse_with_rel(link_header); +//! assert!(res.is_ok()); +//! +//! let val = res.unwrap(); +//! assert_eq!(val.len(), 2); +//! assert_eq!(val.get("next").unwrap().raw_uri, "https://api.github.com/repositories/41986369/contributors?page=2"); +//! assert_eq!(val.get("last").unwrap().raw_uri, "https://api.github.com/repositories/41986369/contributors?page=14"); +//! ``` +//! //! ## Feature: `url` //! //! If you enable the `url` feature, the `uri` field of struct [`Link`](struct.Link.html) will be @@ -102,6 +118,9 @@ pub enum ErrorKind { /// Malformed URI query MalformedQuery, + + /// Missing `rel` parameter when required + MissingRel, } impl fmt::Display for Error { @@ -111,6 +130,7 @@ impl fmt::Display for Error { ErrorKind::InvalidURI => write!(f, "unable to parse URI component"), ErrorKind::MalformedParam => write!(f, "malformed parameter list"), ErrorKind::MalformedQuery => write!(f, "malformed URI query"), + ErrorKind::MissingRel => write!(f, "missing 'rel' parameter"), } } } @@ -148,12 +168,32 @@ pub struct Link { type Rel = String; -/// Type alias for the parsed data returned as a `HashMap` +/// Type alias for the parsed data returned as a `HashMap` with a key of `Option`. +/// +/// This is different from [`RelLinkMap`](type.RelLinkMap.html) which has a key of `Rel`. pub type LinkMap = HashMap, Link>; -/// Parse link header into a [`LinkMap`]. +/// Type alias for the parsed data returned as a `HashMap` where the `rel` parameter is required to +/// be present. +/// +/// This is different from the [`LinkMap`](type.LinkMap.html) which has a key of `Option` +pub type RelLinkMap = HashMap; + +/// Parse link header into a [`RelLinkMap`](type.RelLinkMap.html). /// -/// [`LinkMap`]: type.LinkMap.html +/// Takes a `&str` which is the value of the HTTP `Link:` header, attempts to parse it, and returns +/// a `Result` which represents the mapping between the relationship and the link entry. +pub fn parse_with_rel(link_header: &str) -> Result { + let mut result = HashMap::new(); + + for (k, v) in parse(link_header)? { + result.insert(k.ok_or(Error(ErrorKind::MissingRel))?, v); + } + + Ok(result) +} + +/// Parse link header into a [`LinkMap`](type.LinkMap.html). /// /// Takes a `&str` which is the value of the HTTP `Link:` header, attempts to parse it, and returns /// a `Result` which represents the mapping between the relationship and the link entry. @@ -302,6 +342,75 @@ mod tests { } } + #[test] + fn parse_with_rel_works() { + let link_header = r#"; rel="next", ; rel="last""#; + let mut expected = HashMap::new(); + + expected.insert( + "next".to_string(), + Link { + uri: "https://api.github.com/repositories/41986369/contributors?page=2" + .parse() + .unwrap(), + raw_uri: "https://api.github.com/repositories/41986369/contributors?page=2" + .to_string(), + queries: [("page".to_string(), "2".to_string())] + .iter() + .cloned() + .collect(), + params: [("rel".to_string(), "next".to_string())] + .iter() + .cloned() + .collect(), + }, + ); + expected.insert( + "last".to_string(), + Link { + uri: "https://api.github.com/repositories/41986369/contributors?page=14" + .parse() + .unwrap(), + raw_uri: "https://api.github.com/repositories/41986369/contributors?page=14" + .to_string(), + queries: [("page".to_string(), "14".to_string())] + .iter() + .cloned() + .collect(), + params: [("rel".to_string(), "last".to_string())] + .iter() + .cloned() + .collect(), + }, + ); + + let parsed = parse_with_rel(link_header).unwrap(); + + assert_eq!(expected, parsed); + + #[cfg(not(feature = "url"))] + { + let mut rel_link_expected = HashMap::new(); + + rel_link_expected.insert( + "foo/bar".to_string(), + Link { + uri: "/foo/bar".parse().unwrap(), + raw_uri: "/foo/bar".to_string(), + queries: HashMap::new(), + params: [("rel".to_string(), "foo/bar".to_string())] + .iter() + .cloned() + .collect(), + }, + ); + + let rel_link_parsed = parse_with_rel(r#"; rel="foo/bar""#).unwrap(); + + assert_eq!(rel_link_expected, rel_link_parsed); + } + } + #[test] fn parse_link_header_should_err() { assert_eq!(parse("<>"), Err(Error(ErrorKind::InvalidURI))); @@ -382,6 +491,11 @@ mod tests { format!("{}", Error(ErrorKind::MalformedQuery)), "malformed URI query" ); + + assert_eq!( + format!("{}", Error(ErrorKind::MissingRel)), + "missing 'rel' parameter" + ); } #[test]