diff --git a/rust_web.org b/rust_web.org index 2291ef7..d7755be 100644 --- a/rust_web.org +++ b/rust_web.org @@ -1,16 +1,14 @@ -#+STARTUP: beamer #+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 -#+OPTIONS: ^:nil #+TITLE: Rust, Rocket and Diesel -#+SUBTITLE: Web apps with Rust +#+SUBTITLE: Web apps with rust #+DESCRIPTION: An introduction to Rust, Rocket and Diesel. #+KEYWORDS: rust rocket diesel -#+AUTHOR: \href{mailto:finga@onders.org}{finga} -#+EMAIL: finga@onders.org +#+AUTHOR: \href{mailto:rocket-presentation@onders.org}{finga} +#+EMAIL: rocket-presentation@onders.org #+LANGUAGE: en #+SELECT_TAGS: export #+EXCLUDE_TAGS: noexport @@ -18,10 +16,9 @@ #+OPTIONS: H:2 #+LATEX_CLASS: beamer -#+LATEX_CLASS_OPTIONS: [aspectratio=1610] +#+LATEX_CLASS_OPTIONS: [aspectration=1610] # #+LATEX_HEADER: \hypersetup{colorlinks=true,linkcolor=gray} #+LATEX_HEADER: \hypersetup{colorlinks=true,linkcolor=black,urlcolor=gray} -# #+LATEX_HEADER: \usepackage{listings, listings-rust} #+COLUMNS: %45ITEM %10BEAMER_env(Env) %10BEAMER_act(Act) %4BEAMER_col(Col) %8BEAMER_opt(Opt) #+BEAMER_THEME: Frankfurt #+BEAMER_COLOR_THEME: seagull @@ -40,11 +37,48 @@ *** Rust, the language - About 10 years young (2010) - Memory safe without gcing, optional ref counting - - Ownership, lifetimes, traits, functional paradigms + - 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 @@ -58,209 +92,16 @@ $ rustup toolchain install nightly #+END_SRC -*** Install rustfmt (linter and code formatter) +*** Install rustfmt (code formatter) #+BEGIN_SRC sh $ rustup component add rustfmt #+END_SRC -*** Install clippy (static code analysis) +*** Install clippy (linter and static code analysis) #+BEGIN_SRC sh $ rustup component add clippy #+END_SRC -** Where to find more 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://crates.io][crates.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]] - -** Hello, World! - Executing ~`cargo new foobar`~ creates a new project... - -*** foobar/Cargo.toml - #+BEGIN_SRC toml - [package] - name = "foo" - version = "0.1.0" - authors = ["finga "] - edition = "2018" - #+END_SRC - \tiny - #+BEGIN_SRC toml - # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - #+END_SRC - \normalsize - #+BEGIN_SRC toml - [dependencies] - #+END_SRC - -*** foobar/src/main.rs - #+BEGIN_SRC rust - fn main() { - println!("Hello, world!"); - } - #+END_SRC - -** Mutability and shadowing - -*** Immutable variable - #+BEGIN_SRC rust - let foo = 5; - #+END_SRC - -*** Mutable variable - #+BEGIN_SRC rust - let mut bar = 5; - bar = 6; - #+END_SRC - -*** Shadowing - #+BEGIN_SRC rust - let baz = 5; - let baz = baz + 3; - #+END_SRC - -** Structs - -*** Definition of a struct - #+BEGIN_SRC rust - struct Something { - id: usize, - item: String, - } - #+END_SRC - -*** Instantiation of a struct - #+BEGIN_SRC rust - let something = Something { - id: 0, - item: String::from("an item"), - }; - #+END_SRC - -*** Access of a struct's field - #+BEGIN_SRC rust - something.item - #+END_SRC - -** Tuple structs - -*** Definition of a tuple struct - #+BEGIN_SRC rust - struct Box(usize, usize, usize); - #+END_SRC - -*** Instantiation of a tuple struct - #+BEGIN_SRC rust - let first_box = Box(3, 5, 7); - #+END_SRC - -*** Access of a tuple struct's field - #+BEGIN_SRC rust - first_box.0; - first_box.1; - first_box.2; - #+END_SRC - -** Error related types - - Heavy use of ~Option~ and ~Result~ - -*** Option - #+BEGIN_SRC rust - pub enum Option { - None, - Some(T), - } - #+END_SRC - -*** Result - #+BEGIN_SRC rust - pub enum Result { - Ok(T), - Err(E), - } - #+END_SRC - -** Error handling - -*** Unrecoverable errors \rightarrow panics - #+BEGIN_SRC rust - fn get_data() -> Data { - ... - panic!("Cannot get data"); - } - #+END_SRC - -*** Recoverable error with anyhow - #+BEGIN_SRC rust - fn get_data() -> Result { - ... - Ok(data) // return is only used for "early" returns - } - ... - let data = get_data()?; - ... - #+END_SRC - -** ~match~ statements - - Must be exhaustive - - All match arms must return the same type! - - Can be used everywhere e.g. ~let foo = match ...~ - - @@latex:{\color{red}@@All match arms must return the same type!!1!@@latex:}@@ - -*** Recoverable error with anyhow - #+BEGIN_SRC rust - match age { - 0 => unreachable!("Too young to be a person"), - n @ 1..=12 => println!("Child of age {}", n), - n @ 13..=19 => println!("Teen of age {}", n), - n @ 20..=150 => println!("Person of age {}", n), - _ => unimplemented!("Nobody I know got that old"), - } - #+END_SRC - -** ~if let~ statements - -*** Sometimes an ~if let~ is nicer than a ~match~ or an ~if/else~ statement - #+BEGIN_SRC rust - if let Some(actual_age) = age { - println!("The age is {}", actual_age); - } - #+END_SRC - -** Use functional language features - - ~vec![1, 2, 3].iter()~ - - ~vec![1, 2, 3].iter().sum()~ - - ~vec![1, 2, 3].iter().map(|x| x + 1).collect()~ - - ~foo.into_iter().filter(|i| i.bar == 0).collect()~ - - ~Iterator~ trait - -** Tests - - Use ~assert!()~ family - - Unit tests - - Put them into a test module - - Annotate the test module with ~#[cfg(test)]~ - - Annotate the tests with ~#[test]~ - - Documentation testing - - Integration testing - - Integration tests are put into the ~src/tests/~ directory - -** Docs - - Documentation comments: ~///~ - - Markdown notation - - ~cargo doc~ runns ~rustdoc~ which generates html docs - - ~cargo doc --open~ runs ~rustdoc~ and opens the result in a browser - - ~rustdoc~ documentation: https://doc.rust-lang.org/beta/rustdoc/index.html - * Rocket ** What is Rocket? @@ -279,7 +120,7 @@ - Type Safe - Extensible - Templating - - (Secure) Cookies + - Cookies **** Col right :PROPERTIES: @@ -288,112 +129,6 @@ - Streams - Testing (Unit tests) - Typed URIs - - Single binary - -** Create a new Rocket project -*** Create the crate - #+BEGIN_SRC sh - cargo init new_project - cd new_project - rustup override set nightly - cargo r - #+END_SRC - -*** Add Rocket dependencies to ~Cargo.toml~ - #+BEGIN_SRC toml - [dependencies] - rocket = "0.4" - #+END_SRC - -** Hello Rocket -*** ~src/main.rs~ - #+BEGIN_SRC rust - #![feature(decl_macro)] - - use rocket::{get, routes}; - - #[get("/hello//")] - fn hello(name: String, age: u8) -> String { - format!("Hello, {} year old named {}!", age, name) - } - - fn main() { - rocket::ignite().mount("/", routes![hello]).launch(); - } - #+END_SRC - -** Receive data -*** Struct ~EmailForm~ - #+BEGIN_SRC rust - #[derive(Debug, FromForm)] - struct EmailForm { - email: String, - } - #+END_SRC - -*** Procedure using that form data - #+BEGIN_SRC rust - #[post("/reset", data = "")] - fn reset_email(email: Form) -> Flash { - ... - Flash::success( - Redirect::to(uri!(reset)), - format!("Email was sent to {}", email.email), - ) - } - #+END_SRC - -** Flash messages - #+BEGIN_SRC rust - #[get("/reset")] - fn reset(flash: Option) -> String { - if let Some(ref msg) = flash { // `let ref x = y` is - msg.msg() // equivalent to `let x = &y` - } else { - format!("Hello, reset!"); - } - } - #+END_SRC - -** Templates - - Use the ~rocket_contrib~ crate! -*** ~templates/name.html.tera~ - :PROPERTIES: - :BEAMER_col: 0.35 - :BEAMER_env: block - :END: - #+BEGIN_SRC html - - - - ... - - -

Hello, - {{ name }}!

- - - #+END_SRC - -*** ~src/main.rs~ - :PROPERTIES: - :BEAMER_col: 0.60 - :BEAMER_env: block - :END: - #+BEGIN_SRC rust - #[get("/hello/")] - fn hello(name: String) -> Template { - let mut context = HashMap::new(); - context.insert("name", name); - - Template::render("name", &context) - } - ... - rocket::ignite() - .mount(...) - .attach(Template::fairing()) - .launch(); - #+END_SRC ** Where to get further information? @@ -417,7 +152,7 @@ * Diesel -** ORM +** 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. @@ -484,21 +219,11 @@ comment: "A comment", }; - ... - insert_into(comments) .values(&user) .execute(conn); #+END_SRC -** About Diesel - - Enable connection pooling via the ~r2d2~ crate - - Nested joins are not yet nicely implemented - - Database specific SQL regarding each DBMS - - Take a look at the [[https://github.com/diesel-rs/diesel/tree/master/diesel_cli][diesel_cli]] crate for multi DBMS support - - Use ~diesel_cli~ macros and functions for compiling migration - functionality into the application - ** Where to get further information? *** Docs... @@ -522,7 +247,7 @@ * Lets code something ** Lets create a small web app - A chat box where everybody can post. + A mini text board where everybody can post and reply. *** Caution HTML is not disabled! @@ -561,20 +286,18 @@ ***** Modify ~src/main.rs~ for Rockets "~Hello, world!~" program #+BEGIN_SRC rust - // #![feature(proc_macro_hygiene, decl_macro)] - #![feature(decl_macro)] + #![feature(proc_macro_hygiene, decl_macro)] - // #[macro_use] extern crate rocket; - // #[macro_use] extern crate diesel; + #[macro_use] extern crate rocket; - #[get("/")] - fn index() -> &'static str { - "Hello, world!" - } + #[get("/")] + fn index() -> &'static str { + "Hello, world!" + } - fn main() { - rocket::ignite().mount("/", routes![index]).launch(); - } + fn main() { + rocket::ignite().mount("/", routes![index]).launch(); + } #+END_SRC **** Create first templates @@ -812,109 +535,15 @@ } #+END_SRC -**** Create a template to list all posts - -***** Add a template for the index page ~templates/index.html.tera~ - #+BEGIN_SRC html - {% extends "base" %} - - {% block content %} -

simple text board

-

index

-
    - {% for post in posts %} -
  • {{ loop.index }} - {{ post.title }} - {{ post.author }}
  • - {% endfor %} -
- {% endblock content %} - #+END_SRC - -***** Add serde dependencies to ~Cargo.toml~ - #+BEGIN_SRC toml - chrono = { version = "0.4", features = ["serde"] } - ... - serde = { version = "1.0", features = ["derive"] } - #+END_SRC - -***** Add `Post` to ~src/models.rs~ - #+BEGIN_SRC rust - ... - use diesel::{QueryResult, RunQueryDsl, SqliteConnection}; - use serde::Serialize; - - ... - - #[derive(Debug, Queryable, Serialize)] - pub struct Post { - pub id: i32, - pub parent: Option, - pub timestamp: NaiveDateTime, - pub author: String, - pub email: String, - pub title: String, - pub content: String, - } - - impl Post { - pub fn get_all(connection: &SqliteConnection) -> QueryResult> { - posts::table.load::(connection) - } - } - #+END_SRC - -***** Add index template in ~src/main.rs~ - #+BEGIN_SRC rust - ... - use serde::Serialize; - - ... - - use models::{NewPost, Post}; - - ... - - #[derive(Serialize)] - struct Posts { - posts: Vec, - } - - #[get("/")] - fn index(conn: DbCon) -> Template { - Template::render( - "index", - Posts { - posts: Post::get_all(&conn).unwrap(), - }, - ) - } - #+END_SRC - -**** Create page for posts - -***** Create template ~templates/post.html.tera~ - -**** Create possibility to reply - * Conclusion -** In general - - Rust's community and ecosystem is growing steadily - - Rust covers a broad field from low level code to high level - - Rocket and libraries alike improve creating security and - performance focused web application development - - Diesel is on a good way to become a serious ORM - - Alltogether Rust is ready to be used for web apps - ** What did we do? - Setup Rust and Diesel - Create a project with Rocket and Diesel prerequisites - - Store a small model with Diesel in a SQLite DB - - Create and use Tera templates + - Handle a SQLite DB with Diesel + - Create Tera templates -** Where to continue - - Move DB setup into the project - - Add features (List posts by Author, paging, etc..) - - (Private) Cookies +** What did you hopefully learn! ** Two small projects @@ -924,18 +553,17 @@ :BEAMER_env: block :END: - LDAP password reset tool - - ($\sim$ 300 SloC without tests) + - ($\sim$ 300 SloC) - First release - Tests -*** [[https://git.onders.org/finga/webhookey][webhookey]] +*** [[https://git.onders.org/finga/filerly][filerly]] :PROPERTIES: :BEAMER_col: 0.45 :BEAMER_env: block :END: - - Execute scripts triggered by http(s) requests - - Is becoming more and more robust - - ($\sim$ 400 SloC without tests) + - File sharing a la NextCloud + - Not yet ready, work in progress... ** The End #+BEGIN_CENTER diff --git a/simple_text_board/Cargo.lock b/simple_text_board/Cargo.lock index 77d9e9e..57dfce3 100644 --- a/simple_text_board/Cargo.lock +++ b/simple_text_board/Cargo.lock @@ -195,7 +195,6 @@ dependencies = [ "libc", "num-integer", "num-traits", - "serde", "time", "winapi 0.3.9", ] @@ -1125,20 +1124,6 @@ name = "serde" version = "1.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd" -dependencies = [ - "proc-macro2 1.0.24", - "quote 1.0.8", - "syn 1.0.58", -] [[package]] name = "serde_json" @@ -1184,7 +1169,6 @@ dependencies = [ "log 0.4.13", "rocket", "rocket_contrib", - "serde", ] [[package]] diff --git a/simple_text_board/Cargo.toml b/simple_text_board/Cargo.toml index 05a9df5..462cfb5 100644 --- a/simple_text_board/Cargo.toml +++ b/simple_text_board/Cargo.toml @@ -6,10 +6,9 @@ edition = "2018" [dependencies] rocket = "0.4" -chrono = { version = "0.4", features = ["serde"] } +chrono = "0.4" diesel = { version = "1.4", features = ["sqlite", "chrono"] } log = "0.4" -serde = { version = "1.0", features = ["derive"] } [dependencies.rocket_contrib] version = "0.4" diff --git a/simple_text_board/src/main.rs b/simple_text_board/src/main.rs index dcbf5d4..75a4094 100644 --- a/simple_text_board/src/main.rs +++ b/simple_text_board/src/main.rs @@ -15,12 +15,11 @@ use rocket::{ routes, uri, FromForm, }; use rocket_contrib::{database, templates::Template}; -use serde::Serialize; mod models; mod schema; -use models::{NewPost, Post}; +use models::NewPost; #[database("sqlite")] struct DbCon(SqliteConnection); @@ -33,21 +32,6 @@ struct PostForm { content: String, } -#[derive(Serialize)] -struct Posts { - posts: Vec, -} - -#[get("/")] -fn index(conn: DbCon) -> Template { - Template::render( - "index", - Posts { - posts: Post::get_all(&conn).unwrap(), - }, - ) -} - #[get("/create")] fn create_form(flash: Option) -> Template { let mut context = HashMap::new(); @@ -89,6 +73,11 @@ fn create_post( } } +#[get("/")] +fn index() -> &'static str { + "Hello, world!" +} + fn main() { rocket::ignite() .mount("/", routes![index, create_form, create_post]) diff --git a/simple_text_board/src/models.rs b/simple_text_board/src/models.rs index dc8078b..96edd43 100644 --- a/simple_text_board/src/models.rs +++ b/simple_text_board/src/models.rs @@ -1,7 +1,6 @@ use crate::schema::posts::{self, dsl::posts as table_posts}; use chrono::NaiveDateTime; -use diesel::{QueryResult, RunQueryDsl, SqliteConnection}; -use serde::Serialize; +use diesel::{QueryResult, RunQueryDsl}; #[derive(Insertable)] #[table_name = "posts"] @@ -19,20 +18,3 @@ impl NewPost<'_> { diesel::insert_into(table_posts).values(self).execute(conn) } } - -#[derive(Debug, Queryable, Serialize)] -pub struct Post { - pub id: i32, - pub parent: Option, - pub timestamp: NaiveDateTime, - pub author: String, - pub email: String, - pub title: String, - pub content: String, -} - -impl Post { - pub fn get_all(connection: &SqliteConnection) -> QueryResult> { - posts::table.load::(connection) - } -} diff --git a/simple_text_board/templates/index.html.tera b/simple_text_board/templates/index.html.tera deleted file mode 100644 index 5ab3970..0000000 --- a/simple_text_board/templates/index.html.tera +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "base" %} - -{% block content %} -

simple text board

-

index

-
    - {% for post in posts %} -
  • {{ loop.index }} - {{ post.title }} - {{ post.author }}
  • - {% endfor %} -
-{% endblock content %}