use std::{fmt::Debug, ops::Deref};
use async_trait::async_trait;
use axum::{
body::Body,
extract::{FromRequest, Request},
Json, RequestExt,
};
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use super::Rejection;
use crate::requests::{
json_schema::ValidationErrorResponse, multipart::parse_multipart,
urlencoded::value_from_urlencoded, ContentType,
};
#[derive(Debug)]
pub struct FormOrJson<T>(pub T)
where
T: Debug + JsonSchema + DeserializeOwned;
impl<T> Deref for FormOrJson<T>
where
T: Debug + JsonSchema + DeserializeOwned,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[async_trait]
impl<T, S> FromRequest<S> for FormOrJson<T>
where
T: Debug + JsonSchema + DeserializeOwned + 'static,
S: Sync + Send,
{
type Rejection = Rejection;
async fn from_request(req: Request<Body>, _state: &S) -> Result<Self, Self::Rejection> {
let (data, _) = form_or_json_from_request(req, _state).await?;
Ok(FormOrJson(data))
}
}
pub type FormOrJsonResult<T> = Result<T, Rejection>;
#[derive(Debug)]
pub struct ValidatedForm<T: Debug + JsonSchema + DeserializeOwned + 'static> {
pub data: Option<T>,
pub form: serde_json::Value,
pub errors: ValidationErrorResponse,
}
impl<T: Debug + JsonSchema + DeserializeOwned + 'static> Default for ValidatedForm<T> {
fn default() -> Self {
Self {
data: None,
form: serde_json::Value::Object(serde_json::Map::new()),
errors: ValidationErrorResponse::default(),
}
}
}
#[async_trait]
impl<T, S> FromRequest<S> for ValidatedForm<T>
where
T: Debug + JsonSchema + DeserializeOwned + 'static,
S: Sync + Send,
{
type Rejection = Rejection;
async fn from_request(req: Request<Body>, _state: &S) -> Result<Self, Self::Rejection> {
let value = form_or_json_from_request(req, _state).await;
match value {
Ok((data, form)) => Ok(ValidatedForm {
data: Some(data),
form,
errors: ValidationErrorResponse::default(),
}),
Err(Rejection::Validation((form, errors))) => Ok(ValidatedForm {
data: None,
form,
errors: ValidationErrorResponse::from(errors),
}),
Err(e) => Err(e),
}
}
}
async fn form_or_json_from_request<T, S>(
req: Request<Body>,
_state: &S,
) -> Result<(T, serde_json::Value), Rejection>
where
T: Debug + JsonSchema + DeserializeOwned + 'static,
S: Sync + Send,
{
let content_type = ContentType::new(
req.headers()
.get("content-type")
.and_then(|value| value.to_str().ok())
.unwrap_or("application/json"),
);
let (value, coerce_arrays) = if content_type.is_json() {
let value = Json::<serde_json::Value>::from_request(req, _state)
.await
.map(|json| FormOrJson(json.0))
.map_err(Rejection::Json)?
.0;
(value, false)
} else if content_type.is_form() {
let bytes = axum::body::to_bytes(req.into_limited_body(), usize::MAX)
.await
.map_err(Rejection::ReadBody)?;
let value = value_from_urlencoded(&bytes);
(value, true)
} else if content_type.is_multipart() {
let (value, _) = parse_multipart::<serde_json::Value>(req).await?;
(value, true)
} else {
return Err(Rejection::UnsupportedContentType);
};
let value = crate::requests::json_schema::validate::<T>(value, coerce_arrays)
.map_err(Rejection::Validation)?;
let data = serde_path_to_error::deserialize(&value).map_err(Rejection::Serde)?;
Ok((data, value))
}
#[cfg(test)]
mod test {
use schemars::JsonSchema;
use serde::Deserialize;
use super::*;
#[derive(Deserialize, Debug, PartialEq, Eq, JsonSchema)]
#[serde(untagged)]
enum NumOrBool {
Num(i32),
Bool(bool),
}
#[derive(Deserialize, JsonSchema, Debug, PartialEq, Eq)]
struct Data {
s: String,
s_vec1: Vec<String>,
s_vec2: Vec<String>,
i: i32,
i_vec1: Vec<i32>,
i_vec2: Vec<i32>,
nob_n: NumOrBool,
nob_b: NumOrBool,
nob_vec: Vec<NumOrBool>,
b: bool,
b_omitted: bool,
ob: Option<bool>,
b_vec1: Vec<bool>,
b_vec2: Vec<bool>,
}
#[tokio::test]
async fn extract_from_json() {}
#[tokio::test]
async fn extract_from_form() {
let body = "s=a&s_vec1=a&s_vec2=a&s_vec2=b&i=1&i_vec1=1&i_vec2=1&i_vec2=2&nob_n=1&nob_b=on&nob_vec=1&nob_vec=on&b=true&b_vec1=true&b_vec2=on&b_vec2=false";
let data = hyper::Request::builder()
.header("content-type", "application/x-www-form-urlencoded")
.header("content-length", body.len())
.body(axum::body::Body::from(body))
.unwrap();
let data = FormOrJson::<Data>::from_request(data, &()).await.unwrap();
assert_eq!(
data.0,
Data {
s: "a".to_string(),
s_vec1: vec!["a".to_string()],
s_vec2: vec!["a".to_string(), "b".to_string()],
i: 1,
i_vec1: vec![1],
i_vec2: vec![1, 2],
nob_n: NumOrBool::Num(1),
nob_b: NumOrBool::Bool(true),
nob_vec: vec![NumOrBool::Num(1), NumOrBool::Bool(true)],
b: true,
b_omitted: false,
ob: None,
b_vec1: vec![true],
b_vec2: vec![true, false],
}
)
}
}