mirror of
https://github.com/tuono-labs/tuono
synced 2026-07-25 12:52:47 -07:00
fix: body parsing function (#670)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -37,14 +37,21 @@ pub struct Request {
|
||||
pub uri: Uri,
|
||||
pub headers: HeaderMap,
|
||||
pub params: HashMap<String, String>,
|
||||
body: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn new(uri: Uri, headers: HeaderMap, params: HashMap<String, String>) -> Request {
|
||||
pub fn new(
|
||||
uri: Uri,
|
||||
headers: HeaderMap,
|
||||
params: HashMap<String, String>,
|
||||
body: Option<Vec<u8>>,
|
||||
) -> Request {
|
||||
Request {
|
||||
uri,
|
||||
headers,
|
||||
params,
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,19 +60,14 @@ impl Request {
|
||||
}
|
||||
|
||||
pub fn body<'de, T: Deserialize<'de>>(&'de self) -> Result<T, BodyParseError> {
|
||||
if let Some(body) = self.headers.get("body") {
|
||||
if let Ok(body) = body.to_str() {
|
||||
let body = serde_json::from_str::<T>(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::<T>(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<FakeBody, BodyParseError> = 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<FakeBody, BodyParseError> = request.body();
|
||||
|
||||
assert!(body.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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::<Payload>().unwrap().data
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user