diff --git a/Cargo.toml b/Cargo.toml index c6f7832b7..cc1fca285 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,21 +19,21 @@ readme = "README.md" repository = "https://github.com/http-rs/tide" [dependencies] +accept-encoding = "0.2.0-alpha.2" +async-compression = "0.1.0-alpha.7" cookie = { version = "0.12.0", features = ["percent-encode"] } -futures-preview = "0.3.0-alpha.19" fnv = "1.0.6" +futures-preview = "0.3.0-alpha.19" http = "0.1.19" http-service = "0.3.1" +log = "0.4.8" pin-utils = "0.1.0-alpha.4" route-recognizer = "0.1.13" serde = "1.0.102" serde_derive = "1.0.102" serde_json = "1.0.41" +serde_qs = "0.5.0" typemap = "0.3.3" -serde_urlencoded = "0.6.1" -log = "0.4.8" -accept-encoding = "0.2.0-alpha.2" -async-compression = "0.1.0-alpha.7" [dependencies.http-service-hyper] optional = true diff --git a/examples/cookies.rs b/examples/cookies.rs index ae762ed3b..19c33635a 100644 --- a/examples/cookies.rs +++ b/examples/cookies.rs @@ -3,7 +3,7 @@ use tide::{cookies::ContextExt, middleware::CookiesMiddleware, Context}; /// Tide will use the the `Cookies`'s `Extract` implementation to build this parameter. /// -async fn retrieve_cookie(mut cx: Context<()>) -> String { +async fn retrieve_cookie(cx: Context<()>) -> String { format!("hello cookies: {:?}", cx.get_cookie("hello").unwrap()) } diff --git a/examples/nested_router.rs b/examples/nested_router.rs new file mode 100644 index 000000000..7cf85b98a --- /dev/null +++ b/examples/nested_router.rs @@ -0,0 +1,15 @@ +use tide::App; + +fn main() { + let mut app = App::new(); + app.at("/gates").nest(|router| { + router + .at("/") + .get(|_| async move { "This is an area in front of the gates" }); + router.at("/open").get(|_| async move { "Open the gates!" }); + router + .at("/close") + .get(|_| async move { "Close the gates!" }); + }); + app.run("127.0.0.1:8000").unwrap(); +} diff --git a/src/cookies.rs b/src/cookies.rs index 48fa1a2cb..41f0adcb9 100644 --- a/src/cookies.rs +++ b/src/cookies.rs @@ -30,7 +30,7 @@ impl CookieData { /// An extension to `Context` that provides cached access to cookies pub trait ContextExt { /// returns a `Cookie` by name of the cookie - fn get_cookie(&mut self, name: &str) -> Result>, StringError>; + fn get_cookie(&self, name: &str) -> Result>, StringError>; /// Add cookie to the cookie jar fn set_cookie(&mut self, cookie: Cookie<'static>) -> Result<(), StringError>; @@ -41,7 +41,7 @@ pub trait ContextExt { } impl ContextExt for Context { - fn get_cookie(&mut self, name: &str) -> Result>, StringError> { + fn get_cookie(&self, name: &str) -> Result>, StringError> { let cookie_data = self .extensions() .get::() diff --git a/src/forms.rs b/src/forms.rs index 697027973..60d8687e5 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -21,7 +21,7 @@ impl ContextExt for Context { let body = self.take_body(); Box::pin(async move { let body = body.into_vec().await.client_err()?; - Ok(serde_urlencoded::from_bytes(&body) + Ok(serde_qs::from_bytes(&body) .map_err(|e| err_fmt!("could not decode form: {}", e)) .client_err()?) }) @@ -53,8 +53,6 @@ pub fn form(t: T) -> Response { http::Response::builder() .status(http::status::StatusCode::OK) .header("Content-Type", "application/x-www-form-urlencoded") - .body(Body::from( - serde_urlencoded::to_string(&t).unwrap().into_bytes(), - )) + .body(Body::from(serde_qs::to_string(&t).unwrap().into_bytes())) .unwrap() } diff --git a/src/middleware/cookies.rs b/src/middleware/cookies.rs index cea4e7385..ca2a9631e 100644 --- a/src/middleware/cookies.rs +++ b/src/middleware/cookies.rs @@ -72,7 +72,7 @@ mod tests { static COOKIE_NAME: &str = "testCookie"; /// Tide will use the the `Cookies`'s `Extract` implementation to build this parameter. - async fn retrieve_cookie(mut cx: Context<()>) -> String { + async fn retrieve_cookie(cx: Context<()>) -> String { format!("{}", cx.get_cookie(COOKIE_NAME).unwrap().unwrap().value()) } diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index ba02ff087..e55b61344 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -149,14 +149,12 @@ impl Cors { impl Middleware for Cors { fn handle<'a>(&'a self, cx: Context, next: Next<'a, State>) -> BoxFuture<'a, Response> { Box::pin(async move { - let origin = if let Some(origin) = cx.request().headers().get(header::ORIGIN) { - origin.clone() - } else { - return http::Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::empty()) - .unwrap(); - }; + let origin = cx + .request() + .headers() + .get(header::ORIGIN) + .cloned() + .unwrap_or_else(|| HeaderValue::from_static("")); if !self.is_valid_origin(&origin) { return http::Response::builder() @@ -400,7 +398,7 @@ mod test { let mut server = make_server(app.into_http_service()).unwrap(); let res = server.simulate(request).unwrap(); - assert_eq!(res.status(), 400); + assert_eq!(res.status(), 200); } #[test] diff --git a/src/querystring.rs b/src/querystring.rs index cda68ba94..7caef3984 100644 --- a/src/querystring.rs +++ b/src/querystring.rs @@ -31,8 +31,7 @@ impl<'de, State> ContextExt<'de> for Context { return Err(Error::from(StatusCode::BAD_REQUEST)); } - Ok(serde_urlencoded::from_str(query.unwrap()) - .map_err(|_| Error::from(StatusCode::BAD_REQUEST))?) + Ok(serde_qs::from_str(query.unwrap()).map_err(|_| Error::from(StatusCode::BAD_REQUEST))?) } } diff --git a/src/response.rs b/src/response.rs index 395141ee4..70dd021e8 100644 --- a/src/response.rs +++ b/src/response.rs @@ -4,12 +4,19 @@ pub type Response = http_service::Response; /// Serialize `t` into a JSON-encoded response. pub fn json(t: T) -> Response { - // TODO: remove the `unwrap` - http::Response::builder() - .status(http::status::StatusCode::OK) - .header("Content-Type", "application/json") - .body(Body::from(serde_json::to_vec(&t).unwrap())) - .unwrap() + let mut res = http::Response::builder(); + match serde_json::to_vec(&t) { + Ok(v) => res + .header("Content-Type", "application/json") + .body(Body::from(v)) + .unwrap(), + Err(e) => { + log::error!("{}", e); + res.status(http::status::StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap() + } + } } /// A value that is synchronously convertable into a `Response`. @@ -118,6 +125,7 @@ impl IntoResponse for WithStatus { #[cfg(test)] mod tests { use super::*; + use futures::executor::block_on; #[test] fn test_status() { @@ -125,17 +133,49 @@ mod tests { .with_status(http::status::StatusCode::NOT_FOUND) .into_response(); assert_eq!(resp.status(), http::status::StatusCode::NOT_FOUND); + assert_eq!(block_on(resp.into_body().into_vec()).unwrap(), b"foo"); } #[test] fn byte_vec_content_type() { let resp = String::from("foo").into_bytes().into_response(); assert_eq!(resp.headers()["Content-Type"], "application/octet-stream"); + assert_eq!(block_on(resp.into_body().into_vec()).unwrap(), b"foo"); } #[test] fn string_content_type() { let resp = String::from("foo").into_response(); assert_eq!(resp.headers()["Content-Type"], "text/plain; charset=utf-8"); + assert_eq!(block_on(resp.into_body().into_vec()).unwrap(), b"foo"); + } + + #[test] + fn json_content_type() { + use std::collections::BTreeMap; + + let mut map = BTreeMap::new(); + map.insert(Some("a"), 2); + map.insert(Some("b"), 4); + map.insert(None, 6); + + let resp = json(map); + assert_eq!( + resp.status(), + http::status::StatusCode::INTERNAL_SERVER_ERROR + ); + assert_eq!(block_on(resp.into_body().into_vec()).unwrap(), b""); + + let mut map = BTreeMap::new(); + map.insert("a", 2); + map.insert("b", 4); + map.insert("c", 6); + + let resp = json(map); + assert_eq!(resp.status(), http::status::StatusCode::OK); + assert_eq!( + block_on(resp.into_body().into_vec()).unwrap(), + br##"{"a":2,"b":4,"c":6}"## + ); } }