use http::StatusCode;
use http_body_util::BodyExt;
use serde::de::DeserializeOwned;
use crate::extractors::FromRequest;
use crate::responder::Responder;
use crate::types::Request;
#[doc(alias = "form")]
pub struct Form<T>(pub T);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FormError {
InvalidContentType,
BodyReadError(String),
InvalidUtf8,
ParseError(String),
DeserializationError(String),
}
impl std::fmt::Display for FormError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidContentType => {
write!(
f,
"invalid content type; expected application/x-www-form-urlencoded"
)
}
Self::BodyReadError(err) => write!(f, "failed to read request body: {err}"),
Self::InvalidUtf8 => write!(f, "request body contains invalid UTF-8"),
Self::ParseError(err) => write!(f, "failed to parse form data: {err}"),
Self::DeserializationError(err) => write!(f, "failed to deserialize form data: {err}"),
}
}
}
impl std::error::Error for FormError {}
impl Responder for FormError {
fn into_response(self) -> crate::types::Response {
match self {
FormError::InvalidContentType => (
StatusCode::BAD_REQUEST,
"Invalid content type; expected application/x-www-form-urlencoded",
)
.into_response(),
FormError::BodyReadError(err) => (
StatusCode::BAD_REQUEST,
format!("Failed to read request body: {err}"),
)
.into_response(),
FormError::InvalidUtf8 => (
StatusCode::BAD_REQUEST,
"Request body contains invalid UTF-8",
)
.into_response(),
FormError::ParseError(err) => (
StatusCode::BAD_REQUEST,
format!("Failed to parse form data: {err}"),
)
.into_response(),
FormError::DeserializationError(err) => (
StatusCode::BAD_REQUEST,
format!("Failed to deserialize form data: {err}"),
)
.into_response(),
}
}
}
impl<'a, T> FromRequest<'a> for Form<T>
where
T: DeserializeOwned + Send + 'static,
{
type Error = FormError;
fn from_request(
req: &'a mut Request,
) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
async move {
let content_type = req
.headers()
.get(http::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok());
if content_type != Some("application/x-www-form-urlencoded") {
return Err(FormError::InvalidContentType);
}
let body_bytes = req
.body_mut()
.collect()
.await
.map_err(|e| FormError::BodyReadError(e.to_string()))?
.to_bytes();
let body_str = std::str::from_utf8(&body_bytes).map_err(|_| FormError::InvalidUtf8)?;
let form_data = serde_urlencoded::from_str::<T>(body_str)
.map_err(|e| FormError::DeserializationError(e.to_string()))?;
Ok(Form(form_data))
}
}
}