Skip to content

Commit

Permalink
Response: add Error storage, retreival, conversion
Browse files Browse the repository at this point in the history
This allows for robust creation of Response-s directly from Error-s, with error
capture for future reference, and retreival via `error() -> Option<&Error>`.

Refs: http-rs#169
Refs: http-rs/tide#546
Refs: http-rs/tide#532
Refs: http-rs/tide#452
  • Loading branch information
Fishrock123 committed Jun 5, 2020
1 parent 8090139 commit 93a8607
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 5 deletions.
112 changes: 108 additions & 4 deletions src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use crate::headers::{
self, HeaderName, HeaderValue, HeaderValues, Headers, Names, ToHeaderValues, Values,
CONTENT_TYPE,
};
use crate::mime::Mime;
use crate::mime::{self, Mime};
use crate::trailers::{self, Trailers};
use crate::{Body, Extensions, StatusCode, Version};
use crate::{Body, Error, Extensions, StatusCode, Version};

cfg_unstable! {
use crate::upgrade;
Expand Down Expand Up @@ -49,6 +49,7 @@ pin_project_lite::pin_project! {
ext: Extensions,
local_addr: Option<String>,
peer_addr: Option<String>,
error: Option<Error>,
}
}

Expand Down Expand Up @@ -83,6 +84,7 @@ pin_project_lite::pin_project! {
ext: Extensions,
local_addr: Option<String>,
peer_addr: Option<String>,
error: Option<Error>,
}
}

Expand All @@ -108,6 +110,7 @@ impl Response {
ext: Extensions::new(),
peer_addr: None,
local_addr: None,
error: None,
}
}

Expand Down Expand Up @@ -136,9 +139,93 @@ impl Response {
ext: Extensions::new(),
peer_addr: None,
local_addr: None,
// XXX(Jeremiah): should this be autogenerated on 4xx and 5xx codes?
error: None,
}
}

/// Create a new response from an `http_types::Error`.
///
/// This will store the error in the `Response`, allowing it to later be
/// checked via `Response::error()`.
///
/// If the `Error`'s status had a status code that was not a 5XX server
/// error code, the response body will be set to the error's message, and
/// the content-type header will be set to `http_types::mime::Plain`.
#[cfg(not(feature = "unstable"))]
pub fn from_error(error: Error) -> Self {
// Only send the message if it is a non-500 range error. All
// errors default to 500 by default, so sending the error
// body is opt-in at the call site.
let was_server_error = error.status().is_server_error();
let body = if !was_server_error {
Body::from_string(error.to_string())
} else {
Body::empty()
};

let (trailers_sender, trailers_receiver) = sync::channel(1);
let mut res = Self {
status: error.status(),
headers: Headers::new(),
version: None,
body,
trailers_sender: Some(trailers_sender),
trailers_receiver: Some(trailers_receiver),
ext: Extensions::new(),
peer_addr: None,
local_addr: None,
error: Some(error),
};
if !was_server_error {
res.set_content_type(mime::PLAIN);
}
res
}

/// Create a new response from an `http_types::Error`.
///
/// This will store the error in the `Response`, allowing it to later be
/// checked via `Response::error()`.
///
/// If the `Error`'s status had a status code that was not a 5XX server
/// error code, the response body will be set to the error's message, and
/// the content-type header will be set to `http_types::mime::Plain`.
#[cfg(feature = "unstable")]
pub fn from_error(error: Error) -> Self {
// Only send the message if it is a non-500 range error. All
// errors default to 500 by default, so sending the error
// body is opt-in at the call site.
let was_server_error = error.status().is_server_error();
let body = if !was_server_error {
Body::from_string(error.to_string())
} else {
Body::empty()
};

let (trailers_sender, trailers_receiver) = sync::channel(1);
let (upgrade_sender, upgrade_receiver) = sync::channel(1);
let mut res = Self {
status: error.status(),
headers: Headers::new(),
version: None,
body,
trailers_sender: Some(trailers_sender),
trailers_receiver: Some(trailers_receiver),
upgrade_sender: Some(upgrade_sender),
upgrade_receiver: Some(upgrade_receiver),
has_upgrade: false,
ext: Extensions::new(),
peer_addr: None,
local_addr: None,
error: Some(error),
};
if !was_server_error {
res.set_content_type(mime::PLAIN);
}
res
}

/// Get the status
pub fn status(&self) -> StatusCode {
self.status
Expand Down Expand Up @@ -465,6 +552,16 @@ impl Response {
self.body.is_empty()
}

/// Returns an optional reference to the `Error` if the response was created from one, or else `None`.
pub fn error(&mut self) -> Option<&Error> {
self.error.as_ref()
}

/// Takes the `Error` from the response if one exists, replacing it with `None`.
pub fn take_error(&mut self) -> Option<Error> {
self.error.take()
}

/// Get the HTTP version, if one has been set.
///
/// # Examples
Expand Down Expand Up @@ -631,8 +728,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()`, removing
/// extensions, and unsetting any `Error`.
fn clone(&self) -> Self {
Self {
status: self.status.clone(),
Expand All @@ -650,6 +747,7 @@ impl Clone for Response {
ext: Extensions::new(),
peer_addr: self.peer_addr.clone(),
local_addr: self.local_addr.clone(),
error: None,
}
}
}
Expand Down Expand Up @@ -722,6 +820,12 @@ impl Index<&str> for Response {
}
}

impl From<Error> for Response {
fn from(e: Error) -> Self {
Self::from_error(e)
}
}

impl From<StatusCode> for Response {
fn from(s: StatusCode) -> Self {
Response::new(s)
Expand Down
22 changes: 21 additions & 1 deletion tests/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use http_types::{bail, ensure, ensure_eq, Error, StatusCode};
use http_types::{bail, ensure, ensure_eq, Error, StatusCode, Response};
use std::io;

#[test]
Expand Down Expand Up @@ -71,3 +71,23 @@ fn option_ext() {
let err = res.unwrap_err();
assert_eq!(err.status(), StatusCode::NotFound);
}

#[async_std::test]
async fn to_response() {
let msg = "This is an error";

let error = Error::from_str(StatusCode::NotFound, msg);
let mut res = Response::from_error(error);

assert!(res.error().is_some());
// Ensure we did not consume the error
assert!(res.error().is_some());

assert_eq!(res.error().unwrap().status(), StatusCode::NotFound);
assert_eq!(res.error().unwrap().to_string(), msg);

res.take_error();
assert!(res.error().is_none());

assert_eq!(res.body_string().await.unwrap(), msg);
}

0 comments on commit 93a8607

Please sign in to comment.