diff --git a/README.md b/README.md index 13b8acfc..bb0821aa 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ with. Will probably change the name to something else. - As easy to use as tide. I don't really consider warp easy to use due to type tricks it uses. `fn route() -> impl Filter<...>` also isn't very ergonomic. - Just `async fn(Request) -> Result` would be nicer. + Just `async fn(Request) -> Response` would be nicer. - Deep integration with Tower meaning you can - Apply middleware to the entire application. - Apply middleware to a single route. @@ -40,8 +40,8 @@ Defining a single route looks like this: ```rust let app = tower_web::app().at("/").get(root); -async fn root(req: Request) -> Result<&'static str, Error> { - Ok("Hello, World!") +async fn root(req: Request) -> &'static str { + "Hello, World!" } ``` @@ -59,14 +59,14 @@ let app = tower_web::app() Handler functions are just async functions like: ```rust -async fn handler(req: Request) -> Result<&'static str, Error> { - Ok("Hello, World!") +async fn handler(req: Request) -> &'static str { + "Hello, World!" } ``` -They most take the request as the first argument but all arguments following +They must take the request as the first argument but all arguments following are called "extractors" and are used to extract data from the request (similar -to rocket but no macros): +to rocket but without macros): ```rust #[derive(Deserialize)] @@ -86,7 +86,7 @@ async fn handler( user: extract::Json, // deserialize query string into a `Pagination` pagination: extract::Query, -) -> Result<&'static str, Error> { +) -> &'static str { let user: UserPayload = user.into_inner(); let pagination: Pagination = pagination.into_inner(); @@ -100,7 +100,7 @@ The inputs can also be optional: async fn handler( req: Request, user: Option>, -) -> Result<&'static str, Error> { +) -> &'static str { // ... } ``` @@ -112,7 +112,7 @@ async fn handler( req: Request, // buffer the whole request body body: Bytes, -) -> Result<&'static str, Error> { +) -> &'static str { // ... } ``` @@ -124,56 +124,96 @@ async fn handler( req: Request, // max body size in bytes body: extract::BytesMaxLength<1024>, -) -> Result<&'static str, Error> { - Ok("Hello, World!") +) -> &'static str { + // ... +} +``` + +Params from dynamic routes like `GET /users/:id` can be extracted like so + +```rust +async fn handle( + req: Request, + // get a map of key value pairs + map: extract::UrlParamsMap, +) -> &'static str { + let raw_id: Option<&str> = map.get("id"); + let parsed_id: Option = map.get_typed::("id"); + + // ... +} + +async fn handle( + req: Request, + // or get a tuple with each param + params: extract::UrlParams<(i32, String)>, +) -> &'static str { + let (id, name) = params.into_inner(); + + // ... } ``` Anything that implements `FromRequest` can work as an extractor where -`FromRequest` is a simple async trait: +`FromRequest` is an async trait: ```rust #[async_trait] pub trait FromRequest: Sized { - async fn from_request(req: &mut Request) -> Result; + type Rejection: IntoResponse; + + async fn from_request(req: &mut Request) -> Result; } ``` This "extractor" pattern is inspired by Bevy's ECS. The idea is that it should -be easy to parse pick apart the request without having to repeat yourself a lot -or use macros. +be easy to pick apart the request without having to repeat yourself a lot or use +macros. -Dynamic routes like `GET /users/:id` is also supported. - -You can also return different response types: +The return type must implement `IntoResponse`: ```rust -async fn string_response(req: Request) -> Result { +async fn empty_response(req: Request) { + // ... +} + +// gets `content-type: text/plain` +async fn string_response(req: Request) -> String { // ... } // gets `content-type: appliation/json`. `Json` can contain any `T: Serialize` -async fn json_response(req: Request) -> Result, Error> { +async fn json_response(req: Request) -> response::Json { // ... } // gets `content-type: text/html`. `Html` can contain any `T: Into` -async fn html_response(req: Request) -> Result, Error> { +async fn html_response(req: Request) -> response::Html { // ... } // or for full control -async fn response(req: Request) -> Result, Error> { +async fn response(req: Request) -> Response { + // ... +} + +// Result is supported if each type implements `IntoResponse` +async fn response(req: Request) -> Result, StatusCode> { // ... } ``` +This makes error handling quite simple. Basically handlers are not allowed to +fail and must always produce a response. This also means users are in charge of +how their errors are mapped to responses rather than a framework providing some +opaque catch all error type. + You can also apply Tower middleware to single routes: ```rust let app = tower_web::app() .at("/") - .get(send_some_large_file.layer(tower_http::compression::CompressionLayer::new())) + .get(send_some_large_file.layer(CompressionLayer::new())) ``` Or to the whole app: @@ -222,12 +262,6 @@ See the examples directory for more examples. # TODO -- Error handling should probably be redone. Not quite sure if its bad the - `Error` is just an enum where everything is public. -- Audit which error codes we return for each kind of error. This will probably - be changed when error handling is re-done. -- Probably don't want to require `hyper::Body` for request bodies. Should - have our own so hyper isn't required. - `RouteBuilder` should have an `async fn serve(self) -> Result<(), hyper::Error>` for users who just wanna create a hyper server and not care about the lower level details. Should be gated by a `hyper` feature. diff --git a/src/response.rs b/src/response.rs index 16e9beb5..0b2b0a89 100644 --- a/src/response.rs +++ b/src/response.rs @@ -73,7 +73,10 @@ impl IntoResponse for &'static str { impl IntoResponse for String { fn into_response(self) -> Response { - Response::new(Body::from(self)) + let mut res = Response::new(Body::from(self)); + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain")); + res } }