add IntoHeaderPair trait

This commit is contained in:
Rob Ede 2021-01-03 00:29:02 +00:00
parent 22749150ae
commit dacf8f7b6b
No known key found for this signature in database
GPG Key ID: C2A3B36E841A91E6
9 changed files with 253 additions and 128 deletions

View File

@ -1,7 +1,10 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Changed
* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894] * `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894]
* `Response::set` and `Response::header` methods; use the respective `Response::set_header` and
`Response::append_header` methods. [#1869]
[#1894]: https://github.com/actix/actix-web/pull/1894 [#1894]: https://github.com/actix/actix-web/pull/1894
@ -32,7 +35,12 @@
[#1813]: https://github.com/actix/actix-web/pull/1813 [#1813]: https://github.com/actix/actix-web/pull/1813
[#1857]: https://github.com/actix/actix-web/pull/1857 [#1857]: https://github.com/actix/actix-web/pull/1857
[#1864]: https://github.com/actix/actix-web/pull/1864 [#1864]: https://github.com/actix/actix-web/pull/1864
<<<<<<< HEAD
[#1878]: https://github.com/actix/actix-web/pull/1878 [#1878]: https://github.com/actix/actix-web/pull/1878
=======
[#1869]: https://github.com/actix/actix-web/pull/1869
>>>>>>> 587000a2... add IntoHeaderPair trait
## 2.2.0 - 2020-11-25 ## 2.2.0 - 2020-11-25
### Added ### Added

View File

@ -42,14 +42,13 @@ use crate::httpmessage::HttpMessage;
/// let mut builder = Response::Ok(); /// let mut builder = Response::Ok();
/// builder.set(IfRange::EntityTag(EntityTag::new( /// builder.set(IfRange::EntityTag(EntityTag::new(
/// false, /// false,
/// "xyzzy".to_owned(), /// "abc".to_owned(),
/// ))); /// )));
/// ``` /// ```
/// ///
/// ```rust /// ```rust
/// use actix_http::Response;
/// use actix_http::http::header::IfRange;
/// use std::time::{Duration, SystemTime}; /// use std::time::{Duration, SystemTime};
/// use actix_http::{http::header::IfRange, Response};
/// ///
/// let mut builder = Response::Ok(); /// let mut builder = Response::Ok();
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
@ -57,9 +56,10 @@ use crate::httpmessage::HttpMessage;
/// ``` /// ```
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum IfRange { pub enum IfRange {
/// The entity-tag the client has of the resource /// The entity-tag the client has of the resource.
EntityTag(EntityTag), EntityTag(EntityTag),
/// The date when the client retrieved the resource
/// The date when the client retrieved the resource.
Date(HttpDate), Date(HttpDate),
} }

View File

@ -0,0 +1,84 @@
use std::convert::{Infallible, TryFrom};
use either::Either;
use http::{
header::{HeaderName, InvalidHeaderName, InvalidHeaderValue},
HeaderValue,
};
/// A trait for transforming things
pub trait IntoHeaderPair: Sized {
type Error;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
}
impl IntoHeaderPair for (HeaderName, HeaderValue) {
type Error = Infallible;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
Ok(self)
}
}
impl IntoHeaderPair for (HeaderName, &str) {
type Error = InvalidHeaderValue;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let value = HeaderValue::try_from(value)?;
Ok((name, value))
}
}
impl IntoHeaderPair for (&str, HeaderValue) {
type Error = InvalidHeaderName;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let name = HeaderName::try_from(name)?;
Ok((name, value))
}
}
impl IntoHeaderPair for (&str, &str) {
type Error = Either<InvalidHeaderName, InvalidHeaderValue>;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let name = HeaderName::try_from(name).map_err(Either::Left)?;
let value = HeaderValue::try_from(value).map_err(Either::Right)?;
Ok((name, value))
}
}
impl IntoHeaderPair for (HeaderName, String) {
type Error = InvalidHeaderValue;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let value = HeaderValue::try_from(&value)?;
Ok((name, value))
}
}
impl IntoHeaderPair for (String, HeaderValue) {
type Error = InvalidHeaderName;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let name = HeaderName::try_from(&name)?;
Ok((name, value))
}
}
impl IntoHeaderPair for (String, String) {
type Error = Either<InvalidHeaderName, InvalidHeaderValue>;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let name = HeaderName::try_from(&name).map_err(Either::Left)?;
let value = HeaderValue::try_from(&value).map_err(Either::Right)?;
Ok((name, value))
}
}

View File

@ -0,0 +1,97 @@
use std::convert::TryFrom;
use bytes::Bytes;
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
use mime::Mime;
/// A trait for any object that can be Converted to a `HeaderValue`
pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
type Error: Into<HttpError>;
/// Try to convert value to a Header pair value.
fn try_into(self) -> Result<HeaderValue, Self::Error>;
}
impl IntoHeaderValue for HeaderValue {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
Ok(self)
}
}
impl<'a> IntoHeaderValue for &'a str {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
self.parse()
}
}
impl<'a> IntoHeaderValue for &'a [u8] {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_bytes(self)
}
}
impl IntoHeaderValue for Bytes {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_maybe_shared(self)
}
}
impl IntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self)
}
}
impl IntoHeaderValue for String {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self)
}
}
impl IntoHeaderValue for usize {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self);
HeaderValue::try_from(s)
}
}
impl IntoHeaderValue for u64 {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self);
HeaderValue::try_from(s)
}
}
impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(format!("{}", self))
}
}

View File

@ -7,7 +7,7 @@ use http::header::{HeaderName, HeaderValue};
/// A set of HTTP headers /// A set of HTTP headers
/// ///
/// `HeaderMap` is an multi-map of [`HeaderName`] to values. /// `HeaderMap` is an multi-map of [`HeaderName`] to values.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct HeaderMap { pub struct HeaderMap {
pub(crate) inner: AHashMap<HeaderName, Value>, pub(crate) inner: AHashMap<HeaderName, Value>,
} }

View File

@ -1,10 +1,8 @@
//! Various HTTP headers. //! Various HTTP headers.
use std::{convert::TryFrom, fmt, str::FromStr}; use std::{fmt, str::FromStr};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use http::Error as HttpError;
use mime::Mime;
use percent_encoding::{AsciiSet, CONTROLS}; use percent_encoding::{AsciiSet, CONTROLS};
pub use http::header::*; pub use http::header::*;
@ -12,6 +10,9 @@ pub use http::header::*;
use crate::error::ParseError; use crate::error::ParseError;
use crate::httpmessage::HttpMessage; use crate::httpmessage::HttpMessage;
mod into_pair;
mod into_value;
mod common; mod common;
pub(crate) mod map; pub(crate) mod map;
mod shared; mod shared;
@ -19,15 +20,14 @@ pub use self::common::*;
#[doc(hidden)] #[doc(hidden)]
pub use self::shared::*; pub use self::shared::*;
pub use self::into_pair::IntoHeaderPair;
pub use self::into_value::IntoHeaderValue;
#[doc(hidden)] #[doc(hidden)]
pub use self::map::GetAll; pub use self::map::GetAll;
pub use self::map::HeaderMap; pub use self::map::HeaderMap;
/// A trait for any object that will represent a header field and value. /// A trait for any object that already represents a valid header field and value.
pub trait Header pub trait Header: IntoHeaderValue {
where
Self: IntoHeaderValue,
{
/// Returns the name of the header field /// Returns the name of the header field
fn name() -> HeaderName; fn name() -> HeaderName;
@ -35,98 +35,6 @@ where
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>; fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
} }
/// A trait for any object that can be Converted to a `HeaderValue`
pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
type Error: Into<HttpError>;
/// Try to convert value to a Header value.
fn try_into(self) -> Result<HeaderValue, Self::Error>;
}
impl IntoHeaderValue for HeaderValue {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
Ok(self)
}
}
impl<'a> IntoHeaderValue for &'a str {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
self.parse()
}
}
impl<'a> IntoHeaderValue for &'a [u8] {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_bytes(self)
}
}
impl IntoHeaderValue for Bytes {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::from_maybe_shared(self)
}
}
impl IntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self)
}
}
impl IntoHeaderValue for String {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(self)
}
}
impl IntoHeaderValue for usize {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self);
HeaderValue::try_from(s)
}
}
impl IntoHeaderValue for u64 {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
let s = format!("{}", self);
HeaderValue::try_from(s)
}
}
impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValue;
#[inline]
fn try_into(self) -> Result<HeaderValue, Self::Error> {
HeaderValue::try_from(format!("{}", self))
}
}
/// Represents supported types of content encodings /// Represents supported types of content encodings
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone, PartialEq, Debug)]
pub enum ContentEncoding { pub enum ContentEncoding {

View File

@ -1,4 +1,4 @@
//! Copied for `hyper::header::shared`; //! Originally taken from `hyper::header::shared`.
pub use self::charset::Charset; pub use self::charset::Charset;
pub use self::encoding::Encoding; pub use self::encoding::Encoding;

View File

@ -13,7 +13,7 @@ use crate::payload::Payload;
struct Cookies(Vec<Cookie<'static>>); struct Cookies(Vec<Cookie<'static>>);
/// Trait that implements general purpose operations on http messages /// Trait that implements general purpose operations on HTTP messages.
pub trait HttpMessage: Sized { pub trait HttpMessage: Sized {
/// Type of message payload stream /// Type of message payload stream
type Stream; type Stream;
@ -30,8 +30,8 @@ pub trait HttpMessage: Sized {
/// Mutable reference to a the request's extensions container /// Mutable reference to a the request's extensions container
fn extensions_mut(&self) -> RefMut<'_, Extensions>; fn extensions_mut(&self) -> RefMut<'_, Extensions>;
/// Get a header.
#[doc(hidden)] #[doc(hidden)]
/// Get a header
fn get_header<H: Header>(&self) -> Option<H> fn get_header<H: Header>(&self) -> Option<H>
where where
Self: Sized, Self: Sized,
@ -43,8 +43,8 @@ pub trait HttpMessage: Sized {
} }
} }
/// Read the request content type. If request does not contain /// Read the request content type. If request did not contain a *Content-Type* header, an empty
/// *Content-Type* header, empty str get returned. /// string is returned.
fn content_type(&self) -> &str { fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() { if let Ok(content_type) = content_type.to_str() {
@ -90,7 +90,7 @@ pub trait HttpMessage: Sized {
Ok(None) Ok(None)
} }
/// Check if request has chunked transfer encoding /// Check if request has chunked transfer encoding.
fn chunked(&self) -> Result<bool, ParseError> { fn chunked(&self) -> Result<bool, ParseError> {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
if let Ok(s) = encodings.to_str() { if let Ok(s) = encodings.to_str() {

View File

@ -14,7 +14,7 @@ use crate::body::{Body, BodyStream, MessageBody, ResponseBody};
use crate::cookie::{Cookie, CookieJar}; use crate::cookie::{Cookie, CookieJar};
use crate::error::Error; use crate::error::Error;
use crate::extensions::Extensions; use crate::extensions::Extensions;
use crate::header::{Header, IntoHeaderValue}; use crate::header::{Header, IntoHeaderPair, IntoHeaderValue};
use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::{Error as HttpError, HeaderMap, StatusCode}; use crate::http::{Error as HttpError, HeaderMap, StatusCode};
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
@ -399,35 +399,57 @@ impl ResponseBuilder {
self self
} }
/// Set a header. /// Insert a header, replacing any that existed with an equivalent field name.
/// ///
/// ```rust /// ```rust
/// use actix_http::{http, Request, Response}; /// use actix_http::{http, Request, Response};
/// ///
/// fn index(req: Request) -> Response { /// fn index(req: Request) -> Response {
/// Response::Ok() /// Response::Ok()
/// .set_header("X-TEST", "value") /// .set_header(("X-TEST", "value"))
/// .set_header(http::header::CONTENT_TYPE, "application/json") /// .set_header(ContentType(mime::APPLICATION_JSON))
/// .finish() /// .finish()
/// } /// }
/// ``` /// ```
pub fn set_header<K, V>(&mut self, key: K, value: V) -> &mut Self pub fn insert_header<H>(&mut self, header: H) -> &mut Self
where where
HeaderName: TryFrom<K>, H: IntoHeaderPair,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>, H::Error: Into<HttpError>,
V: IntoHeaderValue,
{ {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
match HeaderName::try_from(key) { match header.try_into_header_pair() {
Ok(key) => match value.try_into() { Ok((key, value)) => parts.headers.insert(key, value),
Ok(value) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()), Err(e) => self.err = Some(e.into()),
}; };
} }
self
}
/// Append a header, keeping any that existed with an equivalent field name.
///
/// ```rust
/// use actix_http::{http, Request, Response};
///
/// fn index(req: Request) -> Response {
/// Response::Ok()
/// .append_header(("X-TEST", "value"))
/// .append_header(ContentType(mime::APPLICATION_JSON))
/// .finish()
/// }
/// ```
pub fn append_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
H::Error: Into<HttpError>,
{
if let Some(parts) = parts(&mut self.head, &self.err) {
match header.try_into_header_pair() {
Ok((key, value)) => parts.headers.append(key, value),
Err(e) => self.err = Some(e.into()),
};
}
self self
} }
@ -458,7 +480,13 @@ impl ResponseBuilder {
if let Some(parts) = parts(&mut self.head, &self.err) { if let Some(parts) = parts(&mut self.head, &self.err) {
parts.set_connection_type(ConnectionType::Upgrade); parts.set_connection_type(ConnectionType::Upgrade);
} }
self.set_header(header::UPGRADE, value)
// TODO: fix signature
if let Ok(value) = value.try_into() {
self.insert_header((header::UPGRADE, value));
}
self
} }
/// Force close connection, even if it is marked as keep-alive /// Force close connection, even if it is marked as keep-alive