Rocket in Gotham with Diesel, oh my!


TODO Microservice
- Get all tasks
- Add a new task
- Update a task
- Delete a task
Rust Frameworks
- Rocket
- Gotham
- Diesel
Diesel orm

diesel cli
$ cargo install diesel_cli
$ diesel setupMigrations
Create the migrations directory
$ diesel migration generate tasksCREATE TABLE tasks (
id SERIAL PRIMARY KEY,
task VARCHAR NOT NULL,
completed BOOLEAN NOT NULL DEFAULT 'f'
)DROP TABLE tasksup.sql
down.sql
adding the models
model.rs
#[derive(Queryable)]
pub struct Task {
id: i32,
task: String,
completed: bool,
}
#[derive(Insertable)]
#[table_name = "tasks"]
pub struct NewTask {
task: String,
completed: bool,
}
generate the schema
$ diesel print-schema > src/schema.rsschema.rs
table! {
tasks (id) {
id -> Integer,
task -> Varchar,
completed -> Bool,
}
}Adding the functionality
impl Task {
// ...
pub fn create(conn: &PgConnection, task: NewTask) {
use schema::tasks;
diesel::insert_into(tasks::table)
.values(&task)
.execute(conn)
.expect("Unable to insert");
}
// ...
}Create a new task
Adding the functionality
impl Task {
// ...
pub fn all(conn: &PgConnection) -> Vec<Task> {
use schema::tasks::dsl::*;
tasks.load::<Task>(conn).expect("Could not load tasks")
}
// ...
}Retrieve all tasks
Adding the functionality
impl Task {
// ...
pub fn update(conn: &PgConnection, task_id: i32, task_update: Task) -> i32 {
use schema::tasks::dsl::*;
diesel::update(tasks.find(task_id))
.set((
task.eq(task_update.task),
completed.eq(task_update.completed),
))
.execute(conn)
.expect("Failed to update");
task_update.id
}
pub fn delete(conn: &PgConnection, task_id: i32) {
use schema::tasks::dsl::*;
diesel::delete(tasks.find(task_id))
.execute(conn)
.expect("Failed to delete");
}
// ...
}Update and delete tasks
Rocket

Managing database connection
type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
fn init_pool() -> Pool {
dotenv::dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let manager = ConnectionManager::<PgConnection>::new(database_url);
r2d2::Pool::new(manager).expect("db pool")
}
pub fn start() {
rocket::ignite()
.manage(init_pool())
.launch();
}
Managing database connection
impl<'a, 'r> FromRequest<'a, 'r> for DbConn {
type Error = ();
fn from_request(request: &'a Request<'r>) -> request::Outcome<DbConn, ()> {
let pool = request.guard::<State<Pool>>()?;
match pool.get() {
Ok(conn) => Outcome::Success(DbConn(conn)),
Err(_) => Outcome::Failure((Status::ServiceUnavailable, ())),
}
}
}Adding request handlers
#[get("/")]
fn get_all_tasks(conn: DbConn) -> Json<Vec<Task>> {
Json(Task::all(&conn))
}
pub fn start() {
rocket::ignite()
.mount("/tasks", routes![get_all_tasks])
.manage(init_pool())
.launch();
}Serializing the data
#[derive(Deserialize, Serialize, Queryable)]
pub struct Task {
id: i32,
task: String,
completed: bool,
}
#[derive(Deserialize, Serialize, Insertable)]
#[table_name = "tasks"]
pub struct NewTask {
task: String,
completed: bool,
}
Adding the Remaining Requests
#[post("/", data = "<task>")]
fn create_task(task: Json<NewTask>, conn: DbConn) -> Json<String> {
Task::create(&conn, task.into_inner());
Json("Task added".to_owned())
}
#[put("/<id>", data = "<task>")]
fn update_task(id: u32, task: Json<Task>, conn: DbConn) -> Json<i32> {
let id = Task::update(&conn, id as i32, task.into_inner());
Json(id)
}
#[delete("/<id>")]
fn delete_task(id: u32, conn: DbConn) -> Json<Value> {
Task::delete(&conn, id as i32);
Json(json!({"status": "ok"}))
}Common ground


Getting it started
use gotham::router::Router;
use gotham::router::builder::*;
fn router -> Router {
build_simple_router(|route|{
route.get("/tasks").to(get_all_tasks);
})
}
pub fn start() {
let addr = "127.0.0.1:8000";
println!("Listening for requests at http://{}", addr);
gotham::start(addr, router())
}Starting to make a handler
extern crate hyper;
extern crate mime;
extern crate serde_json;
use gotham::state::State;
use gotham::http::response::create_response;
use hyper::{Response, StatusCode};
impl IntoResponse for TaskList {
fn into_response(self, state: &State) -> Response {
create_response(
&state,
StatusCode::Ok,
Some((
serde_json::to_string(&self.list)
.expect("serialized product")
.into_bytes(),
mime::APPLICATION_JSON,
)),
)
}
}// ...
#[derive(Deserialize, Serialize)]
pub struct TaskList {
pub list: Vec<Task>,
}
// ...model.rs
making the handler
fn get_all_tasks(state: State) -> (State, TaskList) {
let tasks = vec![
Task {
id: 1,
task: "Do homework".to_owned(),
completed: false,
},
];
(state, tasks)
}manage the database connection
#[derive(StateData)]
struct PoolState(Pool);- Manage through the state
- Handler signature
- Middleware injects this into the state
middleware
use gotham::middleware::Middleware;
#[derive(Clone, NewMiddleware)]
struct DbConnMiddleware;
impl Middleware for DbConnMiddleware {
fn call<Chain>(self, mut state: State, chain: Chain) -> Box<HandlerFuture>
where
Chain: FnOnce(State) -> Box<HandlerFuture>,
{
if !state.has::<PoolState>() {
// Initialize it
state.put(PoolState(init_pool()));
}
chain(state)
}
}Add to the router
use gotham::pipeline::single::single_pipeline;
fn router() -> Router {
let (chain, pipelines) = single_pipeline(new_pipeline().add(DbConnMiddleware).build());
build_router(chain, pipelines, |route| {
route.get_or_head("/").to(index);
route.get("/tasks").to(get_all_tasks);
})
}Retrieving tasks
fn db_conn(state: &State) -> Option<DbConn> {
state.borrow::<PoolState>().get().ok().map(|x| DbConn(x))
}
// ...
fn get_all_tasks(state: State) -> (State, TaskList) {
let conn = db_conn(&state).expect("Failed with DB connection");
let tasks = TaskList {
list: Task::all(&conn),
};
(state, tasks)
}Using path variables
#[derive(Deserialize, StateData, StaticResponseExtender)]
struct PathId {
id: u32,
}
// ...
route
.put("/task/:id")
.with_path_extractor::<PathId>()
.to(update_task);
route
.delete("/task/:id")
.with_path_extractor::<PathId>()
.to(delete_task);
// ...Router
Delete a task
fn delete_task(mut state: State) -> (State, Response) {
let PathId { id } = PathId::take_from(&mut state);
let conn = db_conn(&state).expect("Failed with DB connection");
Task::delete(&conn, id as i32);
let resp = create_response(&state, StatusCode::Ok, None);
(state, resp)
}request body
use hyper::Body;
let body = Body::take_from(&mut state)
.concat2()
.then(// Do something with the body);fn body_handler<F>(mut state: State, f: F) -> Box<HandlerFuture>
where
F: 'static + Fn(String, &State) -> Response,
{
let body = Body::take_from(&mut state)
.concat2()
.then(move |full_body| match full_body {
Ok(valid_body) => {
let body_content = String::from_utf8(valid_body.to_vec()).unwrap();
let res = f(body_content, &mut state);
future::ok((state, res))
}
Err(e) => return future::err((state, e.into_handler_error())),
});
Box::new(body)
}create and update
fn create_task(state: State) -> Box<HandlerFuture> {
body_handler(state, |s, state| {
let task = serde_json::from_str(&s).expect("Failed to deserialize");
let conn = db_conn(state).expect("Failed with DB connection");
Task::create(&conn, task);
create_response(state, StatusCode::Ok, None)
})
}
fn update_task(mut state: State) -> Box<HandlerFuture> {
let PathId { id } = PathId::take_from(&mut state);
body_handler(state, move |s, state| {
let task = serde_json::from_str(&s).expect("Failed to deserialize");
let conn = db_conn(&state).expect("Failed with DB connection");
Task::update(&conn, id as i32, task);
create_response(state, StatusCode::Ok, None)
})
}

thank you

maccoda
Rocket in Gotham with Diesel, oh my!
By maccoda
Rocket in Gotham with Diesel, oh my!
- 299