use std::future::Future;
pub(crate) async fn catch_unwind<F: Future>(fut: F) -> std::thread::Result<F::Output> {
use std::task::Poll;
let mut fut = Box::pin(fut);
std::future::poll_fn(move |cx| {
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| fut.as_mut().poll(cx))) {
Ok(Poll::Pending) => Poll::Pending,
Ok(Poll::Ready(v)) => Poll::Ready(Ok(v)),
Err(panic) => Poll::Ready(Err(panic)),
}
})
.await
}
pub(crate) fn panic_message(panic: &(dyn std::any::Any + Send)) -> &str {
panic
.downcast_ref::<&str>()
.copied()
.or_else(|| panic.downcast_ref::<String>().map(String::as_str))
.unwrap_or("non-string panic payload")
}
#[cfg(any(feature = "manage", feature = "tenancy"))]
#[must_use]
pub(crate) fn catch_panics(router: axum::Router) -> axum::Router {
router.layer(axum::middleware::from_fn(
|req: axum::extract::Request, next: axum::middleware::Next| async move {
let request_id = req
.extensions()
.get::<crate::request_id::RequestId>()
.map(|id| id.0.clone());
let method = req.method().clone();
let path = req.uri().path().to_owned();
match catch_unwind(next.run(req)).await {
Ok(response) => response,
Err(panic) => {
tracing::error!(
target: "rustango::error",
request_id = request_id.as_deref().unwrap_or("-"),
method = %method,
path = %path,
panic = panic_message(&*panic),
"handler panicked"
);
let mut resp = axum::response::Response::new(axum::body::Body::from(
crate::error::OPAQUE_SERVER_ERROR,
));
*resp.status_mut() = axum::http::StatusCode::INTERNAL_SERVER_ERROR;
resp.headers_mut().insert(
axum::http::header::CONTENT_TYPE,
axum::http::HeaderValue::from_static("text/plain; charset=utf-8"),
);
resp
}
}
},
))
}