aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2021-01-24 22:37:36 +0100
committerMartin Fischer <martin@push-f.com>2021-01-24 22:49:57 +0100
commit76e92d7281b45ce506046a8946b7fde3355c485d (patch)
treee3a261e993450047a3a366eae0091efc0948377c /README.md
parent4ba2d050bdf1a3c0070f3aa2331c82745611af1f (diff)
define & impl traits instead of wrapping types
bump version to 0.3.0
Diffstat (limited to 'README.md')
-rw-r--r--README.md89
1 files changed, 37 insertions, 52 deletions
diff --git a/README.md b/README.md
index b2fe919..38bc5d8 100644
--- a/README.md
+++ b/README.md
@@ -1,54 +1,38 @@
# Sputnik
-A microframework based on [Hyper](https://hyper.rs/) that forces you to:
+A microframework based on [Hyper](https://hyper.rs/) providing traits to:
-* make error handling explicit (no possible failures hidden behind macros)
-* implement your own error type ([because you need to anyway](#error-handling))
+* extend `http::request::Parts` with query parameter deserialization & cookie parsing
+* extend `hyper::Body` with form deserialization (and optional [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) protection)
+* extend `http::response::Builder` with methods to set & delete cookies and set the Content-Type
-Sputnik provides:
+Furthermore Sputnik provides what's necessary to implement [signed & expiring
+cookies](#signed--expiring-cookies) with the expiry date encoded into the
+signed cookie value, providing a more lightweight alternative to JWT if you
+don't need interoperability.
-* convenience wrappers around hyper's `Request` & `Response`
- * parse, set and delete cookies
- (powered by the [cookie](https://crates.io/crates/cookie) crate)
- * parse query strings and HTML form data (powered by the
- [serde_urlencoded](https://crates.io/crates/serde_urlencoded) crate)
-* cookie-based [CSRF](https://en.wikipedia.org/wiki/Cross-site_request_forgery) tokens
-* `Key`: a convenience wrapper around HMAC (stolen from the cookie crate, so
- that you don't have to use `CookieJar`s if you don't need them)
-* `decode_expiring_claim` & `encode_expiring_claim`, which can be combined with
- `Key` to implement [signed & expiring cookies](#signed--expiring-cookies)
- (with the expiry date encoded into the signed cookie value)
+Sputnik does **not** handle routing. For most web applications `match`ing on
+(method, path) suffices. If you need path variables, you can use one of the
+many [router crates](https://crates.io/keywords/router).
-Sputnik does **not**:
-
-* handle routing: for most web apps `match`ing on (method, path) suffices
-* handle configuration: we recommend [toml](https://crates.io/crates/toml)
-* handle persistence: we recommend [diesel](https://diesel.rs/)
-* handle templating: we recommend [maud](https://maud.lambda.xyz/)
-
-## Error handling
-
-Rust provides convenient short-circuiting with the `?` operator, which
-converts errors with `From::from()`. Since you probably want to short-circuit
-errors from other crates (e.g. database errors), a web framework cannot
-provide you an error type since Rust disallows you from defining a `From`
-conversion between two foreign types.
-
-This does imply that you need to define your own error type, allowing you to
-implement a `From` conversion for every error type you want to short-circuit
-with `?`. Fortunately the [thiserror](https://crates.io/crates/thiserror)
-crate makes defining custom errors and `From` implementations trivial.
+Sputnik encourages you to create your own error enum and implement `From`
+conversions for every error type, which you want to short-circuit with the `?`
+operator. This can be easily done with [thiserror](https://crates.io/crates/thiserror)
+because Sputnik restricts its error types to the `'static` lifetime.
## Example
```rust
use std::convert::Infallible;
use hyper::service::{service_fn, make_service_fn};
-use hyper::{Method, Server, StatusCode};
+use hyper::{Method, Server, StatusCode, Body};
+use hyper::http::request::Parts;
+use hyper::http::response::Builder;
use serde::Deserialize;
-use sputnik::security::CsrfToken;
-use sputnik::{request::{Parts, Body}, response::Response};
-use sputnik::request::error::*;
+use sputnik::{mime, request::{SputnikParts, SputnikBody}, response::SputnikBuilder};
+use sputnik::request::CsrfProtectedFormError;
+
+type Response = hyper::Response<Body>;
#[derive(thiserror::Error, Debug)]
enum Error {
@@ -65,8 +49,8 @@ fn render_error(err: Error) -> (StatusCode, String) {
}
}
-async fn route(req: &mut Parts, body: Body) -> Result<Response,Error> {
- match (req.method(), req.uri().path()) {
+async fn route(req: &mut Parts, body: Body) -> Result<Response, Error> {
+ match (&req.method, req.uri.path()) {
(&Method::GET, "/form") => get_form(req).await,
(&Method::POST, "/form") => post_form(req, body).await,
_ => return Err(Error::NotFound("page not found".to_owned()))
@@ -74,29 +58,30 @@ async fn route(req: &mut Parts, body: Body) -> Result<Response,Error> {
}
async fn get_form(req: &mut Parts) -> Result<Response, Error> {
- let mut response = Response::new();
- let csrf_token = CsrfToken::from_parts(req, &mut response);
- *response.body() = format!("<form method=post>
- <input name=text>{}<button>Submit</button></form>", csrf_token.html_input()).into();
- Ok(response)
+ let mut response = Builder::new();
+ let csrf_token = req.csrf_token(&mut response);
+ Ok(response.content_type(mime::TEXT_HTML).body(
+ format!("<form method=post>
+ <input name=text>{}<button>Submit</button></form>", csrf_token.html_input()).into()
+ ).unwrap())
}
#[derive(Deserialize)]
struct FormData {text: String}
async fn post_form(req: &mut Parts, body: Body) -> Result<Response, Error> {
- let mut response = Response::new();
- let csrf_token = CsrfToken::from_parts(req, &mut response);
+ let mut response = Builder::new();
+ let csrf_token = req.csrf_token(&mut response);
let msg: FormData = body.into_form_csrf(&csrf_token).await?;
- *response.body() = format!("hello {}", msg.text).into();
- Ok(response)
+ Ok(response.body(
+ format!("hello {}", msg.text).into()
+ ).unwrap())
}
-/// adapt between Hyper's types and Sputnik's convenience types
async fn service(req: hyper::Request<hyper::Body>) -> Result<hyper::Response<hyper::Body>, Infallible> {
- let (mut parts, body) = sputnik::request::adapt(req);
+ let (mut parts, body) = req.into_parts();
match route(&mut parts, body).await {
- Ok(res) => Ok(res.into()),
+ Ok(res) => Ok(res),
Err(err) => {
let (code, message) = render_error(err);
// you can easily wrap or log errors here