From 53ec3ae28068cca74cee03627e3a46e71d12d028 Mon Sep 17 00:00:00 2001 From: Valerio Ageno <51341197+Valerioageno@users.noreply.github.com> Date: Sat, 29 Mar 2025 11:15:45 +0100 Subject: [PATCH] fix: body parsing function (#670) --- crates/tuono_lib/src/catch_all.rs | 2 +- crates/tuono_lib/src/request.rs | 52 +++++++-------------- crates/tuono_lib/tests/server_test.rs | 23 +++++++++ crates/tuono_lib/tests/utils/mock_server.rs | 4 +- crates/tuono_lib/tests/utils/mod.rs | 1 + crates/tuono_lib/tests/utils/post_api.rs | 12 +++++ crates/tuono_lib_macros/src/api.rs | 29 ++++++++++-- crates/tuono_lib_macros/src/handler.rs | 4 +- 8 files changed, 83 insertions(+), 44 deletions(-) create mode 100644 crates/tuono_lib/tests/utils/post_api.rs diff --git a/crates/tuono_lib/src/catch_all.rs b/crates/tuono_lib/src/catch_all.rs index 7fdc147e..e79d5517 100644 --- a/crates/tuono_lib/src/catch_all.rs +++ b/crates/tuono_lib/src/catch_all.rs @@ -10,7 +10,7 @@ pub async fn catch_all( let pathname = request.uri(); let headers = request.headers(); - let req = crate::Request::new(pathname.to_owned(), headers.to_owned(), params); + let req = crate::Request::new(pathname.to_owned(), headers.to_owned(), params, None); // TODO: remove unwrap let payload = Payload::new(&req, &"").client_payload().unwrap(); diff --git a/crates/tuono_lib/src/request.rs b/crates/tuono_lib/src/request.rs index a273c39d..9247add0 100644 --- a/crates/tuono_lib/src/request.rs +++ b/crates/tuono_lib/src/request.rs @@ -37,14 +37,21 @@ pub struct Request { pub uri: Uri, pub headers: HeaderMap, pub params: HashMap, + body: Option>, } impl Request { - pub fn new(uri: Uri, headers: HeaderMap, params: HashMap) -> Request { + pub fn new( + uri: Uri, + headers: HeaderMap, + params: HashMap, + body: Option>, + ) -> Request { Request { uri, headers, params, + body, } } @@ -53,19 +60,14 @@ impl Request { } pub fn body<'de, T: Deserialize<'de>>(&'de self) -> Result { - if let Some(body) = self.headers.get("body") { - if let Ok(body) = body.to_str() { - let body = serde_json::from_str::(body)?; - return Ok(body); - } - return Err(BodyParseError::Io(std::io::Error::new( - std::io::ErrorKind::InvalidData, - "Failed to read body", - ))); + if let Some(body) = &self.body { + let body = serde_json::from_slice::(body)?; + return Ok(body); } + Err(BodyParseError::Io(std::io::Error::new( std::io::ErrorKind::InvalidData, - "No body found", + "Failed to read body", ))) } } @@ -95,15 +97,11 @@ mod tests { #[test] fn it_correctly_parse_the_body() { - let mut request = Request::new( + let request = Request::new( Uri::from_static("http://localhost:3000"), HeaderMap::new(), HashMap::new(), - ); - - request.headers.insert( - "body", - r#"{"field1": true, "field2": "hello"}"#.parse().unwrap(), + Some(r#"{"field1": true, "field2": "hello"}"#.as_bytes().to_vec()), ); let body: FakeBody = request.body().expect("Failed to parse body"); @@ -113,32 +111,16 @@ mod tests { } #[test] - fn it_should_trigger_an_error_when_no_body_is_found() { + fn it_should_trigger_an_error_when_body_is_invalid() { let request = Request::new( Uri::from_static("http://localhost:3000"), HeaderMap::new(), HashMap::new(), + Some(r#"{"field1": true"#.as_bytes().to_vec()), ); let body: Result = request.body(); assert!(body.is_err()); } - - #[test] - fn it_should_trigger_an_error_when_body_is_invalid() { - let mut request = Request::new( - Uri::from_static("http://localhost:3000"), - HeaderMap::new(), - HashMap::new(), - ); - - request - .headers - .insert("body", r#"{"field1": true"#.parse().unwrap()); - - let body: Result = request.body(); - - assert!(body.is_err()); - } } diff --git a/crates/tuono_lib/tests/server_test.rs b/crates/tuono_lib/tests/server_test.rs index d38ba757..d1319973 100644 --- a/crates/tuono_lib/tests/server_test.rs +++ b/crates/tuono_lib/tests/server_test.rs @@ -171,3 +171,26 @@ async fn it_reads_an_env_var() { assert!(response.status().is_success()); assert_eq!(response.text().await.unwrap(), "foobar"); } + +#[tokio::test] +#[serial] +async fn it_parses_the_http_body() { + let app = MockTuonoServer::spawn().await; + + let client = reqwest::Client::builder() + .redirect(reqwest::redirect::Policy::none()) + .build() + .unwrap(); + + let server_url = format!("http://{}:{}", &app.address, &app.port); + + let response = client + .post(format!("{server_url}/api/post")) + .body(r#"{"data":"payload"}"#) + .send() + .await + .expect("Failed to execute request."); + + assert!(response.status().is_success()); + assert_eq!(response.text().await.unwrap(), "payload"); +} diff --git a/crates/tuono_lib/tests/utils/mock_server.rs b/crates/tuono_lib/tests/utils/mock_server.rs index 37981d10..674f1e27 100644 --- a/crates/tuono_lib/tests/utils/mock_server.rs +++ b/crates/tuono_lib/tests/utils/mock_server.rs @@ -4,13 +4,14 @@ use std::io::Write; use std::path::PathBuf; use std::{env, fs}; use tempfile::{TempDir, tempdir}; -use tuono_lib::axum::routing::get; +use tuono_lib::axum::routing::{get, post}; use tuono_lib::{Mode, Server, axum::Router, tuono_internal_init_v8_platform}; use crate::utils::catch_all::get_tuono_internal_api as catch_all; use crate::utils::dynamic_parameter::get_tuono_internal_api as dynamic_parameter; use crate::utils::env::get_tuono_internal_api as test_env; use crate::utils::health_check::get_tuono_internal_api as health_check; +use crate::utils::post_api::post_tuono_internal_api as post_api; use crate::utils::route as html_route; use crate::utils::route::tuono_internal_api as route_api; @@ -84,6 +85,7 @@ impl MockTuonoServer { .route("/route-api", get(route_api)) .route("/catch_all/{*catch_all}", get(catch_all)) .route("/dynamic/{parameter}", get(dynamic_parameter)) + .route("/api/post", post(post_api)) .route("/env", get(test_env)); let server = Server::init(router, Mode::Prod).await; diff --git a/crates/tuono_lib/tests/utils/mod.rs b/crates/tuono_lib/tests/utils/mod.rs index 9bf184ab..a12fd7e0 100644 --- a/crates/tuono_lib/tests/utils/mod.rs +++ b/crates/tuono_lib/tests/utils/mod.rs @@ -3,4 +3,5 @@ pub mod dynamic_parameter; pub mod env; pub mod health_check; pub mod mock_server; +pub mod post_api; pub mod route; diff --git a/crates/tuono_lib/tests/utils/post_api.rs b/crates/tuono_lib/tests/utils/post_api.rs new file mode 100644 index 00000000..63fa337c --- /dev/null +++ b/crates/tuono_lib/tests/utils/post_api.rs @@ -0,0 +1,12 @@ +use serde::Deserialize; +use tuono_lib::Request; + +#[derive(Deserialize)] +struct Payload { + data: String, +} + +#[tuono_lib::api(POST)] +async fn health_check(req: Request) -> String { + req.body::().unwrap().data +} diff --git a/crates/tuono_lib_macros/src/api.rs b/crates/tuono_lib_macros/src/api.rs index 988ce3b7..392fea79 100644 --- a/crates/tuono_lib_macros/src/api.rs +++ b/crates/tuono_lib_macros/src/api.rs @@ -48,6 +48,28 @@ pub fn api_core(attrs: TokenStream, item: TokenStream) -> TokenStream { let application_state_extractor = crate_application_state_extractor(argument_names.clone()); let application_state_import = import_main_application_state(argument_names.clone()); + let modified_request = if http_method == "post" + || http_method == "put" + || http_method == "patch" + { + quote! { + let (parts, body) = request.into_parts(); + let path = parts.uri.clone(); + let headers = parts.headers.clone(); + + let body = tuono_lib::axum::body::to_bytes(body, usize::MAX).await.unwrap_or(Vec::new().into()).to_vec(); + + let req = tuono_lib::Request::new(path, headers, params, Some(body)); + } + } else { + quote! { + let pathname = request.uri(); + let headers = request.headers(); + + let req = tuono_lib::Request::new(request.uri().to_owned(), request.headers().to_owned(), params, None); + } + }; + quote! { #application_state_import @@ -55,12 +77,9 @@ pub fn api_core(attrs: TokenStream, item: TokenStream) -> TokenStream { pub async fn #api_fn_name(#axum_arguments)#return_type { - #application_state_extractor + #application_state_extractor - let pathname = request.uri(); - let headers = request.headers(); - - let req = tuono_lib::Request::new(pathname.to_owned(), headers.to_owned(), params); + #modified_request #fn_name(req.clone(), #argument_names).await } diff --git a/crates/tuono_lib_macros/src/handler.rs b/crates/tuono_lib_macros/src/handler.rs index b481b100..40469154 100644 --- a/crates/tuono_lib_macros/src/handler.rs +++ b/crates/tuono_lib_macros/src/handler.rs @@ -54,7 +54,7 @@ pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream { let pathname = request.uri(); let headers = request.headers(); - let req = tuono_lib::Request::new(pathname.to_owned(), headers.to_owned(), params); + let req = tuono_lib::Request::new(pathname.to_owned(), headers.to_owned(), params, None); #fn_name(req.clone(), #argument_names).await.render_to_string(req) } @@ -68,7 +68,7 @@ pub fn handler_core(_args: TokenStream, item: TokenStream) -> TokenStream { let pathname = request.uri(); let headers = request.headers(); - let req = tuono_lib::Request::new(pathname.to_owned(), headers.to_owned(), params); + let req = tuono_lib::Request::new(pathname.to_owned(), headers.to_owned(), params, None); #fn_name(req.clone(), #argument_names).await.json() }