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
## Unreleased - 2021-xx-xx
### Changed
* `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
@ -32,7 +35,12 @@
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1857]: https://github.com/actix/actix-web/pull/1857
[#1864]: https://github.com/actix/actix-web/pull/1864
<<<<<<< HEAD
[#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
### Added

View File

@ -42,14 +42,13 @@ use crate::httpmessage::HttpMessage;
/// let mut builder = Response::Ok();
/// builder.set(IfRange::EntityTag(EntityTag::new(
/// false,
/// "xyzzy".to_owned(),
/// "abc".to_owned(),
/// )));
/// ```
///
/// ```rust
/// use actix_http::Response;
/// use actix_http::http::header::IfRange;
/// use std::time::{Duration, SystemTime};
/// use actix_http::{http::header::IfRange, Response};
///
/// let mut builder = Response::Ok();
/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24);
@ -57,9 +56,10 @@ use crate::httpmessage::HttpMessage;
/// ```
#[derive(Clone, Debug, PartialEq)]
pub enum IfRange {
/// The entity-tag the client has of the resource
/// The entity-tag the client has of the resource.
EntityTag(EntityTag),
/// The date when the client retrieved the resource
/// The date when the client retrieved the resource.
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
///
/// `HeaderMap` is an multi-map of [`HeaderName`] to values.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct HeaderMap {
pub(crate) inner: AHashMap<HeaderName, Value>,
}

View File

@ -1,10 +1,8 @@
//! Various HTTP headers.
use std::{convert::TryFrom, fmt, str::FromStr};
use std::{fmt, str::FromStr};
use bytes::{Bytes, BytesMut};
use http::Error as HttpError;
use mime::Mime;
use percent_encoding::{AsciiSet, CONTROLS};
pub use http::header::*;
@ -12,6 +10,9 @@ pub use http::header::*;
use crate::error::ParseError;
use crate::httpmessage::HttpMessage;
mod into_pair;
mod into_value;
mod common;
pub(crate) mod map;
mod shared;
@ -19,15 +20,14 @@ pub use self::common::*;
#[doc(hidden)]
pub use self::shared::*;
pub use self::into_pair::IntoHeaderPair;
pub use self::into_value::IntoHeaderValue;
#[doc(hidden)]
pub use self::map::GetAll;
pub use self::map::HeaderMap;
/// A trait for any object that will represent a header field and value.
pub trait Header
where
Self: IntoHeaderValue,
{
/// A trait for any object that already represents a valid header field and value.
pub trait Header: IntoHeaderValue {
/// Returns the name of the header field
fn name() -> HeaderName;
@ -35,98 +35,6 @@ where
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
#[derive(Copy, Clone, PartialEq, Debug)]
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::encoding::Encoding;

View File

@ -13,7 +13,7 @@ use crate::payload::Payload;
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 {
/// Type of message payload stream
type Stream;
@ -30,8 +30,8 @@ pub trait HttpMessage: Sized {
/// Mutable reference to a the request's extensions container
fn extensions_mut(&self) -> RefMut<'_, Extensions>;
/// Get a header.
#[doc(hidden)]
/// Get a header
fn get_header<H: Header>(&self) -> Option<H>
where
Self: Sized,
@ -43,8 +43,8 @@ pub trait HttpMessage: Sized {
}
}
/// Read the request content type. If request does not contain
/// *Content-Type* header, empty str get returned.
/// Read the request content type. If request did not contain a *Content-Type* header, an empty
/// string is returned.
fn content_type(&self) -> &str {
if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) {
if let Ok(content_type) = content_type.to_str() {
@ -90,7 +90,7 @@ pub trait HttpMessage: Sized {
Ok(None)
}
/// Check if request has chunked transfer encoding
/// Check if request has chunked transfer encoding.
fn chunked(&self) -> Result<bool, ParseError> {
if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) {
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::error::Error;
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::{Error as HttpError, HeaderMap, StatusCode};
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
@ -399,35 +399,57 @@ impl ResponseBuilder {
self
}
/// Set a header.
/// Insert a header, replacing any that existed with an equivalent field name.
///
/// ```rust
/// use actix_http::{http, Request, Response};
///
/// fn index(req: Request) -> Response {
/// Response::Ok()
/// .set_header("X-TEST", "value")
/// .set_header(http::header::CONTENT_TYPE, "application/json")
/// .set_header(("X-TEST", "value"))
/// .set_header(ContentType(mime::APPLICATION_JSON))
/// .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
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
H: IntoHeaderPair,
H::Error: Into<HttpError>,
{
if let Some(parts) = parts(&mut self.head, &self.err) {
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => {
parts.headers.insert(key, value);
}
Err(e) => self.err = Some(e.into()),
},
match header.try_into_header_pair() {
Ok((key, value)) => parts.headers.insert(key, value),
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
}
@ -458,7 +480,13 @@ impl ResponseBuilder {
if let Some(parts) = parts(&mut self.head, &self.err) {
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