Skip to content

Commit ae9790f

Browse files
committed
Response: add Error storage, retrieval, conversion
This allows for robust creation of Response-s directly from Error-s, with error capture for future reference, and retrieval via `error() -> Option<&Error>`. Refs: http-rs#169 Refs: http-rs/tide#546 Refs: http-rs/tide#532 Refs: http-rs/tide#452
1 parent 8090139 commit ae9790f

2 files changed

Lines changed: 129 additions & 5 deletions

File tree

src/response.rs

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use crate::headers::{
1313
self, HeaderName, HeaderValue, HeaderValues, Headers, Names, ToHeaderValues, Values,
1414
CONTENT_TYPE,
1515
};
16-
use crate::mime::Mime;
16+
use crate::mime::{self, Mime};
1717
use crate::trailers::{self, Trailers};
18-
use crate::{Body, Extensions, StatusCode, Version};
18+
use crate::{Body, Error, Extensions, StatusCode, Version};
1919

2020
cfg_unstable! {
2121
use crate::upgrade;
@@ -49,6 +49,7 @@ pin_project_lite::pin_project! {
4949
ext: Extensions,
5050
local_addr: Option<String>,
5151
peer_addr: Option<String>,
52+
error: Option<Error>,
5253
}
5354
}
5455

@@ -83,6 +84,7 @@ pin_project_lite::pin_project! {
8384
ext: Extensions,
8485
local_addr: Option<String>,
8586
peer_addr: Option<String>,
87+
error: Option<Error>,
8688
}
8789
}
8890

@@ -108,6 +110,7 @@ impl Response {
108110
ext: Extensions::new(),
109111
peer_addr: None,
110112
local_addr: None,
113+
error: None,
111114
}
112115
}
113116

@@ -136,9 +139,93 @@ impl Response {
136139
ext: Extensions::new(),
137140
peer_addr: None,
138141
local_addr: None,
142+
// XXX(Jeremiah): should this be autogenerated on 4xx and 5xx codes?
143+
error: None,
139144
}
140145
}
141146

147+
/// Create a new response from an `http_types::Error`.
148+
///
149+
/// This will store the error in the `Response`, allowing it to later be
150+
/// checked via `Response::error()`.
151+
///
152+
/// If the `Error`'s status had a status code that was not a 5XX server
153+
/// error code, the response body will be set to the error's message, and
154+
/// the content-type header will be set to `http_types::mime::Plain`.
155+
#[cfg(not(feature = "unstable"))]
156+
pub fn from_error(error: Error) -> Self {
157+
// Only send the message if it is a non-500 range error. All
158+
// errors default to 500 by default, so sending the error
159+
// body is opt-in at the call site.
160+
let was_server_error = error.status().is_server_error();
161+
let body = if !was_server_error {
162+
Body::from_string(error.to_string())
163+
} else {
164+
Body::empty()
165+
};
166+
167+
let (trailers_sender, trailers_receiver) = sync::channel(1);
168+
let mut res = Self {
169+
status: error.status(),
170+
headers: Headers::new(),
171+
version: None,
172+
body,
173+
trailers_sender: Some(trailers_sender),
174+
trailers_receiver: Some(trailers_receiver),
175+
ext: Extensions::new(),
176+
peer_addr: None,
177+
local_addr: None,
178+
error: Some(error),
179+
};
180+
if !was_server_error {
181+
res.set_content_type(mime::PLAIN);
182+
}
183+
res
184+
}
185+
186+
/// Create a new response from an `http_types::Error`.
187+
///
188+
/// This will store the error in the `Response`, allowing it to later be
189+
/// checked via `Response::error()`.
190+
///
191+
/// If the `Error`'s status had a status code that was not a 5XX server
192+
/// error code, the response body will be set to the error's message, and
193+
/// the content-type header will be set to `http_types::mime::Plain`.
194+
#[cfg(feature = "unstable")]
195+
pub fn from_error(error: Error) -> Self {
196+
// Only send the message if it is a non-500 range error. All
197+
// errors default to 500 by default, so sending the error
198+
// body is opt-in at the call site.
199+
let was_server_error = error.status().is_server_error();
200+
let body = if !was_server_error {
201+
Body::from_string(error.to_string())
202+
} else {
203+
Body::empty()
204+
};
205+
206+
let (trailers_sender, trailers_receiver) = sync::channel(1);
207+
let (upgrade_sender, upgrade_receiver) = sync::channel(1);
208+
let mut res = Self {
209+
status: error.status(),
210+
headers: Headers::new(),
211+
version: None,
212+
body,
213+
trailers_sender: Some(trailers_sender),
214+
trailers_receiver: Some(trailers_receiver),
215+
upgrade_sender: Some(upgrade_sender),
216+
upgrade_receiver: Some(upgrade_receiver),
217+
has_upgrade: false,
218+
ext: Extensions::new(),
219+
peer_addr: None,
220+
local_addr: None,
221+
error: Some(error),
222+
};
223+
if !was_server_error {
224+
res.set_content_type(mime::PLAIN);
225+
}
226+
res
227+
}
228+
142229
/// Get the status
143230
pub fn status(&self) -> StatusCode {
144231
self.status
@@ -465,6 +552,16 @@ impl Response {
465552
self.body.is_empty()
466553
}
467554

555+
/// Returns an optional reference to the `Error` if the response was created from one, or else `None`.
556+
pub fn error(&mut self) -> Option<&Error> {
557+
self.error.as_ref()
558+
}
559+
560+
/// Takes the `Error` from the response if one exists, replacing it with `None`.
561+
pub fn take_error(&mut self) -> Option<Error> {
562+
self.error.take()
563+
}
564+
468565
/// Get the HTTP version, if one has been set.
469566
///
470567
/// # Examples
@@ -631,8 +728,8 @@ impl Response {
631728
}
632729

633730
impl Clone for Response {
634-
/// Clone the response, resolving the body to `Body::empty()` and removing
635-
/// extensions.
731+
/// Clone the response, resolving the body to `Body::empty()`, removing
732+
/// extensions, and unsetting any `Error`.
636733
fn clone(&self) -> Self {
637734
Self {
638735
status: self.status.clone(),
@@ -650,6 +747,7 @@ impl Clone for Response {
650747
ext: Extensions::new(),
651748
peer_addr: self.peer_addr.clone(),
652749
local_addr: self.local_addr.clone(),
750+
error: None,
653751
}
654752
}
655753
}
@@ -722,6 +820,12 @@ impl Index<&str> for Response {
722820
}
723821
}
724822

823+
impl From<Error> for Response {
824+
fn from(e: Error) -> Self {
825+
Self::from_error(e)
826+
}
827+
}
828+
725829
impl From<StatusCode> for Response {
726830
fn from(s: StatusCode) -> Self {
727831
Response::new(s)

tests/error.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use http_types::{bail, ensure, ensure_eq, Error, StatusCode};
1+
use http_types::{bail, ensure, ensure_eq, Error, StatusCode, Response};
22
use std::io;
33

44
#[test]
@@ -71,3 +71,23 @@ fn option_ext() {
7171
let err = res.unwrap_err();
7272
assert_eq!(err.status(), StatusCode::NotFound);
7373
}
74+
75+
#[async_std::test]
76+
async fn to_response() {
77+
let msg = "This is an error";
78+
79+
let error = Error::from_str(StatusCode::NotFound, msg);
80+
let mut res = Response::from_error(error);
81+
82+
assert!(res.error().is_some());
83+
// Ensure we did not consume the error
84+
assert!(res.error().is_some());
85+
86+
assert_eq!(res.error().unwrap().status(), StatusCode::NotFound);
87+
assert_eq!(res.error().unwrap().to_string(), msg);
88+
89+
res.take_error();
90+
assert!(res.error().is_none());
91+
92+
assert_eq!(res.body_string().await.unwrap(), msg);
93+
}

0 commit comments

Comments
 (0)