rama_http/service/web/endpoint/extract/body/
form.rsuse super::BytesRejection;
use crate::dep::http_body_util::BodyExt;
use crate::service::web::extract::FromRequest;
use crate::utils::macros::{composite_http_rejection, define_http_rejection};
use crate::{Method, Request};
pub use crate::response::Form;
define_http_rejection! {
#[status = UNSUPPORTED_MEDIA_TYPE]
#[body = "Form requests must have `Content-Type: application/x-www-form-urlencoded`"]
pub struct InvalidFormContentType;
}
define_http_rejection! {
#[status = BAD_REQUEST]
#[body = "Failed to deserialize form"]
pub struct FailedToDeserializeForm(Error);
}
composite_http_rejection! {
pub enum FormRejection {
InvalidFormContentType,
FailedToDeserializeForm,
BytesRejection,
}
}
impl<T> FromRequest for Form<T>
where
T: serde::de::DeserializeOwned + Send + Sync + 'static,
{
type Rejection = FormRejection;
async fn from_request(req: Request) -> Result<Self, Self::Rejection> {
if req.method() == Method::GET {
let query = req.uri().query().unwrap_or_default();
let value = match serde_html_form::from_bytes(query.as_bytes()) {
Ok(value) => value,
Err(err) => return Err(FailedToDeserializeForm::from_err(err).into()),
};
Ok(Form(value))
} else {
if !crate::service::web::extract::has_any_content_type(
req.headers(),
&[&mime::APPLICATION_WWW_FORM_URLENCODED],
) {
return Err(InvalidFormContentType.into());
}
let body = req.into_body();
match body.collect().await {
Ok(c) => {
let value = match serde_html_form::from_bytes(&c.to_bytes()) {
Ok(value) => value,
Err(err) => return Err(FailedToDeserializeForm::from_err(err).into()),
};
Ok(Form(value))
}
Err(err) => Err(BytesRejection::from_err(err).into()),
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::service::web::WebService;
use crate::{Body, Method, Request, StatusCode};
use rama_core::{Context, Service};
#[tokio::test]
async fn test_form_post_form_urlencoded() {
#[derive(Debug, serde::Deserialize)]
struct Input {
name: String,
age: u8,
}
let service = WebService::default().post("/", |Form(body): Form<Input>| async move {
assert_eq!(body.name, "Devan");
assert_eq!(body.age, 29);
});
let req = Request::builder()
.uri("/")
.method(Method::POST)
.header("content-type", "application/x-www-form-urlencoded")
.body(r#"name=Devan&age=29"#.into())
.unwrap();
let resp = service.serve(Context::default(), req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_form_post_form_urlencoded_missing_data_fail() {
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Input {
name: String,
age: u8,
}
let service =
WebService::default().post("/", |Form(_): Form<Input>| async move { StatusCode::OK });
let req = Request::builder()
.uri("/")
.method(Method::POST)
.header("content-type", "application/x-www-form-urlencoded")
.body(r#"age=29"#.into())
.unwrap();
let resp = service.serve(Context::default(), req).await.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn test_form_get_form_urlencoded_fail() {
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Input {
name: String,
age: u8,
}
let service =
WebService::default().get("/", |Form(_): Form<Input>| async move { StatusCode::OK });
let req = Request::builder()
.uri("/")
.method(Method::GET)
.header("content-type", "application/x-www-form-urlencoded")
.body(r#"name=Devan&age=29"#.into())
.unwrap();
let resp = service.serve(Context::default(), req).await.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn test_form_get() {
#[derive(Debug, serde::Deserialize)]
struct Input {
name: String,
age: u8,
}
let service = WebService::default().get("/", |Form(body): Form<Input>| async move {
assert_eq!(body.name, "Devan");
assert_eq!(body.age, 29);
});
let req = Request::builder()
.uri("/?name=Devan&age=29")
.method(Method::GET)
.body(Body::empty())
.unwrap();
let resp = service.serve(Context::default(), req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
#[tokio::test]
async fn test_form_get_fail_missing_data() {
#[derive(Debug, serde::Deserialize)]
#[allow(dead_code)]
struct Input {
name: String,
age: u8,
}
let service =
WebService::default().get("/", |Form(_): Form<Input>| async move { StatusCode::OK });
let req = Request::builder()
.uri("/?name=Devan")
.method(Method::GET)
.body(Body::empty())
.unwrap();
let resp = service.serve(Context::default(), req).await.unwrap();
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
}