loading...

🚀 Rocket.rs with no word and 4 snippets 🚀

sayanarijit profile image Arijit Basu ・4 min read

If you are one of them who prefer to write code before directly diving into the documentation, check this out.

This is a 4-snippets demo I have compiled for myself, combining the most important concepts (IMO) of Rocket v0.4.5, the popular Rust web backend framework (while reading the official documentation), you might find useful too.

Assuming you have working knowledge of web backends and Rust and you have rust nightly installed and setup,

Let's start.


Cargo.toml

[package]
name = "rocker_app"
version = "0.1.0"
authors = ["Arijit Basu <sayanarijit@gmail.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rocket = "0.4.5"
serde = { version = "1.0", features = ["derive"] }
postgres = { version = "0.15", features = ["with-chrono", "with-uuid"] }

[dependencies.rocket_contrib]
version = "0.4.5"
default-features = false
features = ["json", "serve", "diesel_sqlite_pool", "diesel_postgres_pool", "redis_pool"]

Rocket.toml

# TIP: Use `ROCKET_{VAR}` env vars to override.
# e.g. ROCKET_DATABASES={sqlitedb={url="foo.sqlite"}} cargo run

# [global.limits]
# json = 5242880
# forms = 5242880

[global.databases.sqlitedb]
url = "db.sqlite"
pool_size = 1

[global.databases.pgdb]
url = "postgres://postgres:postgres@localhost:5432/db"
pool_size = 1

[development]
address = "localhost"
port = 8000
workers = 2
keep_alive = 5
log = "normal"
# secret_key = [randomly generated at launch]

[staging]
address = "0.0.0.0"
port = 8000
workers = 4
keep_alive = 5
log = "normal"
# secret_key = [randomly generated at launch]

[production]
address = "0.0.0.0"
port = 8000
workers = 4
keep_alive = 5
log = "critical"
# secret_key = [randomly generated at launch]

src/main.rs

#![feature(proc_macro_hygiene, decl_macro)]


#[macro_use] extern crate rocket;
#[macro_use] extern crate rocket_contrib;

use serde::{Serialize, Deserialize};        // Helps with serializing and deserializing data

use rocket::Request;                        // The HTTP request
use rocket::uri;                            // Robust URL formatter
use rocket::http::RawStr;                   // Represents unvalidated data from URL params / POST data
use rocket::request::{Form, FromFormValue}; // Encodes/Decodes the RawStr to validated data structures
// use rocket::response::content;           // Sets the Content-Type header
use rocket::response::status;               // Sets the Status header
use rocket::fairing::AdHoc;                 // Allows attaching ad-hoc fairings
use rocket_contrib::databases::diesel;      // ORM
use rocket_contrib::serve::StaticFiles;     // Creates routes for serving static files
use rocket_contrib::json::Json;             // JSON helper


// See Rocket.toml
#[database("sqlitedb")]
struct SQLiteDB(diesel::SqliteConnection);

// See Rocket.toml
#[database("pgdb")]
struct PGDB(diesel::PgConnection);

// A basic User schema
#[derive(FromForm, Serialize, Deserialize, UriDisplayQuery)]
struct User {
    name: String,
    account: usize,
}

// A wealthy User schema
#[derive(FromForm)]
struct WealthyUser {
    name: String,
    account: WealthyAccount,
}

struct WealthyAccount(usize);

impl<'v> FromFormValue<'v> for WealthyAccount {
    type Error = &'v RawStr;

    fn from_form_value(form_value: &'v RawStr) -> Result<WealthyAccount, &'v RawStr> {
        match form_value.parse::<usize>() {
            Ok(account) if account >= 1000 => Ok(WealthyAccount(account)),
            _ => Err(form_value),
        }
    }
}


#[get("/")]
fn index(_db: SQLiteDB) -> &'static str {

    // curl -si 'http://localhost:8000'

    "Hello, world!"
}

#[get("/user?<id>&<user..>", rank = 2)]
fn user_get(_db: SQLiteDB, id: u8, user: Form<User>) -> String {
    // Use Option<From<User>> or Result<From<User>> for error handling.

    // curl -si 'http://localhost:8000/user?id=1&name=foo&account=1'

    format!("Hello, {} ({}) [{}]", user.name, user.account, id)
}

#[get("/user?<id>&<user..>", rank = 1)]
fn user_get_wealthy(_db: SQLiteDB, id: u8, user: Form<WealthyUser>) -> Option<String> {

    // curl -si 'http://localhost:8000/user?id=1&name=foo&account=1000'

    // Return None to respond with 404 not found
    Some(format!("Hello dear, {} ({}) [{}]", user.name, user.account.0, id))
}

#[post("/user", data = "<user>", rank = 1)]
fn user_post(_db: SQLiteDB, user: Form<User>) -> status::Accepted<String> {

    // curl -si 'http://localhost:8000/user' -X POST -d 'name=foo&account=122' 

    status::Accepted(Some(uri!(user_get: id = 1, user = user.0).to_string()))
}

#[post("/user", data = "<user>", format = "json", rank = 2)]
fn user_post_json(
    _db: SQLiteDB,
    user: Json<User>
) -> Result<status::Accepted<Json<User>>, status::BadRequest<Json<String>>> {

    // curl -si 'http://localhost:8000/user' -X POST -d '{"name": "x", "account": 122}' -H 'Content-Type: application/json'

    match user.name.len() {
        0 => Err(status::BadRequest(Some(Json("Name cannot be empty".to_string())))),
        _ => Ok(status::Accepted(Some(user))),
    }
}

#[catch(404)]
fn not_found(req: &Request) -> status::NotFound<String> {

    // curl -si 'http://localhost:8000/foo'

    status::NotFound(format!("Sorry, '{}' is not a valid path.", req.uri()))
}

fn main() {

    // TIP: For staging/productioon, run `ROCKET_ENV=<stage/prod> cargo run`

    rocket::ignite()
        .attach(SQLiteDB::fairing())
        // .attach(PGDB::fairing())
        .attach(AdHoc::on_launch("Custom Fairing", |_| { println!("Launching App") }))
        .mount("/", routes![
            index,
            user_get,
            user_get_wealthy,   // when account value >= 1000
            user_post,
            user_post_json      // When Content-Type: application/json
        ])
        .mount("/static", StaticFiles::from("./src/static"))
        .register(catchers![not_found])
        .launch();
}

#[cfg(test)]
mod test;

src/test.rs

use super::*;
use rocket::local::Client;
use rocket::http::Status;

// TIP: Use `ROCKET_CODEGEN_DEBUG=1 cargo build` to expand code

#[test]
fn hello_world() {
    let rocket = rocket::ignite().attach(SQLiteDB::fairing()).mount("/", routes![index]);
    let client = Client::new(rocket).expect("valid rocket instance");

    let mut response = client.get("/").dispatch();

    assert_eq!(response.status(), Status::Ok);
    assert_eq!(response.body_string(), Some("Hello, world!".into()));
}

Here's the gist for your forking needs.

Discussion

markdown guide