Skip to content
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

Status should take TryInto<StatusCode> #157

Merged
merged 6 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 49 additions & 29 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl Response {
/// ```
/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
/// #
/// use http_types::{Url, Method, Response, StatusCode};
/// use http_types::{Method, Response, StatusCode, Url};
///
/// let mut req = Response::new(StatusCode::Ok);
/// req.insert_header("Content-Type", "text/plain");
Expand All @@ -115,8 +115,9 @@ impl Response {

/// Append a header to the headers.
///
/// Unlike `insert` this function will not override the contents of a header, but insert a
/// header if there aren't any. Or else append to the existing list of headers.
/// Unlike `insert` this function will not override the contents of a
/// header, but insert a header if there aren't any. Or else append to
/// the existing list of headers.
///
/// # Examples
///
Expand Down Expand Up @@ -161,7 +162,7 @@ impl Response {
/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
/// # async_std::task::block_on(async {
/// #
/// use http_types::{Body, Url, Method, Response, StatusCode};
/// use http_types::{Body, Method, Response, StatusCode, Url};
///
/// let mut req = Response::new(StatusCode::Ok);
/// req.set_body("Hello, Nori!");
Expand Down Expand Up @@ -190,7 +191,7 @@ impl Response {
/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
/// # async_std::task::block_on(async {
/// #
/// use http_types::{Body, Url, Method, Response, StatusCode};
/// use http_types::{Body, Method, Response, StatusCode, Url};
///
/// let mut req = Response::new(StatusCode::Ok);
/// req.set_body("Hello, Nori!");
Expand Down Expand Up @@ -218,7 +219,7 @@ impl Response {
/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
/// # async_std::task::block_on(async {
/// #
/// use http_types::{Body, Url, Method, Response, StatusCode};
/// use http_types::{Body, Method, Response, StatusCode, Url};
///
/// let mut req = Response::new(StatusCode::Ok);
/// req.set_body("Hello, Nori!");
Expand Down Expand Up @@ -251,10 +252,10 @@ impl Response {
/// # use std::io::prelude::*;
/// # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
/// # async_std::task::block_on(async {
/// use http_types::{Body, Url, Method, Response, StatusCode};
/// use async_std::io::Cursor;
/// use http_types::{Body, Method, Response, StatusCode, Url};
///
/// let mut res = Response::new(StatusCode::Ok);
/// let mut res = Response::new(StatusCode::Ok);
/// let cursor = Cursor::new("Hello Nori");
/// let body = Body::from_reader(cursor, None);
/// res.set_body(body);
Expand All @@ -277,7 +278,7 @@ impl Response {
///
/// ```
/// # fn main() -> Result<(), http_types::Error> { async_std::task::block_on(async {
/// use http_types::{Body, Url, Method, Response, StatusCode};
/// use http_types::{Body, Method, Response, StatusCode, Url};
///
/// let bytes = vec![1, 2, 3];
/// let mut res = Response::new(StatusCode::Ok);
Expand All @@ -303,14 +304,18 @@ impl Response {
///
/// ```
/// # fn main() -> Result<(), http_types::Error> { async_std::task::block_on(async {
/// use http_types::{Body, Url, Method, Response, StatusCode};
/// use http_types::convert::{Serialize, Deserialize};
/// use http_types::convert::{Deserialize, Serialize};
/// use http_types::{Body, Method, Response, StatusCode, Url};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Cat { name: String }
/// struct Cat {
/// name: String,
/// }
///
/// let cat = Cat { name: String::from("chashu") };
/// let mut res = Response::new(StatusCode::Ok);
/// let cat = Cat {
/// name: String::from("chashu"),
/// };
/// let mut res = Response::new(StatusCode::Ok);
/// res.set_body(Body::from_json(&cat)?);
///
/// let cat: Cat = res.body_json().await?;
Expand All @@ -333,14 +338,18 @@ impl Response {
///
/// ```
/// # fn main() -> Result<(), http_types::Error> { async_std::task::block_on(async {
/// use http_types::{Body, Url, Method, Response, StatusCode};
/// use http_types::convert::{Serialize, Deserialize};
/// use http_types::convert::{Deserialize, Serialize};
/// use http_types::{Body, Method, Response, StatusCode, Url};
///
/// #[derive(Debug, Serialize, Deserialize)]
/// struct Cat { name: String }
/// struct Cat {
/// name: String,
/// }
///
/// let cat = Cat { name: String::from("chashu") };
/// let mut res = Response::new(StatusCode::Ok);
/// let cat = Cat {
/// name: String::from("chashu"),
/// };
/// let mut res = Response::new(StatusCode::Ok);
/// res.set_body(Body::from_form(&cat)?);
///
/// let cat: Cat = res.body_form().await?;
Expand Down Expand Up @@ -374,14 +383,16 @@ impl Response {

/// Get the length of the body stream, if it has been set.
///
/// This value is set when passing a fixed-size object into as the body. E.g. a string, or a
/// buffer. Consumers of this API should check this value to decide whether to use `Chunked`
/// encoding, or set the response length.
/// This value is set when passing a fixed-size object into as the body.
/// E.g. a string, or a buffer. Consumers of this API should check this
/// value to decide whether to use `Chunked` encoding, or set the
/// response length.
pub fn len(&self) -> Option<usize> {
self.body.len()
}

/// Returns `true` if the set length of the body stream is zero, `false` otherwise.
/// Returns `true` if the set length of the body stream is zero, `false`
/// otherwise.
pub fn is_empty(&self) -> Option<bool> {
self.body.is_empty()
}
Expand Down Expand Up @@ -426,7 +437,8 @@ impl Response {
self.peer_addr.as_deref()
}

/// Get the local socket address for the underlying transport, if appropriate
/// Get the local socket address for the underlying transport, if
/// appropriate
pub fn local_addr(&self) -> Option<&str> {
self.local_addr.as_deref()
}
Expand Down Expand Up @@ -477,8 +489,8 @@ impl Response {
self.headers.iter()
}

/// An iterator visiting all header pairs in arbitrary order, with mutable references to the
/// values.
/// An iterator visiting all header pairs in arbitrary order, with mutable
/// references to the values.
pub fn iter_mut(&mut self) -> headers::IterMut<'_> {
self.headers.iter_mut()
}
Expand Down Expand Up @@ -506,7 +518,7 @@ impl Response {
/// ```
/// # fn main() -> Result<(), http_types::Error> {
/// #
/// use http_types::{StatusCode, Response, Version};
/// use http_types::{Response, StatusCode, Version};
///
/// let mut res = Response::new(StatusCode::Ok);
/// res.ext_mut().insert("hello from the extension");
Expand All @@ -520,7 +532,8 @@ impl Response {
}

impl Clone for Response {
/// Clone the response, resolving the body to `Body::empty()` and removing extensions.
/// Clone the response, resolving the body to `Body::empty()` and removing
/// extensions.
fn clone(&self) -> Self {
Self {
status: self.status.clone(),
Expand Down Expand Up @@ -656,8 +669,15 @@ impl<'a> IntoIterator for &'a mut Response {
#[cfg(test)]
mod test {
use super::Response;

#[test]
fn construct_shorthand() {
fn construct_shorthand_with_valid_status_code() {
let _res = Response::new(200);
}

#[test]
#[should_panic(expected = "Could not convert into a valid `StatusCode`")]
fn construct_shorthand_with_invalid_status_code() {
let _res = Response::new(600);
}
}
89 changes: 78 additions & 11 deletions src/status.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{Error, StatusCode};
use core::convert::{Infallible, Into};
use core::convert::{Infallible, TryInto};
use std::error::Error as StdError;
use std::fmt::Debug;

/// Provides the `status` method for `Result` and `Option`.
///
Expand All @@ -9,60 +10,108 @@ pub trait Status<T, E>: private::Sealed {
/// Wrap the error value with an additional status code.
fn status<S>(self, status: S) -> Result<T, Error>
where
S: Into<StatusCode>;
S: TryInto<StatusCode>,
S::Error: Debug;

/// Wrap the error value with an additional status code that is evaluated
/// lazily only once an error does occur.
fn with_status<S, F>(self, f: F) -> Result<T, Error>
where
S: Into<StatusCode>,
S: TryInto<StatusCode>,
S::Error: Debug,
F: FnOnce() -> S;
}

impl<T, E> Status<T, E> for Result<T, E>
where
E: StdError + Send + Sync + 'static,
{
/// Wrap the error value with an additional status code.
///
/// # Panics
///
/// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
///
/// [status]: crate::Status
/// [statuscode]: crate::StatusCode
fn status<S>(self, status: S) -> Result<T, Error>
where
S: Into<StatusCode>,
S: TryInto<StatusCode>,
S::Error: Debug,
{
self.map_err(|error| {
let status = status.into();
let status = status
.try_into()
.expect("Could not convert into a valid `StatusCode`");
Error::new(status, error)
})
}

/// Wrap the error value with an additional status code that is evaluated
/// lazily only once an error does occur.
///
/// # Panics
///
/// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
///
/// [status]: crate::Status
/// [statuscode]: crate::StatusCode
fn with_status<S, F>(self, f: F) -> Result<T, Error>
where
S: Into<StatusCode>,
S: TryInto<StatusCode>,
S::Error: Debug,
F: FnOnce() -> S,
{
self.map_err(|error| {
let status = f().into();
let status = f()
.try_into()
.expect("Could not convert into a valid `StatusCode`");
Error::new(status, error)
})
}
}

impl<T> Status<T, Infallible> for Option<T> {
/// Wrap the error value with an additional status code.
///
/// # Panics
///
/// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
///
/// [status]: crate::Status
/// [statuscode]: crate::StatusCode
fn status<S>(self, status: S) -> Result<T, Error>
where
S: Into<StatusCode>,
S: TryInto<StatusCode>,
S::Error: Debug,
{
self.ok_or_else(|| {
let status = status.into();
let status = status
.try_into()
.expect("Could not convert into a valid `StatusCode`");
Error::from_str(status, "NoneError")
})
}

/// Wrap the error value with an additional status code that is evaluated
/// lazily only once an error does occur.
///
/// # Panics
///
/// Panics if [`Status`][status] is not a valid [`StatusCode`][statuscode].
///
/// [status]: crate::Status
/// [statuscode]: crate::StatusCode
fn with_status<S, F>(self, f: F) -> Result<T, Error>
where
S: Into<StatusCode>,
S: TryInto<StatusCode>,
S::Error: Debug,
F: FnOnce() -> S,
{
self.ok_or_else(|| {
let status = f().into();
let status = f()
.try_into()
.expect("Could not convert into a valid `StatusCode`");
Error::from_str(status, "NoneError")
})
}
Expand All @@ -74,3 +123,21 @@ pub(crate) mod private {
impl<T, E> Sealed for Result<T, E> {}
impl<T> Sealed for Option<T> {}
}

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

#[test]
fn construct_shorthand_with_valid_status_code() {
let _res = Some(()).status(200).unwrap();
}

#[test]
#[should_panic(expected = "Could not convert into a valid `StatusCode`")]
fn construct_shorthand_with_invalid_status_code() {
let res: Result<(), std::io::Error> =
Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!"));
let _res = res.status(600).unwrap();
}
}
Loading