From 67fe89dcc74ea745b5147f01b368ffd6a9cac83b Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 6 Jul 2017 01:58:57 -0700 Subject: [PATCH] Update state guide for 0.3. Add databases section. --- site/guide/state.md | 199 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 175 insertions(+), 24 deletions(-) diff --git a/site/guide/state.md b/site/guide/state.md index 86f38d6e..56df3e3a 100644 --- a/site/guide/state.md +++ b/site/guide/state.md @@ -22,15 +22,17 @@ The process for using managed state is simple: ### Adding State To instruct Rocket to manage state for your application, call the -[manage](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) method -on a `Rocket` instance. For example, to ask Rocket to manage a `HitCount` +[`manage`](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) method +on an instance of `Rocket`. For example, to ask Rocket to manage a `HitCount` structure with an internal `AtomicUsize` with an initial value of `0`, we can write the following: ```rust -struct HitCount(AtomicUsize); +struct HitCount { + count: AtomicUsize +} -rocket::ignite().manage(HitCount(AtomicUsize::new(0))); +rocket::ignite().manage(HitCount { count: AtomicUsize::new(0) }); ``` The `manage` method can be called any number of times as long as each call @@ -39,23 +41,23 @@ a `HitCount` value and a `Config` value, we can write: ```rust rocket::ignite() - .manage(HitCount(AtomicUsize::new(0))) - .manage(Config::from(user_input)); + .manage(HitCount { count: AtomicUsize::new(0) }) + .manage(Config::from(user_input)); ``` ### Retrieving State State that is being managed by Rocket can be retrieved via the -[State](https://api.rocket.rs/rocket/struct.State.html) type: a [request +[`State`](https://api.rocket.rs/rocket/struct.State.html) type: a [request guard](/guide/requests/#request-guards) for managed state. To use the request -guard, add a `State` type to any request handler, where `T` is the -type of the managed state. For example, we can retrieve and respond with the -current `HitCount` in a `count` route as follows: +guard, add a `State` type to any request handler, where `T` is the type of +the managed state. For example, we can retrieve and respond with the current +`HitCount` in a `count` route as follows: ```rust #[get("/count")] fn count(hit_count: State) -> String { - let current_count = hit_count.0.load(Ordering::Relaxed); + let current_count = hit_count.count.load(Ordering::Relaxed); format!("Number of visits: {}", current_count) } ``` @@ -67,20 +69,22 @@ You can retrieve more than one `State` type in a single route as well: fn state(hit_count: State, config: State) -> T { ... } ``` +### Within a Request Guard + It can also be useful to retrieve managed state from a `FromRequest` -implementation. To do so, invoke the `from_request` method of a `State` type -directly, passing in the `req` parameter of `from_request`: +implementation. To do so, simple invoke `State` as a guard using the +[`Request::guard()`] method. ```rust fn from_request(req: &'a Request<'r>) -> request::Outcome { - let count = match as FromRequest>::from_request(req) { - Outcome::Success(count) => count, - ... - }; + let hit_count_state = req.guard::>()?; + let current_count = hit_count_state.count.load(Ordering::Relaxed); ... } ``` +[`Request::guard()`]: https://api.rocket.rs/rocket/struct.Request.html#method.guard + ### Unmanaged State If you request a `State` for a `T` that is not `managed`, Rocket won't call @@ -100,7 +104,7 @@ type from previous examples: ```rust #[get("/count")] fn count(hit_count: State) -> String { - let current_count = hit_count.0.load(Ordering::Relaxed); + let current_count = hit_count.count.load(Ordering::Relaxed); format!("Number of visits: {}", current_count) } @@ -120,7 +124,7 @@ warning: HitCount is not currently being managed by Rocket --> src/main.rs:2:17 | 2 | fn count(hit_count: State) -> String { - | ^^^^^^^^^^^^^^^ + | ^^^^^^^^^ | = note: this State request guard will always fail help: maybe add a call to 'manage' here? @@ -131,14 +135,161 @@ help: maybe add a call to 'manage' here? ``` The `unmanaged_state` lint isn't perfect. In particular, it cannot track calls -to `manage` across function boundaries. You can disable the lint on a per-route -basis by adding `#[allow(unmanaged_state)]` to a route handler. If you wish to -disable the lint globally, add `#![allow(unmanaged_state)]` to your crate -attributes. +to `manage` across function boundaries. Because of this, you may find yourself +with incorrect warnings. You can disable the lint on a per-route basis by adding +`#[allow(unmanaged_state)]` to a route handler. If you wish to disable the lint +globally, add `#![allow(unmanaged_state)]` to your crate attributes. -You can find a complete example using the `HitCounter` structure in the [state +You can find a complete example using the `HitCount` structure in the [state example on GitHub](https://github.com/SergioBenitez/Rocket/tree/v0.2.8/examples/state) and learn more about the [manage method](https://api.rocket.rs/rocket/struct.Rocket.html#method.manage) and [State type](https://api.rocket.rs/rocket/struct.State.html) in the API docs. + +## Databases + +While Rocket doesn't have built-in support for databases yet, you can combine a +few external libraries to get native-feeling access to databases in a Rocket +application. Let's take a look at how we might integrate Rocket with two common +database libraries: [`diesel`], a type-safe ORM and query builder, and [`r2d2`], +a library for connection pooling. + +Our approach will be to have Rocket manage a pool of database connections using +managed state and then implement a request guard that retrieves one connection. +This will allow us to get access to the database in a handler by simply adding a +`DbConn` argument: + +```rust +#[get("/users")] +fn handler(conn: DbConn) { ... } +``` + +[`diesel`]: http://diesel.rs/ +[`r2d2`]: https://docs.rs/r2d2/0.7.2/r2d2/ + +### Dependencies + +To get started, we need to depend on the `diesel` and `r2d2` crates. For +detailed information on how to use Diesel, please see the [Diesel getting +started guide](http://diesel.rs/guides/getting-started/). For this example, we +use the following dependencies: + +```toml +[dependencies] +rocket = "0.2.8" +diesel = { version = "*", features = ["sqlite"] } +diesel_codegen = { version = "*", features = ["sqlite"] } +r2d2-diesel = "*" +r2d2 = "*" +``` + +Your `diesel` dependency information will differ. In particular, you should +specify the latest versions of these libraries as opposed to using a `*`. The +crates are imported as well: + +```rust +extern crate rocket; +#[macro_use] extern crate diesel; +#[macro_use] extern crate diesel_codegen; +extern crate r2d2_diesel; +extern crate r2d2; +``` + +### Managed Pool + +The first step is to initialize a pool of database connections. The `init_pool` +function below uses `r2d2` to create a new pool of database connections. Diesel +advocates for using a `DATABASE_URL` environment variable to set the database +URL, and we use the same convention here. Excepting the long-winded types, the +code is fairly straightforward: the `DATABASE_URL` environment variable is +stored in the `DATABASE_URL` static, and an `r2d2::Pool` is created using the +default configuration parameter and a Diesel `SqliteConnection` +`ConnectionManager`. + +```rust +use diesel::sqlite::SqliteConnection; +use r2d2_diesel::ConnectionManager; + +// An alias to the type for a pool of Diesel SQLite connections. +type Pool = r2d2::Pool>; + +// The URL to the database, set via the `DATABASE_URL` environment variable. +static DATABASE_URL: &'static str = env!("DATABASE_URL"); + +/// Initializes a database pool. +fn init_pool() -> Pool { + let config = r2d2::Config::default(); + let manager = ConnectionManager::::new(DATABASE_URL); + r2d2::Pool::new(config, manager).expect("db pool") +} +``` + +We then use managed state to have Rocket manage the pool for us: + +```rust +fn main() { + rocket::ignite() + .manage(init_pool()) + .launch(); +} +``` + +### Connection Request Guard + +The second and final step is to implement a request guard that retrieves a +single connection from the managed connection pool. We create a new type, +`DbConn`, that wraps an `r2d2` pooled connection. We then implement +`FromRequest` for `DbConn` so that we can use it as a request guard. Finally, we +implement `Deref` with a target of `SqliteConnection` so that we can +transparently use an `&DbConn` as an `&SqliteConnection`. + +```rust +use std::ops::Deref; +use rocket::http::Status; +use rocket::request::{self, FromRequest}; +use rocket::{Request, State, Outcome}; + +// Connection request guard type: a wrapper around an r2d2 pooled connection. +pub struct DbConn(pub r2d2::PooledConnection>); + +/// Attempts to retrieve a single connection from the managed database pool. If +/// no pool is currently managed, fails with an `InternalServerError` status. If +/// no connections are available, fails with a `ServiceUnavailable` status. +impl<'a, 'r> FromRequest<'a, 'r> for DbConn { + type Error = (); + + fn from_request(request: &'a Request<'r>) -> request::Outcome { + let pool = request.guard::>()?; + match pool.get() { + Ok(conn) => Outcome::Success(DbConn(conn)), + Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())) + } + } +} + +// For the convenience of using an &DbConn as an &SqliteConnection. +impl Deref for DbConn { + type Target = SqliteConnection; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +``` + +### Usage + +With these two pieces in place, we can use `DbConn` as a request guard in any +handler or other request guard implementation, giving our application access to +a database. As a simple example, we might write a route that returns a JSON +array of some `Task` structures that are fetched from a database: + +```rust +#[get("/tasks")] +fn get_tasks(conn: DbConn) -> QueryResult>> { + all_tasks.order(tasks::id.desc()) + .load::(&conn) + .map(|tasks| JSON(tasks)) +} +```