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
69 changes: 69 additions & 0 deletions crates/driver/src/infra/api/extract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! 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,
};

/// JSON extractor that wraps Axum's native one and logs deserialization
/// errors.
pub struct LoggingJson<T>(pub T);
Comment thread
squadgazzz marked this conversation as resolved.

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)
}
}
}
}

/// Query extractor that wraps Axum's native one and logs deserialization
/// errors.
pub struct LoggingQuery<T>(pub T);
Comment thread
squadgazzz marked this conversation as resolved.

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