Compare commits
No commits in common. "13aacc3b6babc35452459fdff4e941044949bd7e" and "4948195495909efb5091989036e23f7be05d70fb" have entirely different histories.
13aacc3b6b
...
4948195495
6 changed files with 71 additions and 500 deletions
484
rust_web.org
484
rust_web.org
|
@ -1,16 +1,14 @@
|
||||||
#+STARTUP: beamer
|
|
||||||
#+OPTIONS: ':nil *:t -:t ::t <:t H:3 \n:nil ^:t arch:headline
|
#+OPTIONS: ':nil *:t -:t ::t <:t H:3 \n:nil ^:t arch:headline
|
||||||
#+OPTIONS: author:t broken-links:nil c:nil creator:nil
|
#+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: 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: 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: timestamp:t title:t toc:t todo:t |:t
|
||||||
#+OPTIONS: ^:nil
|
|
||||||
#+TITLE: Rust, Rocket and Diesel
|
#+TITLE: Rust, Rocket and Diesel
|
||||||
#+SUBTITLE: Web apps with Rust
|
#+SUBTITLE: Web apps with rust
|
||||||
#+DESCRIPTION: An introduction to Rust, Rocket and Diesel.
|
#+DESCRIPTION: An introduction to Rust, Rocket and Diesel.
|
||||||
#+KEYWORDS: rust rocket diesel
|
#+KEYWORDS: rust rocket diesel
|
||||||
#+AUTHOR: \href{mailto:finga@onders.org}{finga}
|
#+AUTHOR: \href{mailto:rocket-presentation@onders.org}{finga}
|
||||||
#+EMAIL: finga@onders.org
|
#+EMAIL: rocket-presentation@onders.org
|
||||||
#+LANGUAGE: en
|
#+LANGUAGE: en
|
||||||
#+SELECT_TAGS: export
|
#+SELECT_TAGS: export
|
||||||
#+EXCLUDE_TAGS: noexport
|
#+EXCLUDE_TAGS: noexport
|
||||||
|
@ -18,10 +16,9 @@
|
||||||
|
|
||||||
#+OPTIONS: H:2
|
#+OPTIONS: H:2
|
||||||
#+LATEX_CLASS: beamer
|
#+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=gray}
|
||||||
#+LATEX_HEADER: \hypersetup{colorlinks=true,linkcolor=black,urlcolor=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)
|
#+COLUMNS: %45ITEM %10BEAMER_env(Env) %10BEAMER_act(Act) %4BEAMER_col(Col) %8BEAMER_opt(Opt)
|
||||||
#+BEAMER_THEME: Frankfurt
|
#+BEAMER_THEME: Frankfurt
|
||||||
#+BEAMER_COLOR_THEME: seagull
|
#+BEAMER_COLOR_THEME: seagull
|
||||||
|
@ -40,11 +37,48 @@
|
||||||
*** Rust, the language
|
*** Rust, the language
|
||||||
- About 10 years young (2010)
|
- About 10 years young (2010)
|
||||||
- Memory safe without gcing, optional ref counting
|
- Memory safe without gcing, optional ref counting
|
||||||
- Ownership, lifetimes, traits, functional paradigms
|
- Ownership, lifetimes, traits
|
||||||
- Variables are immutable by default and can be shadowed
|
- Variables are immutable by default and can be shadowed
|
||||||
- Performance of idiomatic Rust is comparable to the performance
|
- Performance of idiomatic Rust is comparable to the performance
|
||||||
of idiomatic cpp
|
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
|
** How to install
|
||||||
|
|
||||||
*** Install Rust
|
*** Install Rust
|
||||||
|
@ -58,209 +92,16 @@
|
||||||
$ rustup toolchain install nightly
|
$ rustup toolchain install nightly
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
*** Install rustfmt (linter and code formatter)
|
*** Install rustfmt (code formatter)
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
$ rustup component add rustfmt
|
$ rustup component add rustfmt
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
*** Install clippy (static code analysis)
|
*** Install clippy (linter and static code analysis)
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_SRC sh
|
||||||
$ rustup component add clippy
|
$ rustup component add clippy
|
||||||
#+END_SRC
|
#+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
|
* Rocket
|
||||||
|
|
||||||
** What is Rocket?
|
** What is Rocket?
|
||||||
|
@ -279,7 +120,7 @@
|
||||||
- Type Safe
|
- Type Safe
|
||||||
- Extensible
|
- Extensible
|
||||||
- Templating
|
- Templating
|
||||||
- (Secure) Cookies
|
- Cookies
|
||||||
|
|
||||||
**** Col right
|
**** Col right
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
|
@ -288,112 +129,6 @@
|
||||||
- Streams
|
- Streams
|
||||||
- Testing (Unit tests)
|
- Testing (Unit tests)
|
||||||
- Typed URIs
|
- 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?
|
** Where to get further information?
|
||||||
|
|
||||||
|
@ -417,7 +152,7 @@
|
||||||
|
|
||||||
* Diesel
|
* Diesel
|
||||||
|
|
||||||
** ORM
|
** What is an ORM?
|
||||||
Object-relational-mapping is a programming technique for converting
|
Object-relational-mapping is a programming technique for converting
|
||||||
data between incompatible type systems using the paradigms of your
|
data between incompatible type systems using the paradigms of your
|
||||||
programming language.
|
programming language.
|
||||||
|
@ -484,21 +219,11 @@
|
||||||
comment: "A comment",
|
comment: "A comment",
|
||||||
};
|
};
|
||||||
|
|
||||||
...
|
|
||||||
|
|
||||||
insert_into(comments)
|
insert_into(comments)
|
||||||
.values(&user)
|
.values(&user)
|
||||||
.execute(conn);
|
.execute(conn);
|
||||||
#+END_SRC
|
#+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?
|
** Where to get further information?
|
||||||
|
|
||||||
*** Docs...
|
*** Docs...
|
||||||
|
@ -522,7 +247,7 @@
|
||||||
* Lets code something
|
* Lets code something
|
||||||
|
|
||||||
** Lets create a small web app
|
** Lets create a small web app
|
||||||
A chat box where everybody can post.
|
A mini text board where everybody can post and reply.
|
||||||
|
|
||||||
*** Caution
|
*** Caution
|
||||||
HTML is not disabled!
|
HTML is not disabled!
|
||||||
|
@ -561,11 +286,9 @@
|
||||||
|
|
||||||
***** Modify ~src/main.rs~ for Rockets "~Hello, world!~" program
|
***** Modify ~src/main.rs~ for Rockets "~Hello, world!~" program
|
||||||
#+BEGIN_SRC rust
|
#+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("/")]
|
#[get("/")]
|
||||||
fn index() -> &'static str {
|
fn index() -> &'static str {
|
||||||
|
@ -812,109 +535,15 @@
|
||||||
}
|
}
|
||||||
#+END_SRC
|
#+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
|
* 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?
|
** What did we do?
|
||||||
- Setup Rust and Diesel
|
- Setup Rust and Diesel
|
||||||
- Create a project with Rocket and Diesel prerequisites
|
- Create a project with Rocket and Diesel prerequisites
|
||||||
- Store a small model with Diesel in a SQLite DB
|
- Handle a SQLite DB with Diesel
|
||||||
- Create and use Tera templates
|
- Create Tera templates
|
||||||
|
|
||||||
** Where to continue
|
** What did you hopefully learn!
|
||||||
- Move DB setup into the project
|
|
||||||
- Add features (List posts by Author, paging, etc..)
|
|
||||||
- (Private) Cookies
|
|
||||||
|
|
||||||
** Two small projects
|
** Two small projects
|
||||||
|
|
||||||
|
@ -924,18 +553,17 @@
|
||||||
:BEAMER_env: block
|
:BEAMER_env: block
|
||||||
:END:
|
:END:
|
||||||
- LDAP password reset tool
|
- LDAP password reset tool
|
||||||
- ($\sim$ 300 SloC without tests)
|
- ($\sim$ 300 SloC)
|
||||||
- First release
|
- First release
|
||||||
- Tests
|
- Tests
|
||||||
|
|
||||||
*** [[https://git.onders.org/finga/webhookey][webhookey]]
|
*** [[https://git.onders.org/finga/filerly][filerly]]
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:BEAMER_col: 0.45
|
:BEAMER_col: 0.45
|
||||||
:BEAMER_env: block
|
:BEAMER_env: block
|
||||||
:END:
|
:END:
|
||||||
- Execute scripts triggered by http(s) requests
|
- File sharing a la NextCloud
|
||||||
- Is becoming more and more robust
|
- Not yet ready, work in progress...
|
||||||
- ($\sim$ 400 SloC without tests)
|
|
||||||
|
|
||||||
** The End
|
** The End
|
||||||
#+BEGIN_CENTER
|
#+BEGIN_CENTER
|
||||||
|
|
16
simple_text_board/Cargo.lock
generated
16
simple_text_board/Cargo.lock
generated
|
@ -195,7 +195,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
|
||||||
"time",
|
"time",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
@ -1125,20 +1124,6 @@ name = "serde"
|
||||||
version = "1.0.119"
|
version = "1.0.119"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3"
|
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]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
|
@ -1184,7 +1169,6 @@ dependencies = [
|
||||||
"log 0.4.13",
|
"log 0.4.13",
|
||||||
"rocket",
|
"rocket",
|
||||||
"rocket_contrib",
|
"rocket_contrib",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -6,10 +6,9 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = "0.4"
|
rocket = "0.4"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = "0.4"
|
||||||
diesel = { version = "1.4", features = ["sqlite", "chrono"] }
|
diesel = { version = "1.4", features = ["sqlite", "chrono"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
|
|
||||||
[dependencies.rocket_contrib]
|
[dependencies.rocket_contrib]
|
||||||
version = "0.4"
|
version = "0.4"
|
||||||
|
|
|
@ -15,12 +15,11 @@ use rocket::{
|
||||||
routes, uri, FromForm,
|
routes, uri, FromForm,
|
||||||
};
|
};
|
||||||
use rocket_contrib::{database, templates::Template};
|
use rocket_contrib::{database, templates::Template};
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
mod models;
|
mod models;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
|
||||||
use models::{NewPost, Post};
|
use models::NewPost;
|
||||||
|
|
||||||
#[database("sqlite")]
|
#[database("sqlite")]
|
||||||
struct DbCon(SqliteConnection);
|
struct DbCon(SqliteConnection);
|
||||||
|
@ -33,21 +32,6 @@ struct PostForm {
|
||||||
content: String,
|
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")]
|
#[get("/create")]
|
||||||
fn create_form(flash: Option<FlashMessage>) -> Template {
|
fn create_form(flash: Option<FlashMessage>) -> Template {
|
||||||
let mut context = HashMap::new();
|
let mut context = HashMap::new();
|
||||||
|
@ -89,6 +73,11 @@ fn create_post(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> &'static str {
|
||||||
|
"Hello, world!"
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
rocket::ignite()
|
rocket::ignite()
|
||||||
.mount("/", routes![index, create_form, create_post])
|
.mount("/", routes![index, create_form, create_post])
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::schema::posts::{self, dsl::posts as table_posts};
|
use crate::schema::posts::{self, dsl::posts as table_posts};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use diesel::{QueryResult, RunQueryDsl, SqliteConnection};
|
use diesel::{QueryResult, RunQueryDsl};
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
#[table_name = "posts"]
|
#[table_name = "posts"]
|
||||||
|
@ -19,20 +18,3 @@ impl NewPost<'_> {
|
||||||
diesel::insert_into(table_posts).values(self).execute(conn)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
{% 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 %}
|
|
Loading…
Reference in a new issue