refactor(header): change Header::fmt_header to take a header::Formatter

The `header::Formatter` ensures that a formatted header is written to a
line, and allows for headers that require multiple lines. The only
header to specifically require this is `Set-Cookie`.

BREAKING CHANGE: The `fmt_header` method has changed to take a different
  formatter. In most cases, if your header also implements
  `fmt::Display`, you can just call `f.fmt_line(self)`.
This commit is contained in:
Sean McArthur
2017-04-20 23:01:25 -07:00
parent f1859dfd7a
commit 6f02d43ae0
24 changed files with 177 additions and 162 deletions

View File

@@ -63,14 +63,14 @@ impl Header for AccessControlAllowCredentials {
Err(::Error::Header)
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("true")
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}
impl Display for AccessControlAllowCredentials {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
self.fmt_header(f)
f.write_str("true")
}
}

View File

@@ -72,18 +72,18 @@ impl Header for AccessControlAllowOrigin {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
AccessControlAllowOrigin::Any => f.write_str("*"),
AccessControlAllowOrigin::Null => f.write_str("null"),
AccessControlAllowOrigin::Value(ref url) => Display::fmt(url, f),
}
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}
impl Display for AccessControlAllowOrigin {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
self.fmt_header(f)
match *self {
AccessControlAllowOrigin::Any => f.write_str("*"),
AccessControlAllowOrigin::Null => f.write_str("null"),
AccessControlAllowOrigin::Value(ref url) => Display::fmt(url, f),
}
}
}

View File

@@ -100,8 +100,8 @@ impl<S: Scheme + Any> Header for Authorization<S> where <S as FromStr>::Err: 'st
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -49,6 +49,7 @@ pub struct CacheControl(pub Vec<CacheDirective>);
__hyper__deref!(CacheControl => Vec<CacheDirective>);
//TODO: this could just be the header! macro
impl Header for CacheControl {
fn header_name() -> &'static str {
static NAME: &'static str = "Cache-Control";
@@ -64,8 +65,8 @@ impl Header for CacheControl {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -146,8 +146,8 @@ impl Header for ContentDisposition {
}
#[inline]
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -60,8 +60,8 @@ impl Header for ContentLength {
}
#[inline]
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -54,8 +54,8 @@ impl Header for Cookie {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -54,8 +54,8 @@ impl Header for Expect {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -68,17 +68,17 @@ impl Header for Host {
from_one_raw_str(raw)
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.port {
None | Some(80) | Some(443) => f.write_str(&self.hostname[..]),
Some(port) => write!(f, "{}:{}", self.hostname, port)
}
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}
impl fmt::Display for Host {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_header(f)
match self.port {
None | Some(80) | Some(443) => f.write_str(&self.hostname[..]),
Some(port) => write!(f, "{}:{}", self.hostname, port)
}
}
}

View File

@@ -70,17 +70,17 @@ impl Header for IfRange {
Err(::Error::Header)
}
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
IfRange::EntityTag(ref x) => Display::fmt(x, f),
IfRange::Date(ref x) => Display::fmt(x, f),
}
fn fmt_header(&self, f: &mut ::header::Formatter) -> ::std::fmt::Result {
f.fmt_line(self)
}
}
impl Display for IfRange {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_header(f)
match *self {
IfRange::EntityTag(ref x) => Display::fmt(x, f),
IfRange::Date(ref x) => Display::fmt(x, f),
}
}
}

View File

@@ -423,14 +423,14 @@ impl Header for Link {
.unwrap_or(Err(::Error::Header))
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt_delimited(f, self.values.as_slice(), ", ", ("", ""))
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}
impl fmt::Display for Link {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_header(f)
fmt_delimited(f, self.values.as_slice(), ", ", ("", ""))
}
}

View File

@@ -204,14 +204,13 @@ macro_rules! header {
fn parse_header(raw: &$crate::header::Raw) -> $crate::Result<Self> {
$crate::header::parsing::from_comma_delimited(raw).map($id)
}
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
$crate::header::parsing::fmt_comma_delimited(f, &self.0[..])
fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result {
f.fmt_line(self)
}
}
impl ::std::fmt::Display for $id {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use $crate::header::Header;
self.fmt_header(f)
$crate::header::parsing::fmt_comma_delimited(f, &self.0[..])
}
}
};
@@ -229,14 +228,13 @@ macro_rules! header {
fn parse_header(raw: &$crate::header::Raw) -> $crate::Result<Self> {
$crate::header::parsing::from_comma_delimited(raw).map($id)
}
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
$crate::header::parsing::fmt_comma_delimited(f, &self.0[..])
fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result {
f.fmt_line(self)
}
}
impl ::std::fmt::Display for $id {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use $crate::header::Header;
self.fmt_header(f)
$crate::header::parsing::fmt_comma_delimited(f, &self.0[..])
}
}
};
@@ -254,8 +252,8 @@ macro_rules! header {
fn parse_header(raw: &$crate::header::Raw) -> $crate::Result<Self> {
$crate::header::parsing::from_one_raw_str(raw).map($id)
}
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::Display::fmt(&**self, f)
fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result {
f.fmt_line(self)
}
}
impl ::std::fmt::Display for $id {
@@ -289,8 +287,8 @@ macro_rules! header {
fn parse_header(raw: &$crate::header::Raw) -> $crate::Result<Self> {
$crate::header::parsing::from_one_raw_str::<<$value as ::std::borrow::ToOwned>::Owned>(raw).map($id::new)
}
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::Display::fmt(&**self, f)
fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result {
f.fmt_line(self)
}
}
impl ::std::fmt::Display for $id {
@@ -323,7 +321,12 @@ macro_rules! header {
}
$crate::header::parsing::from_comma_delimited(raw).map($id::Items)
}
fn fmt_header(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
fn fmt_header(&self, f: &mut $crate::header::Formatter) -> ::std::fmt::Result {
f.fmt_line(self)
}
}
impl ::std::fmt::Display for $id {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
$id::Any => f.write_str("*"),
$id::Items(ref fields) => $crate::header::parsing::fmt_comma_delimited(
@@ -331,12 +334,6 @@ macro_rules! header {
}
}
}
impl ::std::fmt::Display for $id {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
use $crate::header::Header;
self.fmt_header(f)
}
}
};
// optional test module

View File

@@ -104,8 +104,8 @@ impl Header for Origin {
from_one_raw_str(raw)
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -54,8 +54,8 @@ impl Header for Pragma {
})
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -65,8 +65,8 @@ impl Header for Prefer {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -63,8 +63,8 @@ impl Header for PreferenceApplied {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -190,8 +190,8 @@ impl Header for Range {
from_one_raw_str(raw)
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -79,8 +79,8 @@ impl Header for ReferrerPolicy {
Err(::Error::Header)
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -121,7 +121,13 @@ impl Header for RetryAfter {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt_header(&self, f: &mut ::header::Formatter) -> ::std::fmt::Result {
f.fmt_line(self)
}
}
impl fmt::Display for RetryAfter {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
RetryAfter::Delay(ref duration) => {
write!(f, "{}", duration.num_seconds())

View File

@@ -91,16 +91,7 @@ impl Header for SetCookie {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.0.len() == 1 {
write!(f, "{}", &self.0[0])
} else {
panic!("SetCookie with multiple cookies cannot be used with fmt_header, must use fmt_multi_header");
}
}
fn fmt_multi_header(&self, f: &mut ::header::MultilineFormatter) -> fmt::Result {
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
for cookie in &self.0 {
try!(f.fmt_line(cookie));
}

View File

@@ -129,8 +129,8 @@ impl Header for StrictTransportSecurity {
parsing::from_one_raw_str(raw)
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}

View File

@@ -96,7 +96,13 @@ impl Header for Warning {
from_one_raw_str(raw)
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}
impl fmt::Display for Warning {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.date {
Some(date) => write!(f, "{:03} {} \"{}\" \"{}\"", self.code, self.agent, self.text, date),
None => write!(f, "{:03} {} \"{}\"", self.code, self.agent, self.text)

View File

@@ -4,7 +4,7 @@ use std::fmt;
use std::str::from_utf8;
use super::cell::{OptCell, PtrMapCell};
use header::{Header, MultilineFormatter, Multi, raw, Raw};
use header::{Header, Formatter, Multi, raw, Raw};
#[derive(Clone)]
@@ -47,7 +47,7 @@ impl Item {
}
let mut raw = raw::new();
self.write_h1(&mut MultilineFormatter(Multi::Raw(&mut raw))).expect("fmt failed");
self.write_h1(&mut Formatter(Multi::Raw(&mut raw))).expect("fmt failed");
self.raw.set(raw);
self.raw.as_ref().unwrap()
@@ -93,7 +93,7 @@ impl Item {
}.map(|typed| unsafe { typed.downcast_unchecked() })
}
pub fn write_h1(&self, f: &mut MultilineFormatter) -> fmt::Result {
pub fn write_h1(&self, f: &mut Formatter) -> fmt::Result {
match *self.raw {
Some(ref raw) => {
for part in raw.iter() {
@@ -111,7 +111,7 @@ impl Item {
},
None => {
let typed = unsafe { self.typed.one() };
typed.fmt_multi_header(f)
typed.fmt_header(f)
}
}
}

View File

@@ -41,7 +41,7 @@
//!
//! ```
//! use std::fmt;
//! use hyper::header::{Header, Raw};
//! use hyper::header::{self, Header, Raw};
//!
//! #[derive(Debug, Clone, Copy)]
//! struct Dnt(bool);
@@ -66,16 +66,16 @@
//! Err(hyper::Error::Header)
//! }
//!
//! fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
//! if self.0 {
//! f.write_str("1")
//! fn fmt_header(&self, f: &mut header::Formatter) -> fmt::Result {
//! let value = if self.0 {
//! "1"
//! } else {
//! f.write_str("0")
//! }
//! "0"
//! };
//! f.fmt_line(&value)
//! }
//! }
//! ```
use std::any::{Any, TypeId};
use std::borrow::{Cow, ToOwned};
use std::iter::{FromIterator, IntoIterator};
use std::{mem, fmt};
@@ -83,6 +83,7 @@ use std::{mem, fmt};
use unicase::UniCase;
use self::internals::{Item, VecMap, Entry};
use self::sealed::{GetType, HeaderClone};
pub use self::shared::*;
pub use self::common::*;
@@ -100,7 +101,7 @@ pub mod parsing;
///
/// This trait represents the construction and identification of headers,
/// and contains trait-object unsafe methods.
pub trait Header: HeaderClone + Any + GetType + Send + Sync {
pub trait Header: HeaderClone + GetType + Send + Sync {
/// Returns the name of the header field this belongs to.
///
/// This will become an associated constant once available.
@@ -113,38 +114,51 @@ pub trait Header: HeaderClone + Any + GetType + Send + Sync {
/// than one field value. If that's the case, you **should** return `None`
/// if `raw.len() > 1`.
fn parse_header(raw: &Raw) -> ::Result<Self> where Self: Sized;
/// Format a header to be output into a TcpStream.
/// Format a header to outgoing stream.
///
/// This method is not allowed to introduce an Err not produced
/// by the passed-in Formatter.
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result;
/// Formats a header over multiple lines.
/// Most headers should be formatted on one line, and so a common pattern
/// would be to implement `std::fmt::Display` for this type as well, and
/// then just call `f.fmt_line(self)`.
///
/// ## Note
///
/// This has the ability to format a header over multiple lines.
///
/// The main example here is `Set-Cookie`, which requires that every
/// cookie being set be specified in a separate line.
///
/// The API here is still being explored, so this is hidden by default.
/// The passed in formatter doesn't have any public methods, so it would
/// be quite difficult to depend on this externally.
#[doc(hidden)]
/// cookie being set be specified in a separate line. Almost every other
/// case should only format as 1 single line.
#[inline]
fn fmt_multi_header(&self, f: &mut MultilineFormatter) -> fmt::Result {
f.fmt_line(&FmtHeader(self))
}
fn fmt_header(&self, f: &mut Formatter) -> fmt::Result;
}
#[doc(hidden)]
pub trait GetType: Any {
mod sealed {
use std::any::{Any, TypeId};
use super::Header;
#[doc(hidden)]
pub trait GetType: Any {
#[inline(always)]
fn get_type(&self) -> TypeId {
TypeId::of::<Self>()
}
}
}
impl<T: Any> GetType for T {}
impl<T: Any> GetType for T {}
#[test]
fn test_get_type() {
#[doc(hidden)]
pub trait HeaderClone {
fn clone_box(&self) -> Box<Header + Send + Sync>;
}
impl<T: Header + Clone> HeaderClone for T {
#[inline]
fn clone_box(&self) -> Box<Header + Send + Sync> {
Box::new(self.clone())
}
}
#[test]
fn test_get_type() {
use ::header::{ContentLength, UserAgent};
let len = ContentLength(5);
@@ -158,11 +172,13 @@ fn test_get_type() {
assert_eq!(TypeId::of::<ContentLength>(), (*len).get_type());
assert_eq!(TypeId::of::<UserAgent>(), (*agent).get_type());
}
}
#[doc(hidden)]
/// A formatter used to serialize headers to an output stream.
#[allow(missing_debug_implementations)]
pub struct MultilineFormatter<'a, 'b: 'a>(Multi<'a, 'b>);
pub struct Formatter<'a, 'b: 'a>(Multi<'a, 'b>);
enum Multi<'a, 'b: 'a> {
Line(&'a str, &'a mut fmt::Formatter<'b>),
@@ -170,8 +186,20 @@ enum Multi<'a, 'b: 'a> {
Raw(&'a mut Raw),
}
impl<'a, 'b> MultilineFormatter<'a, 'b> {
fn fmt_line(&mut self, line: &fmt::Display) -> fmt::Result {
impl<'a, 'b> Formatter<'a, 'b> {
/// Format one 'line' of a header.
///
/// This writes the header name plus the `Display` value as a single line.
///
/// ## Note
///
/// This has the ability to format a header over multiple lines.
///
/// The main example here is `Set-Cookie`, which requires that every
/// cookie being set be specified in a separate line. Almost every other
/// case should only format as 1 single line.
pub fn fmt_line(&mut self, line: &fmt::Display) -> fmt::Result {
use std::fmt::Write;
match self.0 {
Multi::Line(ref name, ref mut f) => {
@@ -198,28 +226,19 @@ impl<'a, 'b> MultilineFormatter<'a, 'b> {
}
}
// Internal helper to wrap fmt_header into a fmt::Display
struct FmtHeader<'a, H: ?Sized + 'a>(&'a H);
impl<'a, H: Header + ?Sized + 'a> fmt::Display for FmtHeader<'a, H> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt_header(f)
}
}
struct ValueString<'a>(&'a Item);
impl<'a> fmt::Debug for ValueString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(f.write_str("\""));
try!(self.0.write_h1(&mut MultilineFormatter(Multi::Join(true, f))));
try!(self.0.write_h1(&mut Formatter(Multi::Join(true, f))));
f.write_str("\"")
}
}
impl<'a> fmt::Display for ValueString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.write_h1(&mut MultilineFormatter(Multi::Join(true, f)))
self.0.write_h1(&mut Formatter(Multi::Join(true, f)))
}
}
@@ -229,7 +248,7 @@ impl<'a, H: Header> fmt::Debug for HeaderValueString<'a, H> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(f.write_str("\""));
try!(self.0.fmt_multi_header(&mut MultilineFormatter(Multi::Join(true, f))));
try!(self.0.fmt_header(&mut Formatter(Multi::Join(true, f))));
f.write_str("\"")
}
}
@@ -254,17 +273,6 @@ impl<'a, F: fmt::Write + 'a> fmt::Write for NewlineReplacer<'a, F> {
}
}
#[doc(hidden)]
pub trait HeaderClone {
fn clone_box(&self) -> Box<Header + Send + Sync>;
}
impl<T: Header + Clone> HeaderClone for T {
#[inline]
fn clone_box(&self) -> Box<Header + Send + Sync> {
Box::new(self.clone())
}
}
impl Header + Send + Sync {
// A trait object looks like this:
@@ -606,7 +614,7 @@ impl<'a> HeaderView<'a> {
impl<'a> fmt::Display for HeaderView<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.1.write_h1(&mut MultilineFormatter(Multi::Line(self.0.as_ref(), f)))
self.1.write_h1(&mut Formatter(Multi::Line(self.0.as_ref(), f)))
}
}
@@ -746,7 +754,13 @@ mod tests {
}
}
fn fmt_header(&self, f: &mut fmt::Formatter) -> fmt::Result {
fn fmt_header(&self, f: &mut super::Formatter) -> fmt::Result {
f.fmt_line(self)
}
}
impl fmt::Display for CrazyLength {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let CrazyLength(ref opt, ref value) = *self;
write!(f, "{:?}, {:?}", opt, value)
}