refactor: single refusal shape with openapi contract test

This commit is contained in:
2026-10-25 21:55:38 +01:00
parent 816d39ffd6
commit c8d9138349
25 changed files with 764 additions and 273 deletions

View File

@@ -0,0 +1,101 @@
//! JSON API extractors whose rejections are refused in the one error shape.
use axum::{
extract::{
FromRequest, FromRequestParts, Path, Query, Request,
rejection::{JsonRejection, PathRejection, QueryRejection},
},
http::{StatusCode, request::Parts},
response::{IntoResponse, Response},
};
use serde::de::DeserializeOwned;
use crate::errors::{INVALID_INPUT, refuse};
/// Malformed request (bad JSON, query or path), refused as `INVALID_INPUT`.
/// Status follows axum: 400 syntax, 415 content type, 422 data errors, 413 size.
pub struct MalformedRequest {
status: StatusCode,
message: String,
}
impl IntoResponse for MalformedRequest {
fn into_response(self) -> Response {
refuse(self.status, INVALID_INPUT, self.message)
}
}
impl From<JsonRejection> for MalformedRequest {
fn from(r: JsonRejection) -> Self {
Self {
status: r.status(),
message: r.body_text(),
}
}
}
impl From<QueryRejection> for MalformedRequest {
fn from(r: QueryRejection) -> Self {
Self {
status: r.status(),
message: r.body_text(),
}
}
}
impl From<PathRejection> for MalformedRequest {
fn from(r: PathRejection) -> Self {
Self {
status: r.status(),
message: r.body_text(),
}
}
}
/// `axum::Json` with refusals in the one error shape.
pub struct Body<T>(pub T);
impl<S, T> FromRequest<S> for Body<T>
where
S: Send + Sync,
T: DeserializeOwned,
{
type Rejection = MalformedRequest;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let axum::Json(v) = axum::Json::<T>::from_request(req, state).await?;
Ok(Self(v))
}
}
/// `axum::extract::Query` with refusals in the one error shape.
pub struct Params<T>(pub T);
impl<S, T> FromRequestParts<S> for Params<T>
where
S: Send + Sync,
T: DeserializeOwned,
{
type Rejection = MalformedRequest;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let Query(v) = Query::<T>::from_request_parts(parts, state).await?;
Ok(Self(v))
}
}
/// `axum::extract::Path` with refusals in the one error shape.
pub struct PathId<T>(pub T);
impl<S, T> FromRequestParts<S> for PathId<T>
where
S: Send + Sync,
T: DeserializeOwned + Send,
{
type Rejection = MalformedRequest;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let Path(v) = Path::<T>::from_request_parts(parts, state).await?;
Ok(Self(v))
}
}