From 262c450f908dbf27754daff0784f0f20145036dd Mon Sep 17 00:00:00 2001 From: Pyfisch Date: Thu, 2 Apr 2015 12:41:04 +0200 Subject: [PATCH] refactor(headers): Introduce header!() macro, improve documentation The new macro handles single value headers, list headers, and list headers with at least one item. It creates the item for the header and contains its documentation. The new macro allows handling more header cases in the future, it will also be possible to include tests inside the macro. BREAKING CHANGE: Removed impl_header!() and impl_list_header!() macros, use new header!() macro. --- src/header/common/accept.rs | 54 +++++++------- src/header/common/accept_charset.rs | 27 ++++--- src/header/common/accept_encoding.rs | 27 ++++--- src/header/common/accept_language.rs | 25 ++++--- src/header/common/allow.rs | 23 +++--- src/header/common/content_encoding.rs | 32 ++++---- src/header/common/content_length.rs | 28 ++++--- src/header/common/content_type.rs | 28 ++++--- src/header/common/date.rs | 17 +++-- src/header/common/expires.rs | 20 ++++- src/header/common/if_modified_since.rs | 20 ++++- src/header/common/if_unmodified_since.rs | 21 ++++-- src/header/common/last_modified.rs | 19 +++-- src/header/common/location.rs | 32 ++++---- src/header/common/mod.rs | 95 +++++++++++++++--------- src/header/common/referer.rs | 31 ++++---- src/header/common/server.rs | 27 ++++--- src/header/common/transfer_encoding.rs | 34 ++++----- src/header/common/user_agent.rs | 32 +++++--- 19 files changed, 367 insertions(+), 225 deletions(-) diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs index 14ed97b45d..02522deaf0 100644 --- a/src/header/common/accept.rs +++ b/src/header/common/accept.rs @@ -2,39 +2,39 @@ use mime::Mime; use header::QualityItem; -/// The `Accept` header. -/// -/// The `Accept` header is used to tell a server which content-types the client -/// is capable of using. It can be a comma-separated list of `Mime`s, and the -/// priority can be indicated with a `q` parameter. -/// -/// Example: -/// -/// ``` -/// # use hyper::header::Headers; -/// # use hyper::header::Accept; -/// # use hyper::header::qitem; -/// use hyper::mime::Mime; -/// use hyper::mime::TopLevel::Text; -/// use hyper::mime::SubLevel::{Html, Xml}; -/// # let mut headers = Headers::new(); -/// headers.set(Accept(vec![ -/// qitem(Mime(Text, Html, vec![])), -/// qitem(Mime(Text, Xml, vec![])) ])); -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct Accept(pub Vec>); - -impl_list_header!(Accept, - "Accept", - Vec>); +header! { + #[doc="`Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2)"] + #[doc=""] + #[doc="The `Accept` header field can be used by user agents to specify"] + #[doc="response media types that are acceptable. Accept header fields can"] + #[doc="be used to indicate that the request is specifically limited to a"] + #[doc="small set of desired types, as in the case of a request for an"] + #[doc="in-line image"] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Accept = #( media-range [ accept-params ] )"] + #[doc=""] + #[doc="media-range = ( \"*/*\""] + #[doc=" / ( type \"/\" \"*\" )"] + #[doc=" / ( type \"/\" subtype )"] + #[doc=" ) *( OWS \";\" OWS parameter )"] + #[doc="accept-params = weight *( accept-ext )"] + #[doc="accept-ext = OWS \";\" OWS token [ \"=\" ( token / quoted-string ) ]"] + #[doc="```"] + #[doc=""] + #[doc="# Notes"] + #[doc="* Using always Mime types to represent `media-range` differs from the ABNF."] + #[doc="* **FIXME**: `accept-ext` is not supported."] + (Accept, "Accept") => (QualityItem)+ +} #[cfg(test)] mod tests { use mime::*; use header::{Header, Quality, QualityItem, qitem}; - + use super::Accept; #[test] diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs index 59bb3724bd..7b36bd9d18 100644 --- a/src/header/common/accept_charset.rs +++ b/src/header/common/accept_charset.rs @@ -1,15 +1,22 @@ use header::{Charset, QualityItem}; -/// The `Accept-Charset` header -/// -/// The `Accept-Charset` header can be used by clients to indicate what -/// response charsets they accept. -#[derive(Clone, PartialEq, Debug)] -pub struct AcceptCharset(pub Vec>); - -impl_list_header!(AcceptCharset, - "Accept-Charset", - Vec>); +header! { + #[doc="`Accept-Charset` header, defined in"] + #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3)"] + #[doc=""] + #[doc="The `Accept-Charset` header field can be sent by a user agent to"] + #[doc="indicate what charsets are acceptable in textual response content."] + #[doc="This field allows user agents capable of understanding more"] + #[doc="comprehensive or special-purpose charsets to signal that capability"] + #[doc="to an origin server that is capable of representing information in"] + #[doc="those charsets."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Accept-Charset = 1#( ( charset / \"*\" ) [ weight ] )"] + #[doc="```"] + (AcceptCharset, "Accept-Charset") => (QualityItem)+ +} #[test] diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs index 1799156d76..03a254f578 100644 --- a/src/header/common/accept_encoding.rs +++ b/src/header/common/accept_encoding.rs @@ -1,15 +1,22 @@ use header::{Encoding, QualityItem}; -/// The `Accept-Encoding` header -/// -/// The `Accept-Encoding` header can be used by clients to indicate what -/// response encodings they accept. -#[derive(Clone, PartialEq, Debug)] -pub struct AcceptEncoding(pub Vec>); - -impl_list_header!(AcceptEncoding, - "Accept-Encoding", - Vec>); +header! { + #[doc="`Accept-Encoding` header, defined in"] + #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4)"] + #[doc=""] + #[doc="The `Accept-Encoding` header field can be used by user agents to"] + #[doc="indicate what response content-codings are"] + #[doc="acceptable in the response. An `identity` token is used as a synonym"] + #[doc="for \"no encoding\" in order to communicate when no encoding is"] + #[doc="preferred."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Accept-Encoding = #( codings [ weight ] )"] + #[doc="codings = content-coding / \"identity\" / \"*\""] + #[doc="```"] + (AcceptEncoding, "Accept-Encoding") => (QualityItem)* +} #[cfg(test)] mod tests { diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs index 838ebbeec5..3cc95c52ba 100644 --- a/src/header/common/accept_language.rs +++ b/src/header/common/accept_language.rs @@ -35,16 +35,21 @@ impl fmt::Display for Language { } } -/// The `Accept-Language` header -/// -/// The `Accept-Language` header can be used by clients to indicate what -/// response languages they accept. -#[derive(Clone, PartialEq, Debug)] -pub struct AcceptLanguage(pub Vec>); - -impl_list_header!(AcceptLanguage, - "Accept-Language", - Vec>); +header! { + #[doc="`Accept-Language` header, defined in"] + #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5)"] + #[doc=""] + #[doc="The `Accept-Language` header field can be used by user agents to"] + #[doc="indicate the set of natural languages that are preferred in the"] + #[doc="response."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Accept-Language = 1#( language-range [ weight ] )"] + #[doc="language-range = "] + #[doc="```"] + (AcceptLanguage, "Accept-Language") => (QualityItem)+ +} #[cfg(test)] mod tests { diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs index b3026a7b1e..41464388f8 100644 --- a/src/header/common/allow.rs +++ b/src/header/common/allow.rs @@ -1,14 +1,19 @@ use method::Method; -/// The `Allow` header. -/// See also https://tools.ietf.org/html/rfc7231#section-7.4.1 - -#[derive(Clone, PartialEq, Debug)] -pub struct Allow(pub Vec); - -impl_list_header!(Allow, - "Allow", - Vec); +header! { + #[doc="`Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1)"] + #[doc=""] + #[doc="The `Allow` header field lists the set of methods advertised as"] + #[doc="supported by the target resource. The purpose of this field is"] + #[doc="strictly to inform the recipient of valid request methods associated"] + #[doc="with the resource."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Allow = #method"] + #[doc="```"] + (Allow, "Allow") => (Method)* +} #[cfg(test)] mod tests { diff --git a/src/header/common/content_encoding.rs b/src/header/common/content_encoding.rs index 6e56c388c1..0d836bb68f 100644 --- a/src/header/common/content_encoding.rs +++ b/src/header/common/content_encoding.rs @@ -1,19 +1,23 @@ use header::Encoding; -/// The `Content-Encoding` header. -/// -/// This header describes the encoding of the message body. It can be -/// comma-separated, including multiple encodings. -/// -/// ```notrust -/// Content-Encoding: gzip -/// ``` -#[derive(Clone, PartialEq, Debug)] -pub struct ContentEncoding(pub Vec); - -impl_list_header!(ContentEncoding, - "Content-Encoding", - Vec); +header! { + #[doc="`Content-Encoding` header, defined in"] + #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.2.2)"] + #[doc=""] + #[doc="The `Content-Encoding` header field indicates what content codings"] + #[doc="have been applied to the representation, beyond those inherent in the"] + #[doc="media type, and thus what decoding mechanisms have to be applied in"] + #[doc="order to obtain data in the media type referenced by the Content-Type"] + #[doc="header field. Content-Encoding is primarily used to allow a"] + #[doc="representation's data to be compressed without losing the identity of"] + #[doc="its underlying media type."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Content-Encoding = 1#content-coding"] + #[doc="```"] + (ContentEncoding, "ContentEncoding") => (Encoding)+ +} bench_header!(single, ContentEncoding, { vec![b"gzip".to_vec()] }); bench_header!(multiple, ContentEncoding, { vec![b"gzip, deflate".to_vec()] }); diff --git a/src/header/common/content_length.rs b/src/header/common/content_length.rs index f65744ab89..ac6308809e 100644 --- a/src/header/common/content_length.rs +++ b/src/header/common/content_length.rs @@ -1,11 +1,21 @@ -/// The `Content-Length` header. -/// -/// Simply a wrapper around a `u64`. -#[derive(Copy, Clone, PartialEq, Debug)] -pub struct ContentLength(pub u64); - -impl_header!(ContentLength, - "Content-Length", - u64); +header! { + #[doc="`Content-Length` header, defined in"] + #[doc="[RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2)"] + #[doc=""] + #[doc="When a message does not have a `Transfer-Encoding` header field, a"] + #[doc="Content-Length header field can provide the anticipated size, as a"] + #[doc="decimal number of octets, for a potential payload body. For messages"] + #[doc="that do include a payload body, the Content-Length field-value"] + #[doc="provides the framing information necessary for determining where the"] + #[doc="body (and message) ends. For messages that do not include a payload"] + #[doc="body, the Content-Length indicates the size of the selected"] + #[doc="representation."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Content-Length = 1*DIGIT"] + #[doc="```"] + (ContentLength, "Content-Length") => [u64] +} bench_header!(bench, ContentLength, { vec![b"42349984".to_vec()] }); diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs index 264ed257b9..427de5eef8 100644 --- a/src/header/common/content_type.rs +++ b/src/header/common/content_type.rs @@ -1,14 +1,22 @@ use mime::Mime; -/// The `Content-Type` header. -/// -/// Used to describe the MIME type of message body. Can be used with both -/// requests and responses. -#[derive(Clone, PartialEq, Debug)] -pub struct ContentType(pub Mime); - -impl_header!(ContentType, - "Content-Type", - Mime); +header! { + #[doc="`Content-Type` header, defined in"] + #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5)"] + #[doc=""] + #[doc="The `Content-Type` header field indicates the media type of the"] + #[doc="associated representation: either the representation enclosed in the"] + #[doc="message payload or the selected representation, as determined by the"] + #[doc="message semantics. The indicated media type defines both the data"] + #[doc="format and how that data is intended to be processed by a recipient,"] + #[doc="within the scope of the received message semantics, after any content"] + #[doc="codings indicated by Content-Encoding are decoded."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Content-Type = media-type"] + #[doc="```"] + (ContentType, "Content-Type") => [Mime] +} bench_header!(bench, ContentType, { vec![b"application/json; charset=utf-8".to_vec()] }); diff --git a/src/header/common/date.rs b/src/header/common/date.rs index 47ca75daa9..70fcb5fed5 100644 --- a/src/header/common/date.rs +++ b/src/header/common/date.rs @@ -1,10 +1,17 @@ use header::HttpDate; -/// The `Date` header field. -#[derive(Copy, PartialEq, Clone, Debug)] -pub struct Date(pub HttpDate); - -impl_header!(Date, "Date", HttpDate); +header! { + #[doc="`Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2)"] + #[doc=""] + #[doc="The `Date` header field represents the date and time at which the"] + #[doc="message was originated."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Date = HTTP-date"] + #[doc="```"] + (Date, "Date") => [HttpDate] +} bench_header!(imf_fixdate, Date, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, Date, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs index 57819180ba..613ef6a0e0 100644 --- a/src/header/common/expires.rs +++ b/src/header/common/expires.rs @@ -1,9 +1,21 @@ use header::HttpDate; -/// The `Expires` header field. -#[derive(Copy, PartialEq, Clone, Debug)] -pub struct Expires(pub HttpDate); -impl_header!(Expires, "Expires", HttpDate); +header! { + #[doc="`Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3)"] + #[doc=""] + #[doc="The `Expires` header field gives the date/time after which the"] + #[doc="response is considered stale."] + #[doc=""] + #[doc="The presence of an Expires field does not imply that the original"] + #[doc="resource will change or cease to exist at, before, or after that"] + #[doc="time."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Expires = HTTP-date"] + #[doc="```"] + (Expires, "Expires") => [HttpDate] +} bench_header!(imf_fixdate, Expires, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, Expires, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs index 53dae88a12..b845320393 100644 --- a/src/header/common/if_modified_since.rs +++ b/src/header/common/if_modified_since.rs @@ -1,9 +1,21 @@ use header::HttpDate; -/// The `If-Modified-Since` header field. -#[derive(Copy, PartialEq, Clone, Debug)] -pub struct IfModifiedSince(pub HttpDate); -impl_header!(IfModifiedSince, "If-Modified-Since", HttpDate); +header! { + #[doc="`If-Modified-Since` header, defined in"] + #[doc="[RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3)"] + #[doc=""] + #[doc="The `If-Modified-Since` header field makes a GET or HEAD request"] + #[doc="method conditional on the selected representation's modification date"] + #[doc="being more recent than the date provided in the field-value."] + #[doc="Transfer of the selected representation's data is avoided if that"] + #[doc="data has not changed."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="If-Unmodified-Since = HTTP-date"] + #[doc="```"] + (IfModifiedSince, "If-Modified-Since") => [HttpDate] +} bench_header!(imf_fixdate, IfModifiedSince, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, IfModifiedSince, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs index 88d0f48146..200d030d5a 100644 --- a/src/header/common/if_unmodified_since.rs +++ b/src/header/common/if_unmodified_since.rs @@ -1,10 +1,21 @@ use header::HttpDate; -/// The `If-Unmodified-Since` header field. -#[derive(Copy, PartialEq, Clone, Debug)] -pub struct IfUnmodifiedSince(pub HttpDate); - -impl_header!(IfUnmodifiedSince, "If-Unmodified-Since", HttpDate); +header! { + #[doc="`If-Unmodified-Since` header, defined in"] + #[doc="[RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4)"] + #[doc=""] + #[doc="The `If-Unmodified-Since` header field makes the request method"] + #[doc="conditional on the selected representation's last modification date"] + #[doc="being earlier than or equal to the date provided in the field-value."] + #[doc="This field accomplishes the same purpose as If-Match for cases where"] + #[doc="the user agent does not have an entity-tag for the representation."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="If-Unmodified-Since = HTTP-date"] + #[doc="```"] + (IfUnmodifiedSince, "If-Unmodified-Since") => [HttpDate] +} bench_header!(imf_fixdate, IfUnmodifiedSince, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, IfUnmodifiedSince, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs index 5b90dce6e4..cf3f0cf564 100644 --- a/src/header/common/last_modified.rs +++ b/src/header/common/last_modified.rs @@ -1,10 +1,19 @@ use header::HttpDate; -/// The `LastModified` header field. -#[derive(Copy, PartialEq, Clone, Debug)] -pub struct LastModified(pub HttpDate); - -impl_header!(LastModified, "Last-Modified", HttpDate); +header! { + #[doc="`Last-Modified` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2)"] + #[doc=""] + #[doc="The `Last-Modified` header field in a response provides a timestamp"] + #[doc="indicating the date and time at which the origin server believes the"] + #[doc="selected representation was last modified, as determined at the"] + #[doc="conclusion of handling the request."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Expires = HTTP-date"] + #[doc="```"] + (LastModified, "Last-Modified") => [HttpDate] +} bench_header!(imf_fixdate, LastModified, { vec![b"Sun, 07 Nov 1994 08:48:37 GMT".to_vec()] }); bench_header!(rfc_850, LastModified, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); diff --git a/src/header/common/location.rs b/src/header/common/location.rs index a1232482a2..f5d23023f0 100644 --- a/src/header/common/location.rs +++ b/src/header/common/location.rs @@ -1,19 +1,19 @@ -/// The `Location` header. -/// -/// The Location response-header field is used to redirect the recipient to -/// a location other than the Request-URI for completion of the request or identification -/// of a new resource. For 201 (Created) responses, the Location is that of the new -/// resource which was created by the request. For 3xx responses, the location SHOULD -/// indicate the server's preferred URI for automatic redirection to the resource. -/// The field value consists of a single absolute URI. -/// -/// Currently is just a String, but it should probably become a better type, -/// like url::Url or something. -#[derive(Clone, PartialEq, Debug)] -pub struct Location(pub String); +header! { + #[doc="`Location` header, defined in"] + #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.2)"] + #[doc=""] + #[doc="The `Location` header field is used in some responses to refer to a"] + #[doc="specific resource in relation to the response. The type of"] + #[doc="relationship is defined by the combination of request method and"] + #[doc="status code semantics."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Location = URI-reference"] + #[doc="```"] + // TODO: Use URL + (Location, "Location") => [String] -impl_header!(Location, - "Location", - String); +} bench_header!(bench, Location, { vec![b"http://foo.com/hello:3000".to_vec()] }); diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs index 7a374b8d70..dd57a0a557 100644 --- a/src/header/common/mod.rs +++ b/src/header/common/mod.rs @@ -89,64 +89,91 @@ macro_rules! deref( ); #[macro_export] -macro_rules! impl_list_header( - ($from:ident, $name:expr, $item:ty) => { - deref!($from => $item); - - impl $crate::header::Header for $from { +macro_rules! header { + // $a:meta: Attributes associated with the header item (usually docs) + // $id:ident: Identifier of the header + // $n:expr: Lowercase name of the header + // $nn:expr: Nice name of the header + + // List header, zero or more items + ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)*) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub Vec<$item>); + deref!($id => Vec<$item>); + impl $crate::header::Header for $id { fn header_name() -> &'static str { - $name + $n } - - fn parse_header(raw: &[Vec]) -> Option<$from> { - $crate::header::parsing::from_comma_delimited(raw).map($from) + fn parse_header(raw: &[Vec]) -> Option { + $crate::header::parsing::from_comma_delimited(raw).map($id) } } - - impl $crate::header::HeaderFormat for $from { - fn fmt_header(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::parsing::fmt_comma_delimited(fmt, &self[..]) + impl $crate::header::HeaderFormat for $id { + fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + $crate::header::parsing::fmt_comma_delimited(f, &self.0[..]) } } - - impl ::std::fmt::Display for $from { + impl ::std::fmt::Display for $id { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { use $crate::header::HeaderFormat; self.fmt_header(f) } } - } -); -#[macro_export] -macro_rules! impl_header( - ($from:ident, $name:expr, $item:ty) => { - deref!($from => $item); - - impl $crate::header::Header for $from { + }; + // List header, one or more items + ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub Vec<$item>); + deref!($id => Vec<$item>); + impl $crate::header::Header for $id { fn header_name() -> &'static str { - $name + $n } - - fn parse_header(raw: &[Vec]) -> Option<$from> { - $crate::header::parsing::from_one_raw_str(raw).map($from) + fn parse_header(raw: &[Vec]) -> Option { + $crate::header::parsing::from_comma_delimited(raw).map($id) } } - - impl $crate::header::HeaderFormat for $from { + impl $crate::header::HeaderFormat for $id { fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::std::fmt::Display::fmt(&**self, f) + $crate::header::parsing::fmt_comma_delimited(f, &self.0[..]) } } - - impl ::std::fmt::Display for $from { + impl ::std::fmt::Display for $id { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { use $crate::header::HeaderFormat; self.fmt_header(f) } } - } -); + }; + // Single value header + ($(#[$a:meta])*($id:ident, $n:expr) => [$value:ty]) => { + $(#[$a])* + #[derive(Clone, Debug, PartialEq)] + pub struct $id(pub $value); + deref!($id => $value); + impl $crate::header::Header for $id { + fn header_name() -> &'static str { + $n + } + fn parse_header(raw: &[Vec]) -> Option { + $crate::header::parsing::from_one_raw_str(raw).map($id) + } + } + impl $crate::header::HeaderFormat for $id { + fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&**self, f) + } + } + impl ::std::fmt::Display for $id { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&**self, f) + } + } + }; +} mod access_control; mod accept; diff --git a/src/header/common/referer.rs b/src/header/common/referer.rs index 4839401538..ab720537ea 100644 --- a/src/header/common/referer.rs +++ b/src/header/common/referer.rs @@ -1,16 +1,19 @@ -/// The `Referer` header. -/// -/// The Referer header is used by user agents to inform server about -/// the page URL user has came from. -/// -/// See alse [RFC 1945, section 10.13](http://tools.ietf.org/html/rfc1945#section-10.13). -/// -/// Currently just a string, but maybe better replace it with url::Url or something like it. -#[derive(Clone, PartialEq, Debug)] -pub struct Referer(pub String); - -impl_header!(Referer, - "Referer", - String); +header! { + #[doc="`Referer` header, defined in"] + #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.2)"] + #[doc=""] + #[doc="The `Referer` [sic] header field allows the user agent to specify a"] + #[doc="URI reference for the resource from which the target URI was obtained"] + #[doc="(i.e., the \"referrer\", though the field name is misspelled). A user"] + #[doc="agent MUST NOT include the fragment and userinfo components of the"] + #[doc="URI reference, if any, when generating the Referer field value."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Referer = absolute-URI / partial-URI"] + #[doc="```"] + // TODO: Use URL + (Referer, "Referer") => [String] +} bench_header!(bench, Referer, { vec![b"http://foo.com/hello:3000".to_vec()] }); diff --git a/src/header/common/server.rs b/src/header/common/server.rs index 2b95033e5e..476d4bc083 100644 --- a/src/header/common/server.rs +++ b/src/header/common/server.rs @@ -1,11 +1,20 @@ -/// The `Server` header field. -/// -/// They can contain any value, so it just wraps a `String`. -#[derive(Clone, PartialEq, Debug)] -pub struct Server(pub String); - -impl_header!(Server, - "Server", - String); +header! { + #[doc="`Server` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.2)"] + #[doc=""] + #[doc="The `Server` header field contains information about the software"] + #[doc="used by the origin server to handle the request, which is often used"] + #[doc="by clients to help identify the scope of reported interoperability"] + #[doc="problems, to work around or tailor requests to avoid particular"] + #[doc="server limitations, and for analytics regarding server or operating"] + #[doc="system use. An origin server MAY generate a Server field in its"] + #[doc="responses."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Server = product *( RWS ( product / comment ) )"] + #[doc="```"] + // TODO: Maybe parse as defined in the spec? + (Server, "Server") => [String] +} bench_header!(bench, Server, { vec![b"Some String".to_vec()] }); diff --git a/src/header/common/transfer_encoding.rs b/src/header/common/transfer_encoding.rs index c031f2f55d..4e8db0e95d 100644 --- a/src/header/common/transfer_encoding.rs +++ b/src/header/common/transfer_encoding.rs @@ -1,24 +1,20 @@ use header::Encoding; -/// The `Transfer-Encoding` header. -/// -/// This header describes the encoding of the message body. It can be -/// comma-separated, including multiple encodings. -/// -/// ```notrust -/// Transfer-Encoding: gzip, chunked -/// ``` -/// -/// According to the spec, if a `Content-Length` header is not included, -/// this header should include `chunked` as the last encoding. -/// -/// The implementation uses a vector of `Encoding` values. -#[derive(Clone, PartialEq, Debug)] -pub struct TransferEncoding(pub Vec); - -impl_list_header!(TransferEncoding, - "Transfer-Encoding", - Vec); +header! { + #[doc="`Transfer-Encoding` header, defined in"] + #[doc="[RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.1)"] + #[doc=""] + #[doc="The `Transfer-Encoding` header field lists the transfer coding names"] + #[doc="corresponding to the sequence of transfer codings that have been (or"] + #[doc="will be) applied to the payload body in order to form the message"] + #[doc="body."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="Transfer-Encoding = 1#transfer-coding"] + #[doc="```"] + (TransferEncoding, "Transfer-Encoding") => (Encoding)+ +} bench_header!(normal, TransferEncoding, { vec![b"chunked, gzip".to_vec()] }); bench_header!(ext, TransferEncoding, { vec![b"ext".to_vec()] }); diff --git a/src/header/common/user_agent.rs b/src/header/common/user_agent.rs index 51112d1fba..2a481e9bab 100644 --- a/src/header/common/user_agent.rs +++ b/src/header/common/user_agent.rs @@ -1,14 +1,24 @@ -/// The `User-Agent` header field. -/// -/// They can contain any value, so it just wraps a `String`. -#[derive(Clone, PartialEq, Debug)] -pub struct UserAgent(pub String); - -impl_header!(UserAgent, - "User-Agent", - String); - -bench_header!(bench, UserAgent, { vec![b"cargo bench".to_vec()] }); +header! { + #[doc="`User-Agent` header, defined in"] + #[doc="[RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.3)"] + #[doc=""] + #[doc="The `User-Agent` header field contains information about the user"] + #[doc="agent originating the request, which is often used by servers to help"] + #[doc="identify the scope of reported interoperability problems, to work"] + #[doc="around or tailor responses to avoid particular user agent"] + #[doc="limitations, and for analytics regarding browser or operating system"] + #[doc="use. A user agent SHOULD send a User-Agent field in each request"] + #[doc="unless specifically configured not to do so."] + #[doc=""] + #[doc="# ABNF"] + #[doc="```plain"] + #[doc="User-Agent = product *( RWS ( product / comment ) )"] + #[doc="product = token [\"/\" product-version]"] + #[doc="product-version = token"] + #[doc="```"] + // TODO: Maybe write parsing according to the spec? (Split the String) + (UserAgent, "User-Agent") => [String] +} #[test] fn test_format() { use std::borrow::ToOwned;