Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions volo-http/src/server/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,4 +671,135 @@ mod extract_tests {
assert_handler(option_cx_req);
assert_handler(result_cx_req);
}

#[cfg(any(feature = "form", feature = "json"))]
fn simple_req(content_type: &'static str, body: &'static str) -> crate::request::Request {
let mut req = crate::request::Request::new(Body::from(body));
req.headers_mut().insert(
http::header::CONTENT_TYPE,
http::header::HeaderValue::from_static(content_type),
);
req
}

#[cfg(feature = "form")]
#[tokio::test]
async fn extract_form() {
use crate::server::test_helpers;

#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
struct TestForm {
key1: String,
key2: String,
key3: String,
}

const VALID_FORM: &str = "key1=value1&key2=value2&key3=value3";
const INVALID_FORM: &str = "if (key && value) { print(key, value) }";

let test_form = serde_urlencoded::from_str(VALID_FORM).unwrap();

// simple content-type
{
let req = simple_req("application/x-www-form-urlencoded", VALID_FORM);
let (parts, body) = req.into_parts();
assert_eq!(
super::Form::<TestForm>::from_request(&mut test_helpers::empty_cx(), parts, body,)
.await
.unwrap()
.0,
test_form,
);
}
// content-type with charset
{
let req = simple_req(
"application/x-www-form-urlencoded; charset=utf-8",
VALID_FORM,
);
let (parts, body) = req.into_parts();
assert_eq!(
super::Form::<TestForm>::from_request(&mut test_helpers::empty_cx(), parts, body,)
.await
.unwrap()
.0,
test_form,
);
}
// wrong content type
{
let req = simple_req("text/javascript", VALID_FORM);
let (parts, body) = req.into_parts();
super::Form::<TestForm>::from_request(&mut test_helpers::empty_cx(), parts, body)
.await
.unwrap_err();
}
// invalid form
{
let req = simple_req("application/x-www-form-urlencoded", INVALID_FORM);
let (parts, body) = req.into_parts();
super::Form::<TestForm>::from_request(&mut test_helpers::empty_cx(), parts, body)
.await
.unwrap_err();
}
}

#[cfg(feature = "json")]
#[tokio::test]
async fn extract_json() {
use crate::server::test_helpers;

#[derive(Debug, PartialEq, Eq, serde::Deserialize)]
struct TestJson {
key1: String,
key2: String,
key3: String,
}

const VALID_JSON: &str = r#"{"key1":"value1","key2":"value2", "key3": "value3"}"#;
const INVALID_JSON: &str = "if (key && value) { print(key, value) }";

let test_json = crate::utils::json::deserialize(VALID_JSON.as_bytes()).unwrap();

// simple content-type
{
let req = simple_req("application/json", VALID_JSON);
let (parts, body) = req.into_parts();
assert_eq!(
super::Json::<TestJson>::from_request(&mut test_helpers::empty_cx(), parts, body,)
.await
.unwrap()
.0,
test_json,
);
}
// content-type with charset
{
let req = simple_req("application/json; charset=utf-8", VALID_JSON);
let (parts, body) = req.into_parts();
assert_eq!(
super::Json::<TestJson>::from_request(&mut test_helpers::empty_cx(), parts, body,)
.await
.unwrap()
.0,
test_json,
);
}
// wrong content type
{
let req = simple_req("text/javascript", VALID_JSON);
let (parts, body) = req.into_parts();
super::Json::<TestJson>::from_request(&mut test_helpers::empty_cx(), parts, body)
.await
.unwrap_err();
}
// invalid form
{
let req = simple_req("application/json", INVALID_JSON);
let (parts, body) = req.into_parts();
super::Json::<TestJson>::from_request(&mut test_helpers::empty_cx(), parts, body)
.await
.unwrap_err();
}
}
}
98 changes: 98 additions & 0 deletions volo-http/src/server/panic_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,101 @@ pub fn always_internal_error<Cx, E>(
pub fn fixed_payload<R>(payload: R) -> FixedPayload<R> {
FixedPayload { payload }
}

#[cfg(test)]
mod panic_handler_tests {
use std::{convert::Infallible, marker::PhantomData};

use http::status::StatusCode;
use motore::{layer::Layer, service::Service};

use super::{always_internal_error, fixed_payload};
use crate::{
body::{Body, BodyConversion},
request::Request,
response::Response,
server::{IntoResponse, test_helpers},
};

struct PanicService<R = Response, E = Infallible> {
_marker: PhantomData<fn(R, E)>,
}

impl<R, E> PanicService<R, E> {
const fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}

impl<Cx, Req, Resp, E> Service<Cx, Req> for PanicService<Resp, E>
where
Cx: Send,
Req: Send,
Resp: Send,
{
type Response = Resp;
type Error = E;

async fn call(&self, _: &mut Cx, _: Req) -> Result<Self::Response, Self::Error> {
panic!("oops")
}
}

#[tokio::test]
async fn fixed_payload_test() {
const ERR_MSG: &str = "oops";

// it's ok
{
let srv =
volo::catch_panic::Layer::new(fixed_payload((StatusCode::NOT_FOUND, ERR_MSG)))
.layer(test_helpers::DefaultService::<Response, Infallible>::new());
let resp = srv
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
.await
.into_response();
assert_eq!(resp.status(), StatusCode::OK);
}
// it panics
{
let srv =
volo::catch_panic::Layer::new(fixed_payload((StatusCode::NOT_FOUND, ERR_MSG)))
.layer(PanicService::<Response, Infallible>::new());
let resp = srv
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
.await
.into_response();
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
assert_eq!(resp.into_string().await.unwrap(), ERR_MSG);
}
}

#[tokio::test]
async fn internal_error_test() {
// it's ok
{
let srv = volo::catch_panic::Layer::new(always_internal_error)
.layer(test_helpers::DefaultService::<Response, Infallible>::new());
let resp = srv
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
.await
.into_response();
assert_eq!(resp.status(), StatusCode::OK);
}
// it panics
{
let srv = volo::catch_panic::Layer::new(always_internal_error).layer(PanicService::<
Response,
Infallible,
>::new(
));
let resp = srv
.call(&mut test_helpers::empty_cx(), Request::<Body>::default())
.await
.into_response();
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}
}
36 changes: 35 additions & 1 deletion volo-http/src/server/test_helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Test utilities for server of Volo-HTTP.

use std::{fmt::Debug, marker::PhantomData};
use std::{convert::Infallible, fmt::Debug, marker::PhantomData};

use http::method::Method;
use motore::{layer::Layer, service::Service};
Expand All @@ -15,6 +15,40 @@ use crate::{
utils::test_helpers::mock_address,
};

/// Default [`Service`] that always responds `Ok(R::default())`
pub struct DefaultService<R = Response, E = Infallible> {
_marker: PhantomData<fn(R, E)>,
}

impl<R, E> DefaultService<R, E> {
/// Create a new [`DefaultService`]
pub const fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}

impl<R, E> Default for DefaultService<R, E> {
fn default() -> Self {
Self::new()
}
}

impl<Cx, Req, Resp, E> Service<Cx, Req> for DefaultService<Resp, E>
where
Cx: Send,
Req: Send,
Resp: Default + Send,
{
type Response = Resp;
type Error = E;

async fn call(&self, _: &mut Cx, _: Req) -> Result<Self::Response, Self::Error> {
Ok(Resp::default())
}
}

/// Wrap a [`Handler`] into a [`HandlerService`].
///
/// Since [`Handler`] is not exposed, the [`Handler::into_service`] cannot be called outside of
Expand Down
Loading