Skip to content

Create a new AcceptEncoding header, and supporting structs/enums #70

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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ headers-core = { version = "0.2", path = "./headers-core" }
base64 = "0.12"
bitflags = "1.0"
bytes = "0.5"
itertools = "0.9"
mime = "0.3.14"
sha-1 = "0.8"
time = "0.1.34"
Expand Down
156 changes: 156 additions & 0 deletions src/common/accept_encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
use std::convert::TryFrom;

use {ContentCoding, HeaderValue};
use util::{QualityValue, TryFromValues};

/// `Accept-Encoding` header, defined in
/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.4)
///
/// The `Accept-Encoding` header field can be used by user agents to
/// indicate what response content-codings are acceptable in the response.
/// An "identity" token is used as a synonym for "no encoding" in
/// order to communicate when no encoding is preferred.
///
/// # ABNF
///
/// ```text
/// Accept-Encoding = #( codings [ weight ] )
/// codings = content-coding / "identity" / "*"
/// ```
///
/// # Example Values
///
/// * `gzip`
/// * `br;q=1.0, gzip;q=0.8`
///
#[derive(Clone, Debug)]
pub struct AcceptEncoding(pub QualityValue);

derive_header! {
AcceptEncoding(_),
name: ACCEPT_ENCODING
}

impl AcceptEncoding {
/// Convience method to create an `Accept-Encoding: gzip` header
#[inline]
pub fn gzip() -> AcceptEncoding {
AcceptEncoding(HeaderValue::from_static("gzip").into())
}

/// A convience method to create an Accept-Encoding header from pairs of values and qualities
///
/// # Example
///
/// ```
/// use headers::AcceptEncoding;
///
/// let pairs = vec![("gzip", 1.0), ("deflate", 0.8)];
/// let header = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter());
/// ```
pub fn from_quality_pairs<'i, I>(pairs: &mut I) -> Result<AcceptEncoding, ::Error>
where
I: Iterator<Item = (&'i str, f32)>,
{
let values: Vec<HeaderValue> = pairs
.map(|pair| {
QualityValue::try_from(pair).map(|qual: QualityValue| HeaderValue::from(qual))
})
.collect::<Result<Vec<HeaderValue>, ::Error>>()?;
let value = QualityValue::try_from_values(&mut values.iter())?;
Ok(AcceptEncoding(value))
}

/// Returns the most prefered encoding that is specified by the header,
/// if one is specified.
///
/// Note: This peeks at the underlying iter, not modifying it.
///
/// # Example
///
/// ```
/// use headers::{AcceptEncoding, ContentCoding};
///
/// let pairs = vec![("gzip", 1.0), ("deflate", 0.8)];
/// let accept_enc = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()).unwrap();
/// let mut encodings = accept_enc.sorted_encodings();
///
/// assert_eq!(accept_enc.prefered_encoding(), Some(ContentCoding::GZIP));
/// ```
pub fn prefered_encoding(&self) -> Option<ContentCoding> {
self.0.iter().peekable().peek().map(|s| ContentCoding::from_str(*s))
}

/// Returns a quality sorted iterator of the `ContentCoding`
///
/// # Example
///
/// ```
/// use headers::{AcceptEncoding, ContentCoding, HeaderValue};
///
/// let val = HeaderValue::from_static("deflate, gzip;q=1.0, br;q=0.8");
/// let accept_enc = AcceptEncoding(val.into());
/// let mut encodings = accept_enc.sorted_encodings();
///
/// assert_eq!(encodings.next(), Some(ContentCoding::DEFLATE));
/// assert_eq!(encodings.next(), Some(ContentCoding::GZIP));
/// assert_eq!(encodings.next(), Some(ContentCoding::BROTLI));
/// assert_eq!(encodings.next(), None);
/// ```
pub fn sorted_encodings<'a>(&'a self) -> impl Iterator<Item = ContentCoding> + 'a {
self.0.iter().map(|s| ContentCoding::from_str(s))
}

/// Returns a quality sorted iterator of values
///
/// # Example
///
/// ```
/// use headers::{AcceptEncoding, ContentCoding, HeaderValue};
///
/// let val = HeaderValue::from_static("deflate, gzip;q=1.0, br;q=0.8");
/// let accept_enc = AcceptEncoding(val.into());
/// let mut encodings = accept_enc.sorted_values();
///
/// assert_eq!(encodings.next(), Some("deflate"));
/// assert_eq!(encodings.next(), Some("gzip"));
/// assert_eq!(encodings.next(), Some("br"));
/// assert_eq!(encodings.next(), None);
/// ```
pub fn sorted_values(&self) -> impl Iterator<Item = &str> {
self.0.iter()
}
}

#[cfg(test)]
mod tests {
use super::*;
use {ContentCoding, HeaderValue};

#[test]
fn from_static() {
let val = HeaderValue::from_static("deflate, gzip;q=1.0, br;q=0.9");
let accept_enc = AcceptEncoding(val.into());

assert_eq!(accept_enc.prefered_encoding(), Some(ContentCoding::DEFLATE));

let mut encodings = accept_enc.sorted_encodings();
assert_eq!(encodings.next(), Some(ContentCoding::DEFLATE));
assert_eq!(encodings.next(), Some(ContentCoding::GZIP));
assert_eq!(encodings.next(), Some(ContentCoding::BROTLI));
assert_eq!(encodings.next(), None);
}

#[test]
fn from_pairs() {
let pairs = vec![("gzip", 1.0), ("br", 0.9)];
let accept_enc = AcceptEncoding::from_quality_pairs(&mut pairs.into_iter()).unwrap();

assert_eq!(accept_enc.prefered_encoding(), Some(ContentCoding::GZIP));

let mut encodings = accept_enc.sorted_encodings();
assert_eq!(encodings.next(), Some(ContentCoding::GZIP));
assert_eq!(encodings.next(), Some(ContentCoding::BROTLI));
assert_eq!(encodings.next(), None);
}
}
139 changes: 139 additions & 0 deletions src/common/content_coding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
use HeaderValue;

// Derives an enum to represent content codings and some helpful impls
macro_rules! define_content_coding {
($($coding:ident; $str:expr,)+) => {
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
/// Values that are used with headers like [`Content-Encoding`](self::ContentEncoding) or
/// [`Accept-Encoding`](self::AcceptEncoding)
///
/// [RFC7231](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml)
pub enum ContentCoding {
$(
#[doc = $str]
$coding,
)+
}

impl ContentCoding {
/// Returns a `&'static str` for a `ContentCoding`
///
/// # Example
///
/// ```
/// use headers::ContentCoding;
///
/// let coding = ContentCoding::BROTLI;
/// assert_eq!(coding.to_static(), "br");
/// ```
#[inline]
pub fn to_static(&self) -> &'static str {
match *self {
$(ContentCoding::$coding => $str,)+
}
}

/// Given a `&str` returns a `ContentCoding`
///
/// Note this will never fail, in the case of `&str` being an invalid content coding,
/// will return `ContentCoding::IDENTITY` because `'identity'` is generally always an
/// accepted coding.
///
/// # Example
///
/// ```
/// use headers::ContentCoding;
///
/// let invalid = ContentCoding::from_str("not a valid coding");
/// assert_eq!(invalid, ContentCoding::IDENTITY);
///
/// let valid = ContentCoding::from_str("gzip");
/// assert_eq!(valid, ContentCoding::GZIP);
/// ```
#[inline]
pub fn from_str(s: &str) -> Self {
ContentCoding::try_from_str(s).unwrap_or_else(|_| ContentCoding::IDENTITY)
}

/// Given a `&str` will try to return a `ContentCoding`
///
/// Different from `ContentCoding::from_str(&str)`, if `&str` is an invalid content
/// coding, it will return `Err(())`
///
/// # Example
///
/// ```
/// use headers::ContentCoding;
///
/// let invalid = ContentCoding::try_from_str("not a valid coding");
/// assert!(invalid.is_err());
///
/// let valid = ContentCoding::try_from_str("gzip");
/// assert_eq!(valid.unwrap(), ContentCoding::GZIP);
/// ```
#[inline]
pub fn try_from_str(s: &str) -> Result<Self, ()> {
match s {
$(
stringify!($coding)
| $str => Ok(ContentCoding::$coding),
)+
_ => Err(())
}
}
}

impl std::string::ToString for ContentCoding {
#[inline]
fn to_string(&self) -> String {
match *self {
$(ContentCoding::$coding => $str.to_string(),)+
}
}
}

impl From<ContentCoding> for HeaderValue {
fn from(coding: ContentCoding) -> HeaderValue {
match coding {
$(ContentCoding::$coding => HeaderValue::from_static($str),)+
}
}
}
}
}

define_content_coding! {
BROTLI; "br",
COMPRESS; "compress",
DEFLATE; "deflate",
GZIP; "gzip",
IDENTITY; "identity",
}

#[cfg(test)]
mod tests {
use super::ContentCoding;

#[test]
fn to_static() {
assert_eq!(ContentCoding::GZIP.to_static(), "gzip");
}

#[test]
fn to_string() {
assert_eq!(ContentCoding::DEFLATE.to_string(), "deflate".to_string());
}

#[test]
fn from_str() {
assert_eq!(ContentCoding::from_str("br"), ContentCoding::BROTLI);
assert_eq!(ContentCoding::from_str("GZIP"), ContentCoding::GZIP);
assert_eq!(ContentCoding::from_str("blah blah"), ContentCoding::IDENTITY);
}

#[test]
fn try_from_str() {
assert_eq!(ContentCoding::try_from_str("br"), Ok(ContentCoding::BROTLI));
assert_eq!(ContentCoding::try_from_str("blah blah"), Err(()));
}
}
6 changes: 4 additions & 2 deletions src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
//! is used, such as `ContentType(pub Mime)`.

//pub use self::accept_charset::AcceptCharset;
//pub use self::accept_encoding::AcceptEncoding;
pub use self::accept_encoding::AcceptEncoding;
//pub use self::accept_language::AcceptLanguage;
pub use self::accept_ranges::AcceptRanges;
//pub use self::accept::Accept;
Expand All @@ -23,6 +23,7 @@ pub use self::allow::Allow;
pub use self::authorization::Authorization;
pub use self::cache_control::CacheControl;
pub use self::connection::Connection;
pub use self::content_coding::ContentCoding;
pub use self::content_disposition::ContentDisposition;
pub use self::content_encoding::ContentEncoding;
//pub use self::content_language::ContentLanguage;
Expand Down Expand Up @@ -127,7 +128,7 @@ macro_rules! bench_header {

//mod accept;
//mod accept_charset;
//mod accept_encoding;
mod accept_encoding;
//mod accept_language;
mod accept_ranges;
mod access_control_allow_credentials;
Expand All @@ -142,6 +143,7 @@ mod allow;
pub mod authorization;
mod cache_control;
mod connection;
mod content_coding;
mod content_disposition;
mod content_encoding;
//mod content_language;
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ extern crate bitflags;
extern crate bytes;
extern crate headers_core;
extern crate http;
extern crate itertools;
extern crate mime;
extern crate sha1;
#[cfg(all(test, feature = "nightly"))]
Expand Down
4 changes: 2 additions & 2 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub(crate) use self::fmt::fmt;
pub(crate) use self::http_date::HttpDate;
pub(crate) use self::iter::IterExt;
//pub use language_tags::LanguageTag;
//pub use self::quality_value::{Quality, QualityValue};
pub(crate) use self::quality_value::QualityValue;
pub(crate) use self::seconds::Seconds;
pub(crate) use self::value_string::HeaderValueString;

Expand All @@ -20,7 +20,7 @@ mod flat_csv;
mod fmt;
mod http_date;
mod iter;
//mod quality_value;
mod quality_value;
mod seconds;
mod value_string;

Expand Down
Loading