Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion crates/driver/src/domain/competition/pre_processing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,11 @@ impl Utilities {
observe::metrics::metrics().on_auction_overhead_start("driver", "parse_dto");
// deserialization takes tens of milliseconds so run it on a blocking task
tokio::task::spawn_blocking(move || {
serde_json::from_slice(&solve_request).context("could not parse solve request")
serde_json::from_slice(&solve_request)
.inspect_err(|err| {
tracing::warn!(?err, "failed to parse /solve request body");
})
.context("could not parse solve request")
})
.await
.context("failed to await blocking task")??
Expand Down
65 changes: 65 additions & 0 deletions crates/driver/src/infra/api/extract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//! Axum extractors that emit a `warn` log when request deserialization
//! fails, then delegate to the stock extractor's rejection so the HTTP
//! response shape is unchanged.

use {
axum::{
extract::{
FromRequest,
FromRequestParts,
Request,
rejection::{JsonRejection, QueryRejection},
},
http::request::Parts,
},
serde::de::DeserializeOwned,
};

pub struct LoggingJson<T>(pub T);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this

Suggested change
pub struct LoggingJson<T>(pub T);
/// Json extractor wrapping Axum's native one but logs deserialization errors
pub struct LoggingJson<T>(pub T);


impl<S, T> FromRequest<S> for LoggingJson<T>
where
S: Send + Sync,
T: DeserializeOwned,
{
type Rejection = JsonRejection;

async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
match axum::Json::<T>::from_request(req, state).await {
Ok(axum::Json(value)) => Ok(Self(value)),
Err(rejection) => {
tracing::warn!(
err = %rejection,
target = std::any::type_name::<T>(),
"failed to deserialize JSON request body",
);
Err(rejection)
}
}
}
}

pub struct LoggingQuery<T>(pub T);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above but applied to query


impl<S, T> FromRequestParts<S> for LoggingQuery<T>
where
S: Send + Sync,
T: DeserializeOwned,
{
type Rejection = QueryRejection;

async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
match axum::extract::Query::<T>::from_request_parts(parts, state).await {
Ok(axum::extract::Query(value)) => Ok(Self(value)),
Err(rejection) => {
tracing::warn!(
err = %rejection,
target = std::any::type_name::<T>(),
query = parts.uri.query().unwrap_or_default(),
"failed to deserialize query string",
);
Err(rejection)
}
}
}
}
1 change: 1 addition & 0 deletions crates/driver/src/infra/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use {
};

mod error;
mod extract;
pub mod routes;

pub struct Api {
Expand Down
6 changes: 3 additions & 3 deletions crates/driver/src/infra/api/routes/quote/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use {
crate::infra::{
api::{Error, State},
api::{Error, State, extract::LoggingQuery},
observe,
},
tracing::Instrument,
Expand All @@ -16,10 +16,10 @@ pub(in crate::infra::api) fn quote(router: axum::Router<State>) -> axum::Router<

async fn route(
state: axum::extract::State<State>,
order: axum::extract::Query<dto::Order>,
LoggingQuery(order): LoggingQuery<dto::Order>,
) -> Result<axum::Json<dto::Quote>, (axum::http::StatusCode, axum::Json<Error>)> {
let handle_request = async {
let order = order.0.into_domain();
let order = order.into_domain();
observe::quoting(&order);
let quote = order
.quote(
Expand Down
4 changes: 2 additions & 2 deletions crates/driver/src/infra/api/routes/reveal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use {
crate::{
domain::competition::auction,
infra::{
api::{self, Error, State},
api::{self, Error, State, extract::LoggingJson},
observe,
},
},
Expand All @@ -17,7 +17,7 @@ pub(in crate::infra::api) fn reveal(router: axum::Router<State>) -> axum::Router

async fn route(
state: axum::extract::State<State>,
req: axum::Json<dto::RevealRequest>,
LoggingJson(req): LoggingJson<dto::RevealRequest>,
) -> Result<axum::Json<dto::RevealResponse>, (axum::http::StatusCode, axum::Json<Error>)> {
let auction_id =
auction::Id::try_from(req.auction_id).map_err(api::routes::AuctionError::from)?;
Expand Down
4 changes: 2 additions & 2 deletions crates/driver/src/infra/api/routes/settle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use {
crate::{
domain::competition::auction,
infra::{
api::{self, Error, State},
api::{self, Error, State, extract::LoggingJson},
observe,
},
},
Expand All @@ -17,7 +17,7 @@ pub(in crate::infra::api) fn settle(router: axum::Router<State>) -> axum::Router

async fn route(
state: axum::extract::State<State>,
req: axum::Json<dto::SettleRequest>,
LoggingJson(req): LoggingJson<dto::SettleRequest>,
) -> Result<(), (axum::http::StatusCode, axum::Json<Error>)> {
let auction_id =
auction::Id::try_from(req.auction_id).map_err(api::routes::AuctionError::from)?;
Expand Down
Loading