Skip to content

Commit da2aa01

Browse files
committed
Log inner errors when chained within a user facing error
1 parent 1778505 commit da2aa01

File tree

4 files changed

+57
-18
lines changed

4 files changed

+57
-18
lines changed

src/controllers.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ mod frontend_prelude {
88
pub use crate::util::errors::{bad_request, server_error};
99
}
1010

11+
pub(crate) use prelude::RequestUtils;
12+
1113
mod prelude {
1214
pub use super::helpers::ok_true;
1315
pub use diesel::prelude::*;

src/middleware/log_request.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ pub fn add_custom_metadata<V: Display>(req: &mut dyn Request, key: &'static str,
5858
}
5959
}
6060

61+
#[cfg(test)]
62+
pub(crate) fn get_log_message(req: &dyn Request, key: &'static str) -> String {
63+
for (k, v) in &req.extensions().find::<CustomMetadata>().unwrap().entries {
64+
if key == *k {
65+
return v.clone();
66+
}
67+
}
68+
String::new()
69+
}
70+
6171
struct RequestLine<'r> {
6272
req: &'r dyn Request,
6373
res: &'r Result<Response>,

src/router.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,15 @@ impl Handler for C {
143143
let C(f) = *self;
144144
match f(req) {
145145
Ok(resp) => Ok(resp),
146-
Err(e) => match e.response() {
147-
Some(response) => Ok(response),
148-
None => Err(std_error(e)),
149-
},
146+
Err(e) => {
147+
if let Some(cause) = e.cause() {
148+
req.log_metadata("cause", cause.to_string())
149+
};
150+
match e.response() {
151+
Some(response) => Ok(response),
152+
None => Err(std_error(e)),
153+
}
154+
}
150155
}
151156
}
152157
}
@@ -181,7 +186,9 @@ impl Handler for R404 {
181186
#[cfg(test)]
182187
mod tests {
183188
use super::*;
184-
use crate::util::errors::{bad_request, cargo_err, internal, AppError, NotFound, Unauthorized};
189+
use crate::util::errors::{
190+
bad_request, cargo_err, internal, AppError, ChainError, NotFound, Unauthorized,
191+
};
185192

186193
use conduit_test::MockRequest;
187194
use diesel::result::Error as DieselError;
@@ -219,6 +226,21 @@ mod tests {
219226
200
220227
);
221228

229+
// Inner errors are captured for logging when wrapped by a user facing error
230+
let response = C(|_| {
231+
Err("-1"
232+
.parse::<u8>()
233+
.chain_error(|| bad_request("outer user facing error"))
234+
.unwrap_err())
235+
})
236+
.call(&mut req)
237+
.unwrap();
238+
assert_eq!(response.status.0, 400);
239+
assert_eq!(
240+
crate::middleware::log_request::get_log_message(&req, "cause"),
241+
"invalid digit found in string"
242+
);
243+
222244
// All other error types are propogated up the middleware, eventually becoming status 500
223245
assert!(C(|_| Err(internal(""))).call(&mut req).is_err());
224246
assert!(

src/util/errors.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ pub trait AppError: Send + fmt::Display + fmt::Debug + 'static {
7777
/// where it is eventually logged and turned into a status 500 response.
7878
fn response(&self) -> Option<Response>;
7979

80+
/// The cause of an error response
81+
///
82+
/// If present, an error provided to the `LogRequests` middleware.
83+
///
84+
/// This is intended for use with the `ChainError` trait, where a user facing
85+
/// error may wrap an internal error we still want to log.
86+
fn cause(&self) -> Option<&dyn AppError> {
87+
None
88+
}
89+
8090
fn get_type_id(&self) -> TypeId {
8191
TypeId::of::<Self>()
8292
}
@@ -108,6 +118,10 @@ impl AppError for Box<dyn AppError> {
108118
fn response(&self) -> Option<Response> {
109119
(**self).response()
110120
}
121+
122+
fn cause(&self) -> Option<&dyn AppError> {
123+
(**self).cause()
124+
}
111125
}
112126

113127
pub type AppResult<T> = Result<T, Box<dyn AppError>>;
@@ -128,19 +142,6 @@ struct ChainedError<E> {
128142
cause: Box<dyn AppError>,
129143
}
130144

131-
impl<T, F> ChainError<T> for F
132-
where
133-
F: FnOnce() -> AppResult<T>,
134-
{
135-
fn chain_error<E, C>(self, callback: C) -> AppResult<T>
136-
where
137-
E: AppError,
138-
C: FnOnce() -> E,
139-
{
140-
self().chain_error(callback)
141-
}
142-
}
143-
144145
impl<T, E: AppError> ChainError<T> for Result<T, E> {
145146
fn chain_error<E2, C>(self, callback: C) -> AppResult<T>
146147
where
@@ -173,6 +174,10 @@ impl<E: AppError> AppError for ChainedError<E> {
173174
fn response(&self) -> Option<Response> {
174175
self.error.response()
175176
}
177+
178+
fn cause(&self) -> Option<&dyn AppError> {
179+
Some(&*self.cause)
180+
}
176181
}
177182

178183
impl<E: AppError> fmt::Display for ChainedError<E> {

0 commit comments

Comments
 (0)