use super::extension_types::{CorrelationId, RequestId};
use serde::{Deserialize, Serialize};
use tide::{Body, Middleware, Next, Request, Result};
#[cfg(feature = "honeycomb")]
use tracing_honeycomb::TraceId;
#[cfg(feature = "test")]
use uuid::Uuid;
#[derive(Debug, Default, Clone)]
pub struct JsonErrorMiddleware {
_priv: (),
}
struct JsonErrorMiddlewareHasBeenRun;
#[derive(Debug, Deserialize, Serialize)]
pub struct JsonError {
pub status: u16,
pub title: String,
pub message: String,
pub request_id: RequestId,
pub correlation_id: Option<String>,
#[cfg(feature = "honeycomb")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "honeycomb")))]
pub honeycomb_trace_id: Option<String>,
}
impl JsonErrorMiddleware {
#[must_use]
pub fn new() -> Self {
Self { _priv: () }
}
async fn handle<'a, State: Clone + Send + Sync + 'static>(
&'a self,
mut req: Request<State>,
next: Next<'a, State>,
) -> Result {
if req.ext::<JsonErrorMiddlewareHasBeenRun>().is_some() {
return Ok(next.run(req).await);
}
req.set_ext(JsonErrorMiddlewareHasBeenRun);
let request_id = req
.ext::<RequestId>()
.expect("RequestIdMiddleware must be installed before JsonErrorMiddleware.")
.clone();
#[cfg(feature = "honeycomb")]
let honeycomb_trace_id = req.ext::<TraceId>().cloned();
let mut res = next.run(req).await;
let status = res.status();
if status.is_server_error() {
#[cfg(not(feature = "test"))]
let correlation_id = CorrelationId::new();
#[cfg(feature = "test")]
let correlation_id: CorrelationId = Uuid::nil().into();
let body = JsonError {
title: status.canonical_reason().to_string(),
message: format!("Internal Server Error (correlation_id={})", correlation_id),
status: status as u16,
request_id,
correlation_id: Some(correlation_id.to_string()),
#[cfg(feature = "honeycomb")]
honeycomb_trace_id: honeycomb_trace_id.map(|v| v.to_string()),
};
res.set_body(Body::from_json(&body)?);
res.insert_header("X-Correlation-Id", correlation_id.as_str());
res.insert_ext(correlation_id);
return Ok(res);
}
if status.is_client_error() {
if let Some(error) = res.error() {
let body = JsonError {
title: status.canonical_reason().to_string(),
message: format!("{:?}", error),
status: status as u16,
request_id,
correlation_id: None,
#[cfg(feature = "honeycomb")]
honeycomb_trace_id: honeycomb_trace_id.map(|v| v.to_string()),
};
res.set_body(Body::from_json(&body)?);
} else {
let body = JsonError {
title: status.canonical_reason().to_string(),
message: "(no additional context)".to_string(),
status: status as u16,
request_id,
correlation_id: None,
#[cfg(feature = "honeycomb")]
honeycomb_trace_id: honeycomb_trace_id.map(|v| v.to_string()),
};
res.set_body(Body::from_json(&body)?);
}
return Ok(res);
}
Ok(res)
}
}
#[tide::utils::async_trait]
impl<State: Clone + Send + Sync + 'static> Middleware<State> for JsonErrorMiddleware {
async fn handle(&self, req: Request<State>, next: Next<'_, State>) -> Result {
self.handle(req, next).await
}
}