Compare commits

...

2 commits

Author SHA1 Message Date
13aacc3b6b Add content from last presentation 2021-04-14 17:12:12 +02:00
099bcaadc6 List posts 2021-04-14 17:07:10 +02:00
6 changed files with 500 additions and 71 deletions

View file

@ -1,14 +1,16 @@
#+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:rocket-presentation@onders.org}{finga}
#+EMAIL: rocket-presentation@onders.org
#+AUTHOR: \href{mailto:finga@onders.org}{finga}
#+EMAIL: finga@onders.org
#+LANGUAGE: en
#+SELECT_TAGS: export
#+EXCLUDE_TAGS: noexport
@ -16,9 +18,10 @@
#+OPTIONS: H:2
#+LATEX_CLASS: beamer
#+LATEX_CLASS_OPTIONS: [aspectration=1610]
#+LATEX_CLASS_OPTIONS: [aspectratio=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
@ -37,48 +40,11 @@
*** Rust, the language
- About 10 years young (2010)
- Memory safe without gcing, optional ref counting
- Ownership, lifetimes, traits
- Ownership, lifetimes, traits, functional paradigms
- 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
@ -92,16 +58,209 @@
$ rustup toolchain install nightly
#+END_SRC
*** Install rustfmt (code formatter)
*** Install rustfmt (linter and code formatter)
#+BEGIN_SRC sh
$ rustup component add rustfmt
#+END_SRC
*** Install clippy (linter and static code analysis)
*** Install clippy (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 <finga@onders.org>"]
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<T>~ and ~Result<T, E>~
*** Option
#+BEGIN_SRC rust
pub enum Option<T> {
None,
Some(T),
}
#+END_SRC
*** Result
#+BEGIN_SRC rust
pub enum Result<T, E> {
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<Data> {
...
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?
@ -120,7 +279,7 @@
- Type Safe
- Extensible
- Templating
- Cookies
- (Secure) Cookies
**** Col right
:PROPERTIES:
@ -129,6 +288,112 @@
- 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/<name>/<age>")]
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 = "<email>")]
fn reset_email(email: Form<EmailForm>) -> Flash<Redirect> {
...
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<FlashMessage>) -> 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
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<p> Hello,
{{ name }}!</p>
</body>
</html>
#+END_SRC
*** ~src/main.rs~
:PROPERTIES:
:BEAMER_col: 0.60
:BEAMER_env: block
:END:
#+BEGIN_SRC rust
#[get("/hello/<name>")]
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?
@ -152,7 +417,7 @@
* Diesel
** What is an ORM?
** ORM
Object-relational-mapping is a programming technique for converting
data between incompatible type systems using the paradigms of your
programming language.
@ -219,11 +484,21 @@
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...
@ -247,7 +522,7 @@
* Lets code something
** Lets create a small web app
A mini text board where everybody can post and reply.
A chat box where everybody can post.
*** Caution
HTML is not disabled!
@ -286,9 +561,11 @@
***** Modify ~src/main.rs~ for Rockets "~Hello, world!~" program
#+BEGIN_SRC rust
#![feature(proc_macro_hygiene, decl_macro)]
// #![feature(proc_macro_hygiene, decl_macro)]
#![feature(decl_macro)]
#[macro_use] extern crate rocket;
// #[macro_use] extern crate rocket;
// #[macro_use] extern crate diesel;
#[get("/")]
fn index() -> &'static str {
@ -535,15 +812,109 @@
}
#+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 %}
<h1>simple text board</h1>
<h2>index</h2>
<ul>
{% for post in posts %}
<li>{{ loop.index }} - <a href="post/{{ post.id }}">{{ post.title }}</a> - {{ post.author }}</li>
{% endfor %}
</ul>
{% 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<i32>,
pub timestamp: NaiveDateTime,
pub author: String,
pub email: String,
pub title: String,
pub content: String,
}
impl Post {
pub fn get_all(connection: &SqliteConnection) -> QueryResult<Vec<Post>> {
posts::table.load::<Post>(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<Post>,
}
#[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
- Handle a SQLite DB with Diesel
- Create Tera templates
- Store a small model with Diesel in a SQLite DB
- Create and use Tera templates
** What did you hopefully learn!
** Where to continue
- Move DB setup into the project
- Add features (List posts by Author, paging, etc..)
- (Private) Cookies
** Two small projects
@ -553,17 +924,18 @@
:BEAMER_env: block
:END:
- LDAP password reset tool
- ($\sim$ 300 SloC)
- ($\sim$ 300 SloC without tests)
- First release
- Tests
*** [[https://git.onders.org/finga/filerly][filerly]]
*** [[https://git.onders.org/finga/webhookey][webhookey]]
:PROPERTIES:
:BEAMER_col: 0.45
:BEAMER_env: block
:END:
- File sharing a la NextCloud
- Not yet ready, work in progress...
- Execute scripts triggered by http(s) requests
- Is becoming more and more robust
- ($\sim$ 400 SloC without tests)
** The End
#+BEGIN_CENTER

View file

@ -195,6 +195,7 @@ dependencies = [
"libc",
"num-integer",
"num-traits",
"serde",
"time",
"winapi 0.3.9",
]
@ -1124,6 +1125,20 @@ 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"
@ -1169,6 +1184,7 @@ dependencies = [
"log 0.4.13",
"rocket",
"rocket_contrib",
"serde",
]
[[package]]

View file

@ -6,9 +6,10 @@ edition = "2018"
[dependencies]
rocket = "0.4"
chrono = "0.4"
chrono = { version = "0.4", features = ["serde"] }
diesel = { version = "1.4", features = ["sqlite", "chrono"] }
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
[dependencies.rocket_contrib]
version = "0.4"

View file

@ -15,11 +15,12 @@ use rocket::{
routes, uri, FromForm,
};
use rocket_contrib::{database, templates::Template};
use serde::Serialize;
mod models;
mod schema;
use models::NewPost;
use models::{NewPost, Post};
#[database("sqlite")]
struct DbCon(SqliteConnection);
@ -32,6 +33,21 @@ struct PostForm {
content: String,
}
#[derive(Serialize)]
struct Posts {
posts: Vec<Post>,
}
#[get("/")]
fn index(conn: DbCon) -> Template {
Template::render(
"index",
Posts {
posts: Post::get_all(&conn).unwrap(),
},
)
}
#[get("/create")]
fn create_form(flash: Option<FlashMessage>) -> Template {
let mut context = HashMap::new();
@ -73,11 +89,6 @@ fn create_post(
}
}
#[get("/")]
fn index() -> &'static str {
"Hello, world!"
}
fn main() {
rocket::ignite()
.mount("/", routes![index, create_form, create_post])

View file

@ -1,6 +1,7 @@
use crate::schema::posts::{self, dsl::posts as table_posts};
use chrono::NaiveDateTime;
use diesel::{QueryResult, RunQueryDsl};
use diesel::{QueryResult, RunQueryDsl, SqliteConnection};
use serde::Serialize;
#[derive(Insertable)]
#[table_name = "posts"]
@ -18,3 +19,20 @@ impl NewPost<'_> {
diesel::insert_into(table_posts).values(self).execute(conn)
}
}
#[derive(Debug, Queryable, Serialize)]
pub struct Post {
pub id: i32,
pub parent: Option<i32>,
pub timestamp: NaiveDateTime,
pub author: String,
pub email: String,
pub title: String,
pub content: String,
}
impl Post {
pub fn get_all(connection: &SqliteConnection) -> QueryResult<Vec<Post>> {
posts::table.load::<Post>(connection)
}
}

View file

@ -0,0 +1,11 @@
{% extends "base" %}
{% block content %}
<h1>simple text board</h1>
<h2>index</h2>
<ul>
{% for post in posts %}
<li>{{ loop.index }} - <a href="post/{{ post.id }}">{{ post.title }}</a> - {{ post.author }}</li>
{% endfor %}
</ul>
{% endblock content %}