#+OPTIONS: ':nil *:t -:t ::t <:t H:3 \n:nil ^:t arch:headline #+OPTIONS: author:t broken-links:nil c:nil creator:nil #+OPTIONS: d:(not "LOGBOOK") date:t e:t email:nil f:t inline:t num:t #+OPTIONS: p:nil pri:nil prop:nil stat:t tags:t tasks:t tex:t #+OPTIONS: timestamp:t title:t toc:t todo:t |:t #+TITLE: Rust, Rocket and Diesel #+SUBTITLE: Web apps with rust #+DESCRIPTION: An introduction to Rust, Rocket and Diesel. #+KEYWORDS: rust rocket diesel #+AUTHOR: \href{mailto:rocket-presentation@onders.org}{finga} #+EMAIL: rocket-presentation@onders.org #+LANGUAGE: en #+SELECT_TAGS: export #+EXCLUDE_TAGS: noexport #+CREATOR: Emacs 26.1 (Org mode 9.1.9) #+OPTIONS: H:2 #+LATEX_CLASS: beamer #+LATEX_CLASS_OPTIONS: [aspectration=1610] # #+LATEX_HEADER: \hypersetup{colorlinks=true,linkcolor=gray} #+LATEX_HEADER: \hypersetup{colorlinks=true,linkcolor=black,urlcolor=gray} #+COLUMNS: %45ITEM %10BEAMER_env(Env) %10BEAMER_act(Act) %4BEAMER_col(Col) %8BEAMER_opt(Opt) #+BEAMER_THEME: Frankfurt #+BEAMER_COLOR_THEME: seagull #+BEAMER_FONT_THEME: #+BEAMER_INNER_THEME: #+BEAMER_OUTER_THEME: #+BEAMER_HEADER: \institute[INST]{\href{https://cccsbg.at}{cccsbg}} * Rust ** What is Rust? #+BEGIN_CENTER #+LaTeX:\includegraphics[width = 0.65\textwidth]{img/Bruine_roest_op_tarwe_(Puccinia_recondita_f.sp._tritici_on_Triticum_aestivum).jpg} #+END_CENTER *** Rust, the language - About 10 years young (2010) - Memory safe without gcing, optional ref counting - Ownership, lifetimes, traits - Variables are immutable by default and can be shadowed - Performance of idiomatic Rust is comparable to the performance of idiomatic cpp ** Where to get further information? *** Docs... - [[https://doc.rust-lang.org/book/][The rust book]] (~$ rustup docs --book~) - [[https://doc.rust-lang.org/rust-by-example/][Rust by example]] (~$ rustup docs --rust-by-example~) - [[https://doc.rust-lang.org/std/][The Rust Standard Library]] (~$ rustup docs --std~) - Use '~$ rustup help docs~' to get an overview - [[https://cheats.rs][cheats.rs]] - [[https://programming-idioms.org/cheatsheet/Rust][programming-idioms.org]] - [[https://cargo.io][cargo.io]]/[[https://lib.rs][lib.rs]] *** Chat (Discord) - [[https://discord.gg/rust-lang][The Rust Programming Language]] - [[https://discord.com/invite/tcbkpyQ][The Rust Programming Language Community Server]] ** What are we going to use? *** Definitely :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - [[https://rocket.rs][rocket]] - [[https://diesel.rs][diesel]] - log/env_logger *** Maybe :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - anyhow - lettre - sha3 - serde - chrono ** How to install *** Install Rust #+BEGIN_SRC sh $ curl --proto '=https' --tlsv1.2 -sSf \ https://sh.rustup.rs | sh #+END_SRC *** Install nightly Rust (needed for Rocket) #+BEGIN_SRC sh $ rustup toolchain install nightly #+END_SRC *** Install rustfmt (code formatter) #+BEGIN_SRC sh $ rustup component add rustfmt #+END_SRC *** Install clippy (linter and static code analysis) #+BEGIN_SRC sh $ rustup component add clippy #+END_SRC * Rocket ** What is Rocket? #+BEGIN_QUOTE Rocket is a web framework for Rust that makes it simple to write fast, secure web applications without sacrificing flexibility, usability, or type safety. #+END_QUOTE *** Features **** Col left :PROPERTIES: :BEAMER_col: 0.45 :END: - Type Safe - Extensible - Templating - Cookies **** Col right :PROPERTIES: :BEAMER_col: 0.45 :END: - Streams - Testing (Unit tests) - Typed URIs ** Where to get further information? *** Docs... :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - [[https://rocket.rs][rocket.rs]] - [[https://rocket.rs/v0.4/guide/][Rocket Guide]] - [[https://api.rocket.rs/v0.4/rocket/][Rocket API docs]] - [[https://github.com/SergioBenitez/Rocket/tree/v0.4/examples][Examples]] *** Chat :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - Matrix: [[https://chat.mozilla.org/#/room/%23rocket:mozilla.org][#rocket:mozilla.org]] - IRC on Freenode: [[https://kiwiirc.com/client/chat.freenode.net/#rocket][#rocket]] * Diesel ** What is an ORM? Object-relational-mapping is a programming technique for converting data between incompatible type systems using the paradigms of your programming language. *** Pros :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - Models are DRY (don't repeat yourself) - Cleaner separation - Prepared and sanitised queries - Changes are versioned *** Cons :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - Debugging can get more complicated (performance) - No need to learn SQL ** Prerequisites ~diesel_cli~ *** Setup Diesel for default DBMS #+BEGIN_SRC sh $ sudo apt install libpq-dev libsqlite3-dev \ default-libmysqlclient-dev $ cargo install diesel_cli #+END_SRC *** Setup Diesel for SQLite only #+BEGIN_SRC sh $ sudo apt install libsqlite3-dev $ cargo install diesel_cli -no-default-features \ --features sqlite #+END_SRC ** How does that look in Rust *** Define a struct :PROPERTIES: :BEAMER_col: 0.50 :BEAMER_env: block :END: #+BEGIN_SRC rust #[derive(Queryable)] pub struct NewComment<'a> { name: &'a str, email: &'a str, comment: &'a str, } #+END_SRC *** Insert something :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: #+BEGIN_SRC rust let user = NewUser { name: "foo", email: "foo@bar.baz", comment: "A comment", }; insert_into(comments) .values(&user) .execute(conn); #+END_SRC ** Where to get further information? *** Docs... :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - [[https://diesel.rs][diesel.rs]] - [[https://diesel.rs/guides/][Diesel Guides]] - [[https://docs.diesel.rs/master/diesel/index.html][Diesel API docs]] - [[https://github.com/diesel-rs/diesel/tree/master/examples][Examples]] (minimalistic) *** Support :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - [[https://gitter.im/diesel-rs/diesel][Gitter]] - [[https://discourse.diesel.rs/][Discourse Forum]] * Lets code something ** Lets create a small web app A mini text board where everybody can post and reply. *** Caution HTML is not disabled! ** Let us start #+BEGIN_CENTER #+LaTeX:\includegraphics[width = 0.7\textwidth]{img/challenge-accepted-lets-code-it.jpg} #+END_CENTER *** Steps to reproduce :noexport: **** Create new crate #+BEGIN_SRC sh cargo init simple_text_board cd simple_text_board rustup override set nightly cargo r #+END_SRC **** Setup Rocket ***** Add Rocket to ~Cargo.toml~ #+BEGIN_SRC toml [dependencies] rocket = "0.4" #+END_SRC ***** Add ~Rocket.toml~ #+BEGIN_SRC toml [development] address = "0.0.0.0" port = 8000 workers = 2 keep_alive = 5 log = "normal" #+END_SRC ***** Modify ~src/main.rs~ for Rockets "~Hello, world!~" program #+BEGIN_SRC rust #![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate rocket; #[get("/")] fn index() -> &'static str { "Hello, world!" } fn main() { rocket::ignite().mount("/", routes![index]).launch(); } #+END_SRC **** Create first templates ***** Create the base template at ~templates/base.html.tera~ #+BEGIN_SRC html simple text board {% block content %} {% endblock %} #+END_SRC ***** Create the create template at ~templates/create.html.tera~ #+BEGIN_SRC html {% extends "base" %} {% block content %}

simple text board

create a post

{% if flash %}

{{ flash }}

{% endif %}
{% endblock content %} #+END_SRC ***** Create the ~create_form~ function and add it to routing #+BEGIN_SRC rust #![feature(proc_macro_hygiene, decl_macro)] use std::collections::HashMap; use rocket::{get, request::FlashMessage, routes}; use rocket_contrib::templates::Template; #[get("/create")] fn create_form(flash: Option) -> Template { let mut context = HashMap::new(); if let Some(ref msg) = flash { context.insert("flash", msg.msg()); } Template::render("create", &context) } ... fn main() { rocket::ignite() .mount("/", routes![index, create_form]) .attach(Template::fairing()) .launch(); } #+END_SRC **** Setup Database and posts ***** Create the ~.env~ file #+BEGIN_SRC sh DATABASE_URL=simple_text_board.sqlite #+END_SRC ***** Add diesel to the dependencies and to ~rocket_contrib~ in ~Cargo.toml~ #+BEGIN_SRC toml ... chrono = "0.4" diesel = { version = "1.4", features = ["sqlite", "chrono"] } log = "0.4" ... [dependencies.rocket_contrib] ... features = ["diesel_sqlite_pool", "tera_templates"] #+END_SRC ***** Configure database for Rocket in ~Rocket.toml~ #+BEGIN_SRC toml [global.databases] sqlite = { url = "simple_text_board.sqlite" } #+END_SRC ***** Create the first table schema #+BEGIN_SRC sh diesel setup diesel migration generate create_posts #+END_SRC ***** How to create the table #+BEGIN_SRC sql CREATE TABLE posts ( id INTEGER NOT NULL PRIMARY KEY, parent INTEGER, timestamp DATETIME NOT NULL, author VARCHAR NOT NULL, email VARCHAR NOT NULL, title VARCHAR NOT NULL, content VARCHAR NOT NULL, FOREIGN KEY (parent) REFERENCES posts(id) ); #+END_SRC ***** How to destroy it #+BEGIN_SRC sql DROP TABLE posts; #+END_SRC ***** Applay and test rollback #+BEGIN_SRC sh diesel migration run diesel migration redo #+END_SRC ***** Create data model in ~src/models.rs~ #+BEGIN_SRC rust use crate::schema::posts::{self, dsl::posts as table_posts}; use chrono::NaiveDateTime; use diesel::{QueryResult, RunQueryDsl}; #[derive(Insertable)] #[table_name = "posts"] pub struct NewPost<'a> { pub parent: Option<&'a i32>, pub timestamp: NaiveDateTime, pub author: &'a str, pub email: &'a str, pub title: &'a str, pub content: &'a str, } impl NewPost<'_> { pub fn insert(&self, conn: &diesel::SqliteConnection) -> QueryResult { diesel::insert_into(table_posts).values(self).execute(conn) } } #+END_SRC ***** Add create post functionality to ~src/main.rs~ #+BEGIN_SRC rust #![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate diesel; use std::{collections::HashMap, net::SocketAddr}; use chrono::Local; use diesel::SqliteConnection; use log::{error, info}; use rocket::{ get, post, request::{FlashMessage, Form}, response::{Flash, Redirect}, routes, uri, FromForm, }; use rocket_contrib::{database, templates::Template}; mod models; mod schema; use models::NewPost; #[database("sqlite")] struct DbCon(SqliteConnection); #[derive(FromForm)] pub struct PostForm { author: String, email: String, title: String, content: String, } ... #[post("/create", data = "")] fn create_post( conn: DbCon, remote_address: SocketAddr, post: Form, ) -> Result> { let post = NewPost { parent: None, timestamp: Local::now().naive_local(), author: &post.author.trim(), email: &post.email.trim(), title: &post.title.trim(), content: &post.content.trim(), }; match post.insert(&conn) { Ok(_) => { info!("New post from {}", remote_address); Ok(Redirect::to(uri!(index))) } Err(e) => { error!("Could not create post from {}: {:?}", remote_address, e); Err(Flash::error( Redirect::to(uri!(create_form)), "Could not create post.".to_string(), )) } } } ... fn main() { rocket::ignite() .mount("/", routes![index, create_form, create_post]) .attach(DbCon::fairing()) .attach(Template::fairing()) .launch(); } #+END_SRC * Conclusion ** What did we do? - Setup Rust and Diesel - Create a project with Rocket and Diesel prerequisites - Handle a SQLite DB with Diesel - Create Tera templates ** What did you hopefully learn! ** Two small projects *** [[https://git.onders.org/finga/ldap0r][ldap0r]] :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - LDAP password reset tool - ($\sim$ 300 SloC) - First release - Tests *** [[https://git.onders.org/finga/filerly][filerly]] :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - File sharing a la NextCloud - Not yet ready, work in progress... ** The End #+BEGIN_CENTER #+LaTeX:\includegraphics[width = 0.3\textwidth]{img/goodbye.jpg} \Huge Thanks for your attention!!1! This is the end.. #+END_CENTER