├── 2024 └── slides │ ├── assets │ ├── is-logo.png │ ├── aws-logo.png │ ├── discord-logo.png │ ├── dropbox-logo.png │ ├── google-logo.png │ ├── huawei-logo.png │ ├── mozilla-logo.png │ ├── rust-logo-1.png │ ├── twitch-logo.png │ ├── 03-images │ │ └── ????.png │ ├── 12-images │ │ ├── tabs.png │ │ ├── glade.png │ │ ├── popover.png │ │ ├── window.png │ │ ├── side-bar.png │ │ ├── header-bar.png │ │ └── message-dialog.png │ ├── facebook-logo.png │ ├── microsoft-logo.png │ ├── 05-images │ │ ├── doc-1.png │ │ └── doc-2.png │ ├── 01-images │ │ ├── compiling.png │ │ ├── rust-c-1.png │ │ ├── rust-c-2.png │ │ ├── 01-microsoft-cves.png │ │ ├── techempowered-fortunes.png │ │ ├── techempowered-fortunes-2.png │ │ ├── techempowered-fortunes-top-2022.png │ │ ├── techempowered-fortunes-spring-2022.png │ │ ├── rustacean-flat-noshadow.svg │ │ ├── computerlanguagebenchmarkgame2023.svg │ │ └── computerlanguagebenchmarkgame.svg │ ├── 08-images │ │ └── plantuml.png │ ├── 09-images │ │ ├── 5-complex.png │ │ ├── 2-basic-db.png │ │ ├── 7-complex-ha.png │ │ ├── 3-basic-cache.png │ │ ├── 9-message-bus.png │ │ ├── 8-microservices.png │ │ ├── 1-basic-architecture.png │ │ ├── 4-multiple-webservers.png │ │ └── 6-complex-load-balancer.png │ ├── 04-images │ │ ├── hash-table.png │ │ ├── circular-buffer.png │ │ └── circular-buffer.svg │ ├── 11-images │ │ ├── grpc-vs-rest.png │ │ ├── grpc-fw-benchmark.png │ │ └── grpc-implementation.png │ ├── 02-images │ │ ├── memory_layout.png │ │ └── program-stack.png │ ├── 07-images │ │ ├── network_stack.png │ │ ├── work_stealing.png │ │ └── message_passing.png │ ├── 10-images │ │ └── wasm-benchmark.png │ └── 06-images │ │ ├── 4_01_ThreadDiagram.jpeg │ │ ├── many-to-many-model.png │ │ ├── windows_priorities.jpeg │ │ ├── windows_thread_states.jpeg │ │ ├── windows_irql_vs_threadp.jpeg │ │ ├── widnows_thread_switching_1.jpeg │ │ ├── widnows_thread_switching_2.jpeg │ │ └── windows_dispatcher_database.jpeg │ ├── 00-starting-soon.md │ ├── marp.config.js │ ├── split-template.md │ ├── package.json │ ├── themes │ └── rust.css │ ├── 14-bonus.md │ └── 13-graphql.md ├── examples └── 10-wasm │ ├── demo-router-fetching │ ├── src │ │ ├── views │ │ │ ├── mod.rs │ │ │ ├── home.rs │ │ │ └── server.rs │ │ ├── components │ │ │ ├── mod.rs │ │ │ ├── component-template.rs │ │ │ ├── container.rs │ │ │ ├── like_button.rs │ │ │ ├── header.rs │ │ │ └── card.rs │ │ └── main.rs │ ├── Cargo.toml │ ├── index.html │ └── assets │ │ └── styles.css │ ├── demo-template │ ├── src │ │ ├── components │ │ │ ├── mod.rs │ │ │ ├── component-template.rs │ │ │ ├── header.rs │ │ │ ├── container.rs │ │ │ ├── like_button.rs │ │ │ └── card.rs │ │ └── main.rs │ ├── readme.md │ ├── Cargo.toml │ ├── index.html │ └── assets │ │ └── styles.css │ ├── demo-reference │ ├── src │ │ ├── components │ │ │ ├── mod.rs │ │ │ ├── component-template.rs │ │ │ ├── container.rs │ │ │ ├── like_button.rs │ │ │ ├── header.rs │ │ │ └── card.rs │ │ └── main.rs │ ├── readme.md │ ├── Cargo.toml │ ├── index.html │ └── assets │ │ └── styles.css │ └── backend │ ├── package.json │ └── db.json ├── slides ├── assets │ ├── is-logo.png │ ├── aws-logo.png │ ├── discord-logo.png │ ├── dropbox-logo.png │ ├── google-logo.png │ ├── huawei-logo.png │ ├── mozilla-logo.png │ ├── rust-logo-1.png │ ├── twitch-logo.png │ ├── 03-images │ │ └── ????.png │ ├── 12-images │ │ ├── tabs.png │ │ ├── glade.png │ │ ├── popover.png │ │ ├── window.png │ │ ├── side-bar.png │ │ ├── header-bar.png │ │ └── message-dialog.png │ ├── facebook-logo.png │ ├── microsoft-logo.png │ ├── 05-images │ │ ├── doc-1.png │ │ └── doc-2.png │ ├── 01-images │ │ ├── compiling.png │ │ ├── rust-c-1.png │ │ ├── rust-c-2.png │ │ ├── 01-microsoft-cves.png │ │ ├── techempowered-fortunes.png │ │ ├── techempowered-fortunes-2.png │ │ ├── techempowered-fortunes-top-2022.png │ │ ├── techempowered-fortunes-spring-2022.png │ │ ├── rustacean-flat-noshadow.svg │ │ ├── computerlanguagebenchmarkgame2023.svg │ │ └── computerlanguagebenchmarkgame.svg │ ├── 08-images │ │ └── plantuml.png │ ├── 09-images │ │ ├── 5-complex.png │ │ ├── 2-basic-db.png │ │ ├── 7-complex-ha.png │ │ ├── 3-basic-cache.png │ │ ├── 9-message-bus.png │ │ ├── 8-microservices.png │ │ ├── 1-basic-architecture.png │ │ ├── 4-multiple-webservers.png │ │ └── 6-complex-load-balancer.png │ ├── 04-images │ │ ├── hash-table.png │ │ ├── circular-buffer.png │ │ └── circular-buffer.svg │ ├── 11-images │ │ ├── grpc-vs-rest.png │ │ ├── grpc-fw-benchmark.png │ │ └── grpc-implementation.png │ ├── 02-images │ │ ├── memory_layout.png │ │ └── program-stack.png │ ├── 07-images │ │ ├── network_stack.png │ │ ├── work_stealing.png │ │ └── message_passing.png │ ├── 10-images │ │ └── wasm-benchmark.png │ └── 06-images │ │ ├── 4_01_ThreadDiagram.jpeg │ │ ├── many-to-many-model.png │ │ ├── windows_priorities.jpeg │ │ ├── windows_thread_states.jpeg │ │ ├── windows_irql_vs_threadp.jpeg │ │ ├── widnows_thread_switching_1.jpeg │ │ ├── widnows_thread_switching_2.jpeg │ │ └── windows_dispatcher_database.jpeg ├── 00-starting-soon.md ├── marp.config.js ├── split-template.md ├── package.json ├── themes │ └── rust.css ├── 14-bonus.md ├── 13-graphql.md └── 10-2023-actix-htmx.md ├── .gitignore └── .github └── workflows └── marp.yml /examples/10-wasm/demo-router-fetching/src/views/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod home; 2 | pub mod server; 3 | -------------------------------------------------------------------------------- /slides/assets/is-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/is-logo.png -------------------------------------------------------------------------------- /slides/assets/aws-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/aws-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/is-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/is-logo.png -------------------------------------------------------------------------------- /slides/assets/discord-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/discord-logo.png -------------------------------------------------------------------------------- /slides/assets/dropbox-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/dropbox-logo.png -------------------------------------------------------------------------------- /slides/assets/google-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/google-logo.png -------------------------------------------------------------------------------- /slides/assets/huawei-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/huawei-logo.png -------------------------------------------------------------------------------- /slides/assets/mozilla-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/mozilla-logo.png -------------------------------------------------------------------------------- /slides/assets/rust-logo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/rust-logo-1.png -------------------------------------------------------------------------------- /slides/assets/twitch-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/twitch-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/aws-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/aws-logo.png -------------------------------------------------------------------------------- /slides/assets/03-images/????.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/03-images/????.png -------------------------------------------------------------------------------- /slides/assets/12-images/tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/12-images/tabs.png -------------------------------------------------------------------------------- /slides/assets/facebook-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/facebook-logo.png -------------------------------------------------------------------------------- /slides/assets/microsoft-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/microsoft-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/discord-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/discord-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/dropbox-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/dropbox-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/google-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/google-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/huawei-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/huawei-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/mozilla-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/mozilla-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/rust-logo-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/rust-logo-1.png -------------------------------------------------------------------------------- /2024/slides/assets/twitch-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/twitch-logo.png -------------------------------------------------------------------------------- /slides/assets/05-images/doc-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/05-images/doc-1.png -------------------------------------------------------------------------------- /slides/assets/05-images/doc-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/05-images/doc-2.png -------------------------------------------------------------------------------- /slides/assets/12-images/glade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/12-images/glade.png -------------------------------------------------------------------------------- /slides/assets/12-images/popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/12-images/popover.png -------------------------------------------------------------------------------- /slides/assets/12-images/window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/12-images/window.png -------------------------------------------------------------------------------- /2024/slides/assets/03-images/????.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/03-images/????.png -------------------------------------------------------------------------------- /2024/slides/assets/12-images/tabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/12-images/tabs.png -------------------------------------------------------------------------------- /2024/slides/assets/facebook-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/facebook-logo.png -------------------------------------------------------------------------------- /2024/slides/assets/microsoft-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/microsoft-logo.png -------------------------------------------------------------------------------- /slides/assets/01-images/compiling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/01-images/compiling.png -------------------------------------------------------------------------------- /slides/assets/01-images/rust-c-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/01-images/rust-c-1.png -------------------------------------------------------------------------------- /slides/assets/01-images/rust-c-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/01-images/rust-c-2.png -------------------------------------------------------------------------------- /slides/assets/08-images/plantuml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/08-images/plantuml.png -------------------------------------------------------------------------------- /slides/assets/09-images/5-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/5-complex.png -------------------------------------------------------------------------------- /slides/assets/12-images/side-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/12-images/side-bar.png -------------------------------------------------------------------------------- /2024/slides/assets/05-images/doc-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/05-images/doc-1.png -------------------------------------------------------------------------------- /2024/slides/assets/05-images/doc-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/05-images/doc-2.png -------------------------------------------------------------------------------- /2024/slides/assets/12-images/glade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/12-images/glade.png -------------------------------------------------------------------------------- /2024/slides/assets/12-images/popover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/12-images/popover.png -------------------------------------------------------------------------------- /2024/slides/assets/12-images/window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/12-images/window.png -------------------------------------------------------------------------------- /slides/assets/04-images/hash-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/04-images/hash-table.png -------------------------------------------------------------------------------- /slides/assets/09-images/2-basic-db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/2-basic-db.png -------------------------------------------------------------------------------- /slides/assets/09-images/7-complex-ha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/7-complex-ha.png -------------------------------------------------------------------------------- /slides/assets/11-images/grpc-vs-rest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/11-images/grpc-vs-rest.png -------------------------------------------------------------------------------- /slides/assets/12-images/header-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/12-images/header-bar.png -------------------------------------------------------------------------------- /2024/slides/assets/01-images/compiling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/01-images/compiling.png -------------------------------------------------------------------------------- /2024/slides/assets/01-images/rust-c-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/01-images/rust-c-1.png -------------------------------------------------------------------------------- /2024/slides/assets/01-images/rust-c-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/01-images/rust-c-2.png -------------------------------------------------------------------------------- /2024/slides/assets/08-images/plantuml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/08-images/plantuml.png -------------------------------------------------------------------------------- /2024/slides/assets/09-images/5-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/5-complex.png -------------------------------------------------------------------------------- /2024/slides/assets/12-images/side-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/12-images/side-bar.png -------------------------------------------------------------------------------- /slides/assets/02-images/memory_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/02-images/memory_layout.png -------------------------------------------------------------------------------- /slides/assets/02-images/program-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/02-images/program-stack.png -------------------------------------------------------------------------------- /slides/assets/07-images/network_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/07-images/network_stack.png -------------------------------------------------------------------------------- /slides/assets/07-images/work_stealing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/07-images/work_stealing.png -------------------------------------------------------------------------------- /slides/assets/09-images/3-basic-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/3-basic-cache.png -------------------------------------------------------------------------------- /slides/assets/09-images/9-message-bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/9-message-bus.png -------------------------------------------------------------------------------- /slides/assets/10-images/wasm-benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/10-images/wasm-benchmark.png -------------------------------------------------------------------------------- /slides/assets/12-images/message-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/12-images/message-dialog.png -------------------------------------------------------------------------------- /2024/slides/assets/04-images/hash-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/04-images/hash-table.png -------------------------------------------------------------------------------- /2024/slides/assets/09-images/2-basic-db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/2-basic-db.png -------------------------------------------------------------------------------- /2024/slides/assets/09-images/7-complex-ha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/7-complex-ha.png -------------------------------------------------------------------------------- /2024/slides/assets/11-images/grpc-vs-rest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/11-images/grpc-vs-rest.png -------------------------------------------------------------------------------- /2024/slides/assets/12-images/header-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/12-images/header-bar.png -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod card; 2 | pub mod container; 3 | pub mod header; 4 | pub mod like_button; 5 | -------------------------------------------------------------------------------- /slides/assets/01-images/01-microsoft-cves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/01-images/01-microsoft-cves.png -------------------------------------------------------------------------------- /slides/assets/04-images/circular-buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/04-images/circular-buffer.png -------------------------------------------------------------------------------- /slides/assets/04-images/circular-buffer.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/04-images/circular-buffer.svg -------------------------------------------------------------------------------- /slides/assets/07-images/message_passing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/07-images/message_passing.png -------------------------------------------------------------------------------- /slides/assets/09-images/8-microservices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/8-microservices.png -------------------------------------------------------------------------------- /slides/assets/11-images/grpc-fw-benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/11-images/grpc-fw-benchmark.png -------------------------------------------------------------------------------- /2024/slides/assets/02-images/memory_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/02-images/memory_layout.png -------------------------------------------------------------------------------- /2024/slides/assets/02-images/program-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/02-images/program-stack.png -------------------------------------------------------------------------------- /2024/slides/assets/07-images/network_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/07-images/network_stack.png -------------------------------------------------------------------------------- /2024/slides/assets/07-images/work_stealing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/07-images/work_stealing.png -------------------------------------------------------------------------------- /2024/slides/assets/09-images/3-basic-cache.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/3-basic-cache.png -------------------------------------------------------------------------------- /2024/slides/assets/09-images/9-message-bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/9-message-bus.png -------------------------------------------------------------------------------- /2024/slides/assets/10-images/wasm-benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/10-images/wasm-benchmark.png -------------------------------------------------------------------------------- /2024/slides/assets/12-images/message-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/12-images/message-dialog.png -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod card; 2 | pub mod container; 3 | pub mod header; 4 | pub mod like_button; 5 | -------------------------------------------------------------------------------- /slides/assets/06-images/4_01_ThreadDiagram.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/06-images/4_01_ThreadDiagram.jpeg -------------------------------------------------------------------------------- /slides/assets/06-images/many-to-many-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/06-images/many-to-many-model.png -------------------------------------------------------------------------------- /slides/assets/06-images/windows_priorities.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/06-images/windows_priorities.jpeg -------------------------------------------------------------------------------- /slides/assets/11-images/grpc-implementation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/11-images/grpc-implementation.png -------------------------------------------------------------------------------- /2024/slides/assets/01-images/01-microsoft-cves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/01-images/01-microsoft-cves.png -------------------------------------------------------------------------------- /2024/slides/assets/04-images/circular-buffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/04-images/circular-buffer.png -------------------------------------------------------------------------------- /2024/slides/assets/04-images/circular-buffer.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/04-images/circular-buffer.svg -------------------------------------------------------------------------------- /2024/slides/assets/07-images/message_passing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/07-images/message_passing.png -------------------------------------------------------------------------------- /2024/slides/assets/09-images/8-microservices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/8-microservices.png -------------------------------------------------------------------------------- /2024/slides/assets/11-images/grpc-fw-benchmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/11-images/grpc-fw-benchmark.png -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod card; 2 | pub mod container; 3 | pub mod header; 4 | pub mod like_button; 5 | -------------------------------------------------------------------------------- /slides/assets/01-images/techempowered-fortunes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/01-images/techempowered-fortunes.png -------------------------------------------------------------------------------- /slides/assets/06-images/windows_thread_states.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/06-images/windows_thread_states.jpeg -------------------------------------------------------------------------------- /slides/assets/09-images/1-basic-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/1-basic-architecture.png -------------------------------------------------------------------------------- /slides/assets/09-images/4-multiple-webservers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/4-multiple-webservers.png -------------------------------------------------------------------------------- /2024/slides/assets/06-images/4_01_ThreadDiagram.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/06-images/4_01_ThreadDiagram.jpeg -------------------------------------------------------------------------------- /2024/slides/assets/06-images/many-to-many-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/06-images/many-to-many-model.png -------------------------------------------------------------------------------- /2024/slides/assets/06-images/windows_priorities.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/06-images/windows_priorities.jpeg -------------------------------------------------------------------------------- /2024/slides/assets/11-images/grpc-implementation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/11-images/grpc-implementation.png -------------------------------------------------------------------------------- /slides/assets/01-images/techempowered-fortunes-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/01-images/techempowered-fortunes-2.png -------------------------------------------------------------------------------- /slides/assets/06-images/windows_irql_vs_threadp.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/06-images/windows_irql_vs_threadp.jpeg -------------------------------------------------------------------------------- /slides/assets/09-images/6-complex-load-balancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/09-images/6-complex-load-balancer.png -------------------------------------------------------------------------------- /2024/slides/assets/01-images/techempowered-fortunes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/01-images/techempowered-fortunes.png -------------------------------------------------------------------------------- /2024/slides/assets/06-images/windows_thread_states.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/06-images/windows_thread_states.jpeg -------------------------------------------------------------------------------- /2024/slides/assets/09-images/1-basic-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/1-basic-architecture.png -------------------------------------------------------------------------------- /2024/slides/assets/09-images/4-multiple-webservers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/4-multiple-webservers.png -------------------------------------------------------------------------------- /slides/assets/06-images/widnows_thread_switching_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/06-images/widnows_thread_switching_1.jpeg -------------------------------------------------------------------------------- /slides/assets/06-images/widnows_thread_switching_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/06-images/widnows_thread_switching_2.jpeg -------------------------------------------------------------------------------- /2024/slides/assets/01-images/techempowered-fortunes-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/01-images/techempowered-fortunes-2.png -------------------------------------------------------------------------------- /2024/slides/assets/06-images/windows_irql_vs_threadp.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/06-images/windows_irql_vs_threadp.jpeg -------------------------------------------------------------------------------- /2024/slides/assets/09-images/6-complex-load-balancer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/09-images/6-complex-load-balancer.png -------------------------------------------------------------------------------- /slides/assets/06-images/windows_dispatcher_database.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/06-images/windows_dispatcher_database.jpeg -------------------------------------------------------------------------------- /2024/slides/assets/06-images/widnows_thread_switching_1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/06-images/widnows_thread_switching_1.jpeg -------------------------------------------------------------------------------- /2024/slides/assets/06-images/widnows_thread_switching_2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/06-images/widnows_thread_switching_2.jpeg -------------------------------------------------------------------------------- /slides/assets/01-images/techempowered-fortunes-top-2022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/01-images/techempowered-fortunes-top-2022.png -------------------------------------------------------------------------------- /2024/slides/assets/06-images/windows_dispatcher_database.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/06-images/windows_dispatcher_database.jpeg -------------------------------------------------------------------------------- /slides/assets/01-images/techempowered-fortunes-spring-2022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/slides/assets/01-images/techempowered-fortunes-spring-2022.png -------------------------------------------------------------------------------- /2024/slides/assets/01-images/techempowered-fortunes-top-2022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/01-images/techempowered-fortunes-top-2022.png -------------------------------------------------------------------------------- /2024/slides/assets/01-images/techempowered-fortunes-spring-2022.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grolig/pv281/HEAD/2024/slides/assets/01-images/techempowered-fortunes-spring-2022.png -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/readme.md: -------------------------------------------------------------------------------- 1 | # Demo app 2 | 3 | The web will have a: 4 | - Header 5 | - List of images, each image has: 6 | - a description 7 | - clickable button which "likes" the image 8 | 9 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/readme.md: -------------------------------------------------------------------------------- 1 | # Demo app 2 | 3 | The web will have a: 4 | - Header 5 | - List of images, each image has: 6 | - a description 7 | - clickable button which "likes" the image 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # IDE files 4 | .idea 5 | 6 | # MARP / Trunk target directory 7 | **/dist 8 | 9 | # Rust code target 10 | **/target 11 | # Cargo lock 12 | Cargo.lock 13 | 14 | **/example-reservation-system 15 | **/.DS_Store 16 | -------------------------------------------------------------------------------- /slides/00-starting-soon.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust Intro Session 5 | theme: rust 6 | paginate: true 7 | --- 8 | ![w:512 h:512](./assets/rust-logo-1.png) 9 | # PV281: Starting soon... 10 | -------------------------------------------------------------------------------- /2024/slides/00-starting-soon.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust Intro Session 5 | theme: rust 6 | paginate: true 7 | --- 8 | ![w:512 h:512](./assets/rust-logo-1.png) 9 | # PV281: Starting soon... 10 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo-reference" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] } -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo-template" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] } -------------------------------------------------------------------------------- /slides/marp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | allowLocalFiles: true, 3 | ogImage: process.env.URL && `${process.env.URL}/og-image.jpg`, 4 | themeSet: 'themes', 5 | //url: process.env.URL, 6 | engine: ({ marp }) => marp.use(require('markdown-it-plantuml')).use(require('markdown-it-named-code-blocks')), 7 | } -------------------------------------------------------------------------------- /2024/slides/marp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | allowLocalFiles: true, 3 | ogImage: process.env.URL && `${process.env.URL}/og-image.jpg`, 4 | themeSet: 'themes', 5 | //url: process.env.URL, 6 | engine: ({ marp }) => marp.use(require('markdown-it-plantuml')).use(require('markdown-it-named-code-blocks')), 7 | } -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/src/components/component-template.rs: -------------------------------------------------------------------------------- 1 | use yew::{function_component, html, Html, Properties}; 2 | 3 | #[derive(Properties, PartialEq)] 4 | pub struct ComponentProps {} 5 | 6 | #[function_component(Component)] 7 | pub fn component(props: &ComponentProps) -> Html { 8 | html! {} 9 | } 10 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/components/component-template.rs: -------------------------------------------------------------------------------- 1 | use yew::{function_component, html, Html, Properties}; 2 | 3 | #[derive(Properties, PartialEq)] 4 | pub struct ComponentProps {} 5 | 6 | #[function_component(Component)] 7 | pub fn component(props: &ComponentProps) -> Html { 8 | html! {} 9 | } 10 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/src/components/component-template.rs: -------------------------------------------------------------------------------- 1 | // use yew::{function_component, html, Html, Properties}; 2 | 3 | // #[derive(Properties, PartialEq)] 4 | // pub struct ComponentProps {} 5 | 6 | // #[function_component(Component)] 7 | // pub fn component(props: &ComponentProps) -> Html { 8 | // html! {} 9 | // } 10 | -------------------------------------------------------------------------------- /examples/10-wasm/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "json-server --watch db.json --port 5000" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "json-server": "^0.17.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/src/components/container.rs: -------------------------------------------------------------------------------- 1 | use yew::{function_component, html, Children, Html, Properties}; 2 | 3 | #[derive(Properties, PartialEq)] 4 | pub struct ContainerProps { 5 | pub class: String, 6 | pub children: Children, 7 | } 8 | 9 | #[function_component(Container)] 10 | pub fn container(props: &ContainerProps) -> Html { 11 | html! { 12 |
13 | { 14 | for props.children.iter() 15 | } 16 |
17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/components/container.rs: -------------------------------------------------------------------------------- 1 | use yew::{function_component, html, Children, Html, Properties}; 2 | 3 | #[derive(Properties, PartialEq)] 4 | pub struct ContainerProps { 5 | pub class: String, 6 | pub children: Children, 7 | } 8 | 9 | #[function_component(Container)] 10 | pub fn container(props: &ContainerProps) -> Html { 11 | html! { 12 |
13 | { 14 | for props.children.iter() 15 | } 16 |
17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo-router-fetching" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | yew = { git = "https://github.com/yewstack/yew/", features = ["csr"] } 10 | 11 | # for data fetching 12 | gloo-net = "0.2" 13 | serde = { version = "1.0", features = ["derive"] } 14 | wasm-bindgen-futures = "0.4" 15 | 16 | # for router 17 | yew-router = { git = "https://github.com/yewstack/yew.git" } 18 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/src/components/like_button.rs: -------------------------------------------------------------------------------- 1 | use yew::{function_component, html, use_state, Callback, Html}; 2 | 3 | #[function_component(LikeButton)] 4 | pub fn like_button() -> Html { 5 | let liked = use_state(|| false); 6 | let onclick = { 7 | let liked = liked.clone(); 8 | Callback::from(move |_| liked.set(!*liked)) 9 | }; 10 | 11 | html! { 12 |
{ 13 | if *liked { 14 | "Unlike" 15 | } else { 16 | "Like" 17 | } 18 | }
19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/views/home.rs: -------------------------------------------------------------------------------- 1 | use crate::{components::container::Container, Routes}; 2 | use yew::{classes, function_component, html, Callback, Html}; 3 | use yew_router::prelude::use_navigator; 4 | 5 | #[function_component(HomePage)] 6 | pub fn home_page() -> Html { 7 | let navigator = use_navigator().unwrap(); 8 | 9 | let onclick = Callback::from(move |_| navigator.push(&Routes::Fetched)); 10 | 11 | html! { 12 | 13 |

{"Go to fetched data!"}

14 |
15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/components/like_button.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, function_component, html, use_state, Callback, Html}; 2 | 3 | #[function_component(LikeButton)] 4 | pub fn like_button() -> Html { 5 | let liked = use_state(|| false); 6 | let onclick = { 7 | let liked = liked.clone(); 8 | Callback::from(move |_| liked.set(!*liked)) 9 | }; 10 | 11 | html! { 12 |
{ 13 | if *liked { 14 | "Unlike" 15 | } else { 16 | "Like" 17 | } 18 | }
19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/src/components/header.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, function_component, html, Html, Properties}; 2 | 3 | #[derive(Properties, PartialEq)] 4 | pub struct HeaderProps { 5 | pub image_url: String, 6 | pub title: String, 7 | } 8 | 9 | #[function_component(Header)] 10 | pub fn header(props: &HeaderProps) -> Html { 11 | html! { 12 |
13 |
14 | 15 |
16 |

17 | { props.title.clone() } 18 |

19 |
20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/src/components/header.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, function_component, html, Html, Properties}; 2 | 3 | #[derive(PartialEq, Properties)] 4 | pub struct HeaderProps { 5 | pub image_url: String, 6 | pub title: String, 7 | } 8 | 9 | #[function_component(Header)] 10 | pub fn header(props: &HeaderProps) -> Html { 11 | html! { 12 |
13 |
14 | 15 |
16 |

17 | { props.title.clone() } 18 |

19 |
20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/components/header.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, function_component, html, Html, Properties}; 2 | 3 | #[derive(Properties, PartialEq)] 4 | pub struct HeaderProps { 5 | pub image_url: String, 6 | pub title: String, 7 | } 8 | 9 | #[function_component(Header)] 10 | pub fn header(props: &HeaderProps) -> Html { 11 | html! { 12 |
13 |
14 | 15 |
16 |

17 | { props.title.clone() } 18 |

19 |
20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/10-wasm/backend/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "cards": [ 3 | { 4 | "id": 0, 5 | "description": "Sample description from server", 6 | "image_url": "https://cdn.thenewstack.io/media/2021/07/bde17530-wasm.png", 7 | "title": "The WASM card from the server" 8 | }, 9 | { 10 | "id": 1, 11 | "description": "He do be wildin'.", 12 | "image_url": "https://media.tenor.com/ednxg8HDD0YAAAAd/habibi-wildin.gif", 13 | "title": "Habibi wildin' on the server" 14 | }, 15 | { 16 | "id": 2, 17 | "description": "Forever incel", 18 | "image_url": "https://i.kym-cdn.com/photos/images/original/001/519/511/9c8.jpg", 19 | "title": "When she ghosts you after you show her your RAM stick" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /slides/split-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust Split Slide Template 5 | theme: rust 6 | paginate: true 7 | --- 8 | 9 | 10 | 11 | ### Title 12 | 13 |
14 | 15 | ```rust 16 | 17 | ``` 18 | 19 |
20 |
21 | 22 | ```rust 23 | 24 | ``` 25 | 26 |
27 | 28 | --- 29 | 30 | 31 | 32 | ### Title 33 | 34 |
35 | 36 | Common text. 37 | 38 |
39 | 40 |
41 | 42 | #### Left subtitle 43 | 44 | ```rust 45 | 46 | ``` 47 | 48 |
49 |
50 | 51 | #### Right subtitle 52 | 53 | ```rust 54 | 55 | ``` 56 | 57 |
58 | 59 | --- 60 | 61 | -------------------------------------------------------------------------------- /2024/slides/split-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust Split Slide Template 5 | theme: rust 6 | paginate: true 7 | --- 8 | 9 | 10 | 11 | ### Title 12 | 13 |
14 | 15 | ```rust 16 | 17 | ``` 18 | 19 |
20 |
21 | 22 | ```rust 23 | 24 | ``` 25 | 26 |
27 | 28 | --- 29 | 30 | 31 | 32 | ### Title 33 | 34 |
35 | 36 | Common text. 37 | 38 |
39 | 40 |
41 | 42 | #### Left subtitle 43 | 44 | ```rust 45 | 46 | ``` 47 | 48 |
49 |
50 | 51 | #### Right subtitle 52 | 53 | ```rust 54 | 55 | ``` 56 | 57 |
58 | 59 | --- 60 | 61 | -------------------------------------------------------------------------------- /.github/workflows/marp.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | #env: 12 | 13 | jobs: 14 | # Checks syntax formatting. 15 | convert: 16 | name: Convert to PDF 17 | runs-on: ubuntu-latest 18 | defaults: 19 | run: 20 | working-directory: ./slides 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: actions/setup-node@v3 24 | name: Use Node.js 25 | with: 26 | node-version: '20.x' 27 | - run: "npm ci" 28 | - run: "npx marp --html --pdf --pdf-outlines --pdf-outlines.pages=false -I . -o dist" 29 | - uses: actions/upload-artifact@v3 30 | name: Archive built PDFs 31 | with: 32 | name: pv281-slide-pdfs 33 | path: slides/dist/[0-1]*.pdf -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/src/components/container.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, function_component, html, Children, Html, Properties}; 2 | 3 | // /// Create a container that can accept children 4 | // /// - this is for educational purposes, as this example is trivial 5 | // /// MARKUP 6 | // /// ------ 7 | //
8 | // { 9 | // "here go the children" 10 | // } 11 | //
12 | 13 | #[derive(PartialEq, Properties)] 14 | pub struct ContainerProps { 15 | pub children: Children, 16 | pub class: String, 17 | } 18 | 19 | #[function_component(Container)] 20 | pub fn container(props: &ContainerProps) -> Html { 21 | html! { 22 |
23 | { 24 | for props.children.iter() 25 | } 26 |
27 | } 28 | } 29 | -------------------------------------------------------------------------------- /slides/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pv281-slides", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf dist && npm run -s og-image && npm run -s deck", 8 | "deck": "marp --html --no-stdin 01-intro.md -o dist/index.html && cpy \"assets/**/*\" dist/assets", 9 | "now-build": "npm run -s deck", 10 | "og-image": "marp --html 03-enums-and-structures.md -o dist/og-image.jpg", 11 | "start": "marp -s --html .", 12 | "preview": "CHROME_PATH=/usr/bin/chromium-browser marp -ps --html ." 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@marp-team/marp-cli": "^3.3.0", 18 | "@marp-team/marp-core": "^2.2.0", 19 | "markdown-it-plantuml": "^1.4.1", 20 | "markdown-it-named-code-blocks": "^0.2.0" 21 | }, 22 | "devDependencies": { 23 | "cpy-cli": "^3.1.1", 24 | "rimraf": "^3.0.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/src/components/card.rs: -------------------------------------------------------------------------------- 1 | use super::like_button::LikeButton; 2 | use yew::{classes, function_component, html, Html, Properties}; 3 | 4 | #[derive(Properties, PartialEq)] 5 | pub struct CardProps { 6 | pub description: String, 7 | pub image_url: String, 8 | pub title: String, 9 | } 10 | 11 | #[function_component(Card)] 12 | pub fn card(props: &CardProps) -> Html { 13 | html! { 14 |
15 |
16 | 17 |
18 |
19 |

{ props.title.clone() }

20 |

{ props.description.clone() }

21 | 22 |
23 |
24 | } 25 | } 26 | -------------------------------------------------------------------------------- /2024/slides/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pv281-slides", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rimraf dist && npm run -s og-image && npm run -s deck", 8 | "deck": "marp --html --no-stdin 03-enums-and-structures.md -o dist/index.html && cpy \"assets/**/*\" dist/assets", 9 | "now-build": "npm run -s deck", 10 | "og-image": "marp --html 03-enums-and-structures.md -o dist/og-image.jpg", 11 | "start": "marp -s --html .", 12 | "preview": "CHROME_PATH=/usr/bin/chromium-browser marp -ps --html ." 13 | }, 14 | "author": "", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@marp-team/marp-cli": "^3.3.0", 18 | "@marp-team/marp-core": "^2.2.0", 19 | "markdown-it-plantuml": "^1.4.1", 20 | "markdown-it-named-code-blocks": "^0.2.0" 21 | }, 22 | "devDependencies": { 23 | "cpy-cli": "^3.1.1", 24 | "rimraf": "^3.0.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/components/card.rs: -------------------------------------------------------------------------------- 1 | use super::like_button::LikeButton; 2 | use yew::{classes, function_component, html, Html, Properties}; 3 | 4 | #[derive(Properties, PartialEq)] 5 | pub struct CardProps { 6 | pub description: String, 7 | pub image_url: String, 8 | pub title: String, 9 | } 10 | 11 | #[function_component(Card)] 12 | pub fn card(props: &CardProps) -> Html { 13 | html! { 14 |
15 |
16 | 17 |
18 |
19 |

{ props.title.clone() }

20 |

{ props.description.clone() }

21 | 22 |
23 |
24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/src/components/like_button.rs: -------------------------------------------------------------------------------- 1 | use yew::{function_component, html, use_state, Callback, Html}; 2 | 3 | // /// Create a button with the following markup: 4 | // /// MARKUP 5 | // /// ------ 6 | //
{ 7 | // "stateful data here!" 8 | // }
9 | // /// ------ 10 | // /// The button will respond to clicks by visually modifying itself 11 | 12 | #[function_component(LikeButton)] 13 | pub fn like_button() -> Html { 14 | let liked = use_state(|| false); 15 | let onclick = { 16 | let liked = liked.clone(); 17 | 18 | Callback::from(move |_| liked.set(!*liked)) 19 | }; 20 | 21 | html! { 22 |
23 | { 24 | if !*liked { 25 | "Like!" 26 | } else { 27 | "Unlike." 28 | } 29 | } 30 |
31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{function_component, html, props, Html, Renderer}; 2 | 3 | // import components 4 | mod components; 5 | mod views; 6 | 7 | use components::header::{Header, HeaderProps}; 8 | use views::{home::HomePage, server::ServerPage}; 9 | use yew_router::{BrowserRouter, Routable, Switch}; 10 | 11 | #[derive(Clone, Routable, PartialEq)] 12 | pub enum Routes { 13 | #[at("/")] 14 | Home, 15 | #[at("/server-data")] 16 | Fetched, 17 | #[not_found] 18 | #[at("/404")] 19 | NotFound, 20 | } 21 | 22 | fn switch(routes: Routes) -> Html { 23 | match routes { 24 | Routes::Fetched => html! { }, 25 | _ => html! { }, 26 | } 27 | } 28 | 29 | #[function_component(App)] 30 | fn app() -> Html { 31 | let header_properties = props!(HeaderProps { 32 | image_url: "https://yew.rs/img/logo.png".to_string(), 33 | title: "New awesome app".to_string(), 34 | }); 35 | 36 | html! { 37 | 38 |
39 |
40 | render={switch} /> 41 |
42 |
43 | } 44 | } 45 | 46 | fn main() { 47 | Renderer::::new().render(); 48 | } 49 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/src/components/card.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, function_component, html, Html, Properties}; 2 | 3 | use crate::components::like_button::LikeButton; 4 | 5 | // /// Create a card that displays an image, a title, a description, and a button 6 | // /// which can be toggled (liked/unliked) 7 | // /// MARKUP 8 | // /// ------ 9 | //
10 | //
11 | // 12 | //
13 | //
14 | //

{ "title goes here" }

15 | //

{ "description goes here" }

16 | // { "like button goes here" } 17 | //
18 | //
19 | 20 | #[derive(Properties, PartialEq)] 21 | pub struct CardProps { 22 | pub image_url: String, 23 | pub description: String, 24 | pub title: String, 25 | } 26 | 27 | #[function_component(Card)] 28 | pub fn component(props: &CardProps) -> Html { 29 | html! { 30 |
31 |
32 | 33 |
34 |
35 |

{ props.title.clone() }

36 |

{ props.description.clone() }

37 | 38 |
39 |
40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/src/views/server.rs: -------------------------------------------------------------------------------- 1 | use gloo_net::http::Request; 2 | use serde::Deserialize; 3 | use yew::{function_component, html, use_effect_with_deps, use_state, Html}; 4 | 5 | use crate::components::{ 6 | card::{Card, CardProps}, 7 | container::Container, 8 | }; 9 | 10 | #[derive(Clone, PartialEq, Deserialize)] 11 | struct ImageCard { 12 | description: String, 13 | id: u32, 14 | image_url: String, 15 | title: String, 16 | } 17 | 18 | #[function_component(ServerPage)] 19 | pub fn server_page() -> Html { 20 | let cards = use_state(|| vec![]); 21 | 22 | { 23 | let cards = cards.clone(); 24 | use_effect_with_deps( 25 | move |_| { 26 | // create a JS Promise from the Rust's Future 27 | wasm_bindgen_futures::spawn_local(async move { 28 | let fetched_cards: Vec = Request::get("http://localhost:5000/cards") 29 | .send() 30 | .await 31 | .unwrap() // get the response, will crash the app 32 | .json() 33 | .await 34 | .unwrap(); // try to parse the json; 35 | // serde tries to deserialize the json to 36 | // vector of image cards 37 | 38 | // set the cards 39 | cards.set(fetched_cards); 40 | }) 41 | }, 42 | (), 43 | ) 44 | } 45 | 46 | html! { 47 | 48 | 49 | { 50 | cards.iter().map(|card_data| { 51 | let props = CardProps { 52 | description: card_data.description.clone(), 53 | image_url: card_data.image_url.clone(), 54 | title: card_data.title.clone(), 55 | }; 56 | 57 | html! { 58 | 59 | } 60 | }).collect::() 61 | 62 | } 63 | 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, function_component, html, props, Html, Renderer}; 2 | 3 | // import components 4 | mod components; 5 | 6 | use components::{ 7 | card::{Card, CardProps}, 8 | container::Container, 9 | header::{Header, HeaderProps}, 10 | }; 11 | 12 | struct ImageCard { 13 | description: String, 14 | id: u32, 15 | image_url: String, 16 | title: String, 17 | } 18 | 19 | #[function_component(App)] 20 | fn app() -> Html { 21 | let header_properties = props!(HeaderProps { 22 | image_url: "https://yew.rs/img/logo.png".to_string(), 23 | title: "New awesome app".to_string(), 24 | }); 25 | 26 | let cards: Vec = vec![ 27 | ImageCard { 28 | id: 0, 29 | description: "No, I don't have one. :(".into(), 30 | image_url: "https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/mbp-spacegray-select-202206_GEO_CZ?wid=904&hei=840&fmt=jpeg&qlt=90&.v=1664497359383".into(), 31 | title: "Macbook".into(), 32 | }, 33 | ImageCard { 34 | id: 1, 35 | description: "No, I don't have that one either... :D".into(), 36 | image_url: "https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/iphone-13-model-unselect-gallery-1-202207?wid=5120&hei=2880&fmt=p-jpg&qlt=80&.v=1654893619853".into(), 37 | title: "iPhone".into(), 38 | }, 39 | ]; 40 | 41 | html! { 42 |
43 |
44 | 45 | 46 | { 47 | cards.into_iter().map(|card_data| { 48 | let props = CardProps { 49 | description: card_data.description, 50 | image_url: card_data.image_url, 51 | title: card_data.title, 52 | }; 53 | 54 | html! { 55 | 56 | } 57 | }).collect::() 58 | } 59 | 60 | 61 |
62 | } 63 | } 64 | 65 | fn main() { 66 | Renderer::::new().render(); 67 | } 68 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, function_component, html, props, Html, Renderer}; 2 | 3 | // import components 4 | mod components; 5 | 6 | use components::{ 7 | card::{Card, CardProps}, 8 | container::Container, 9 | header::{Header, HeaderProps}, 10 | }; 11 | 12 | struct ImageCard { 13 | id: u32, 14 | image_url: String, 15 | description: String, 16 | title: String, 17 | } 18 | 19 | // create structure that will hold the card data 20 | 21 | #[function_component(App)] 22 | fn app() -> Html { 23 | // create header properties 24 | let header_props = props!(HeaderProps { 25 | image_url: "https://store.storeimages.cdn-apple.com/4668/as-images.apple.com/is/iphone-13-model-unselect-gallery-1-202207?wid=5120&hei=2880&fmt=p-jpg&qlt=80&.v=1654893619853".to_string(), 26 | title: "Je to eshop".to_string(), 27 | }); 28 | 29 | let cards: Vec = vec![ 30 | ImageCard { 31 | id: 1, 32 | image_url: "https://images.samsung.com/cz/smartphones/galaxy-z-fold3-5g/buy/02_Carousel/02_Feature/ZFold3_Carousel_FeatureKV_ArmorAluminum_MO.jpg".to_string(), 33 | title: "Galaxy".to_string(), 34 | description: "Yayyy".to_string(), 35 | }, 36 | ImageCard { 37 | id: 2, 38 | image_url: "https://images.samsung.com/cz/smartphones/galaxy-z-fold3-5g/buy/02_Carousel/02_Feature/ZFold3_Carousel_FeatureKV_ArmorAluminum_MO.jpg".to_string(), 39 | title: "Another galaxy".to_string(), 40 | description: "Shall I go into detail?".to_string(), 41 | } 42 | ]; 43 | 44 | // create a list of card data 45 | 46 | html! { 47 |
48 |
49 | 50 | 51 | { 52 | cards.into_iter().map(|card_data| { 53 | let props = props!(CardProps { 54 | title: card_data.title.clone(), 55 | description: card_data.description.clone(), 56 | image_url: card_data.image_url.clone(), 57 | }); 58 | 59 | html! { 60 | 61 | } 62 | }).collect::() 63 | } 64 | 65 | 66 |
67 | } 68 | } 69 | 70 | fn main() { 71 | // hook up the WASM rendering 72 | Renderer::::new().render(); 73 | } 74 | -------------------------------------------------------------------------------- /examples/10-wasm/demo-reference/assets/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg: #111111; 3 | --font: #EFF5F5; 4 | --header-bg: #EB6440; 5 | --header-font: var(--font); 6 | --container-color: #497174; 7 | --card-color: #D6E4E5; 8 | --card-font: var(--bg); 9 | } 10 | 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | font-size: 16px; 15 | font-family: 'Roboto', sans-serif; 16 | } 17 | 18 | .heading { 19 | padding: 0; 20 | margin: 0; 21 | } 22 | 23 | .image { 24 | width: 100%; 25 | } 26 | 27 | .main-window { 28 | display: flex; 29 | flex-direction: column; 30 | align-items: center; 31 | background: rgb(17,17,17); 32 | background: linear-gradient(60deg, rgba(17,17,17,1) 9%, rgba(73,113,116,1) 100%); 33 | color: var(--font); 34 | height: 100vh; 35 | width: 100vw; 36 | overflow: hidden; 37 | } 38 | 39 | .header { 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | gap: 2rem; 44 | padding: 2rem; 45 | font-size: 3rem; 46 | background: var(--header-bg); 47 | color: var(--header-font); 48 | width: 100%; 49 | } 50 | 51 | .header__logo-container { 52 | width: 5rem; 53 | } 54 | 55 | .container { 56 | width: inherit; 57 | display: flex; 58 | flex-direction: column; 59 | align-items: center; 60 | overflow: hidden auto; 61 | } 62 | 63 | .card-container { 64 | max-width: min(100%, 60rem); 65 | padding: 2rem 4rem; 66 | background-color: var(--container-color); 67 | display: flex; 68 | gap: 2rem; 69 | flex-direction: column; 70 | align-items: center; 71 | flex-grow: 1; 72 | } 73 | 74 | .card { 75 | align-items: center; 76 | border-radius: 2rem; 77 | background-color: var(--card-color); 78 | padding: 2rem; 79 | width: 30rem; 80 | color: var(--card-font); 81 | display: grid; 82 | gap: 0rem 1rem; 83 | grid-template: 84 | [row1-start] "card-image card-content" [row1-end] 85 | / 10rem auto; 86 | } 87 | 88 | .card__image-container { 89 | grid-area: card-image; 90 | width: 10rem; 91 | } 92 | 93 | .card__content { 94 | display: flex; 95 | flex-direction: column; 96 | grid-area: card-content; 97 | justify-content: center; 98 | } 99 | 100 | .card__title { 101 | font-weight: 900; 102 | font-size: 2rem; 103 | } 104 | 105 | .card__description { 106 | font-weight: 100; 107 | font-size: 1rem; 108 | } 109 | 110 | .card__button { 111 | padding: 0.5rem 1rem; 112 | border-radius: 0.5rem; 113 | background-color: var(--bg); 114 | color: var(--font); 115 | font-weight: 400; 116 | width: fit-content; 117 | } 118 | 119 | .card__button:hover { 120 | cursor: pointer; 121 | } -------------------------------------------------------------------------------- /examples/10-wasm/demo-template/assets/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg: #111111; 3 | --font: #EFF5F5; 4 | --header-bg: #EB6440; 5 | --header-font: var(--font); 6 | --container-color: #497174; 7 | --card-color: #D6E4E5; 8 | --card-font: var(--bg); 9 | } 10 | 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | font-size: 16px; 15 | font-family: 'Roboto', sans-serif; 16 | } 17 | 18 | .heading { 19 | padding: 0; 20 | margin: 0; 21 | } 22 | 23 | .image { 24 | width: 100%; 25 | } 26 | 27 | .main-window { 28 | display: flex; 29 | flex-direction: column; 30 | align-items: center; 31 | background: rgb(17,17,17); 32 | background: linear-gradient(60deg, rgba(17,17,17,1) 9%, rgba(73,113,116,1) 100%); 33 | color: var(--font); 34 | height: 100vh; 35 | width: 100vw; 36 | overflow: hidden; 37 | } 38 | 39 | .header { 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | gap: 2rem; 44 | padding: 2rem; 45 | font-size: 3rem; 46 | background: var(--header-bg); 47 | color: var(--header-font); 48 | width: 100%; 49 | } 50 | 51 | .header__logo-container { 52 | width: 5rem; 53 | } 54 | 55 | .container { 56 | width: inherit; 57 | display: flex; 58 | flex-direction: column; 59 | align-items: center; 60 | overflow: hidden auto; 61 | } 62 | 63 | .card-container { 64 | max-width: min(100%, 60rem); 65 | padding: 2rem 4rem; 66 | background-color: var(--container-color); 67 | display: flex; 68 | gap: 2rem; 69 | flex-direction: column; 70 | align-items: center; 71 | flex-grow: 1; 72 | } 73 | 74 | .card { 75 | align-items: center; 76 | border-radius: 2rem; 77 | background-color: var(--card-color); 78 | padding: 2rem; 79 | width: 30rem; 80 | color: var(--card-font); 81 | display: grid; 82 | gap: 0rem 1rem; 83 | grid-template: 84 | [row1-start] "card-image card-content" [row1-end] 85 | / 10rem auto; 86 | } 87 | 88 | .card__image-container { 89 | grid-area: card-image; 90 | width: 10rem; 91 | } 92 | 93 | .card__content { 94 | display: flex; 95 | flex-direction: column; 96 | grid-area: card-content; 97 | justify-content: center; 98 | } 99 | 100 | .card__title { 101 | font-weight: 900; 102 | font-size: 2rem; 103 | } 104 | 105 | .card__description { 106 | font-weight: 100; 107 | font-size: 1rem; 108 | } 109 | 110 | .card__button { 111 | padding: 0.5rem 1rem; 112 | border-radius: 0.5rem; 113 | background-color: var(--bg); 114 | color: var(--font); 115 | font-weight: 400; 116 | width: fit-content; 117 | } 118 | 119 | .card__button:hover { 120 | cursor: pointer; 121 | } -------------------------------------------------------------------------------- /examples/10-wasm/demo-router-fetching/assets/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg: #111111; 3 | --font: #EFF5F5; 4 | --header-bg: #EB6440; 5 | --header-font: var(--font); 6 | --container-color: #497174; 7 | --card-color: #D6E4E5; 8 | --card-font: var(--bg); 9 | } 10 | 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | font-size: 16px; 15 | font-family: 'Roboto', sans-serif; 16 | } 17 | 18 | .heading { 19 | padding: 0; 20 | margin: 0; 21 | } 22 | 23 | .image { 24 | width: 100%; 25 | } 26 | 27 | .main-window { 28 | display: flex; 29 | flex-direction: column; 30 | align-items: center; 31 | background: rgb(17,17,17); 32 | background: linear-gradient(60deg, rgba(17,17,17,1) 9%, rgba(73,113,116,1) 100%); 33 | color: var(--font); 34 | height: 100vh; 35 | width: 100vw; 36 | overflow: hidden; 37 | } 38 | 39 | .header { 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | gap: 2rem; 44 | padding: 2rem; 45 | font-size: 3rem; 46 | background: var(--header-bg); 47 | color: var(--header-font); 48 | width: 100%; 49 | } 50 | 51 | .header__logo-container { 52 | width: 5rem; 53 | } 54 | 55 | .container { 56 | width: inherit; 57 | display: flex; 58 | flex-direction: column; 59 | align-items: center; 60 | overflow: hidden auto; 61 | } 62 | 63 | .card-container { 64 | max-width: min(100%, 60rem); 65 | padding: 2rem 4rem; 66 | background-color: var(--container-color); 67 | display: flex; 68 | gap: 2rem; 69 | flex-direction: column; 70 | align-items: center; 71 | flex-grow: 1; 72 | } 73 | 74 | .card { 75 | align-items: center; 76 | border-radius: 2rem; 77 | background-color: var(--card-color); 78 | padding: 2rem; 79 | width: 30rem; 80 | color: var(--card-font); 81 | display: grid; 82 | gap: 0rem 1rem; 83 | grid-template: 84 | [row1-start] "card-image card-content" [row1-end] 85 | / 10rem auto; 86 | } 87 | 88 | .card__image-container { 89 | grid-area: card-image; 90 | width: 10rem; 91 | } 92 | 93 | .card__content { 94 | display: flex; 95 | flex-direction: column; 96 | grid-area: card-content; 97 | justify-content: center; 98 | } 99 | 100 | .card__title { 101 | font-weight: 900; 102 | font-size: 2rem; 103 | } 104 | 105 | .card__description { 106 | font-weight: 100; 107 | font-size: 1rem; 108 | } 109 | 110 | .card__button { 111 | padding: 0.5rem 1rem; 112 | border-radius: 0.5rem; 113 | background-color: var(--bg); 114 | color: var(--font); 115 | font-weight: 400; 116 | width: fit-content; 117 | } 118 | 119 | .clickable:hover { 120 | cursor: pointer; 121 | } -------------------------------------------------------------------------------- /slides/assets/01-images/rustacean-flat-noshadow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /2024/slides/assets/01-images/rustacean-flat-noshadow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /slides/themes/rust.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*! 3 | * Marp Dracula theme. 4 | * @theme rust 5 | * @author Daniel Nicolas Gisolfi 6 | * 7 | * @auto-scaling true 8 | * @size 16:9 1280px 720px 9 | */ 10 | 11 | @import url("https://fonts.googleapis.com/css?family=Lato:400,900|IBM+Plex+Sans:400,700"); 12 | @import url('https://fonts.googleapis.com/css2?family=Alfa+Slab+One&display=swap'); 13 | @import url('https://fonts.googleapis.com/css2?family=Fira+Sans&display=swap'); 14 | @import url('https://fonts.googleapis.com/css2?family=Fira+Sans:wght@700&display=swap'); 15 | @import url('https://fonts.googleapis.com/css2?family=Fira+Mono&display=swap'); 16 | @import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap'); 17 | 18 | :root { 19 | --dracula-background: #282a36; 20 | --dracula-current-line: #44475a; 21 | --dracula-secondary-line: #6a6f8d; 22 | --dracula-foreground: #f8f8f2; 23 | --dracula-comment: #8AA1E6; 24 | --dracula-cyan: #8be9fd; 25 | --dracula-green: #50fa7b; 26 | --dracula-orange: #ffb86c; 27 | --dracula-pink: #ff79c6; 28 | --dracula-purple:#bd93f9; 29 | --dracula-red: #ff5555; 30 | --dracula-dark-red: #e73434; 31 | --dracula-yellow: #f1fa8c; 32 | } 33 | 34 | .hljs { 35 | display: block; 36 | overflow-x: auto; 37 | padding: 0.5em; 38 | background: var(--dracula-background); 39 | } 40 | 41 | /* Dracula Foreground */ 42 | .hljs, 43 | .hljs-subst, 44 | .hljs-typing, 45 | .hljs-variable, 46 | .hljs-template-variable { 47 | color: var(--dracula-foreground); 48 | } 49 | 50 | /* Dracula Comment */ 51 | .hljs-comment, 52 | .hljs-quote, 53 | .hljs-deletion { 54 | color: var(--dracula-comment); 55 | } 56 | 57 | /* Dracula Cyan */ 58 | .hljs-meta, 59 | .hljs-doctag, 60 | .hljs-built_in, 61 | .hljs-selector-tag, 62 | .hljs-selector-class, 63 | .hljs-selector-id, 64 | .hljs-section, 65 | .hljs-link, 66 | .hljs-class { 67 | color: var(--dracula-cyan); 68 | } 69 | 70 | /* Dracula Green */ 71 | .hljs-title { 72 | color: var(--dracula-green); 73 | } 74 | 75 | /* Dracula Orange */ 76 | .hljs-params { 77 | color: var(--dracula-orange); 78 | } 79 | 80 | /* Dracula Pink */ 81 | .hljs-keyword { 82 | color: var(--dracula-pink); 83 | } 84 | 85 | /* Dracula Purple */ 86 | .hljs-literal, 87 | .hljs-number { 88 | color: var(--dracula-purple); 89 | } 90 | 91 | /* Dracula Red */ 92 | .hljs-regexp { 93 | color: var(--dracula-red); 94 | } 95 | 96 | /* Dracula Yellow */ 97 | .hljs-string, 98 | .hljs-name, 99 | .hljs-type, 100 | .hljs-attr, 101 | .hljs-attribute, 102 | .hljs-selector-pseudo, 103 | .hljs-symbol, 104 | .hljs-bullet, 105 | .hljs-addition, 106 | .hljs-template-tag { 107 | color: var(--dracula-yellow); 108 | } 109 | 110 | .hljs-keyword, 111 | .hljs-selector-tag, 112 | .hljs-literal, 113 | .hljs-title, 114 | .hljs-section, 115 | .hljs-doctag, 116 | .hljs-type, 117 | .hljs-name, 118 | .hljs-strong { 119 | font-weight: bold; 120 | } 121 | 122 | .hljs-params, 123 | .hljs-emphasis { 124 | font-style: italic; 125 | } 126 | 127 | svg[data-marp-fitting=svg] { 128 | max-height: 580px; 129 | } 130 | 131 | h1, 132 | h2, 133 | h3, 134 | h4, 135 | h5, 136 | h6 { 137 | margin: 0.5em 0 0 0; 138 | color: var(--dracula-red); 139 | } 140 | h1 strong, 141 | h2 strong, 142 | h3 strong, 143 | h4 strong, 144 | h5 strong, 145 | h6 strong { 146 | font-weight: inherit; 147 | } 148 | 149 | h1 { 150 | font-size: 1.8em; 151 | } 152 | 153 | h2 { 154 | font-size: 1.5em; 155 | } 156 | 157 | h3 { 158 | font-size: 1.3em; 159 | } 160 | 161 | h4 { 162 | font-size: 1.1em; 163 | } 164 | 165 | h5 { 166 | font-size: 1em; 167 | } 168 | 169 | h6 { 170 | font-size: 0.9em; 171 | } 172 | 173 | p, 174 | blockquote { 175 | margin: 1em 0 0 0; 176 | } 177 | 178 | ul > li, 179 | ol > li { 180 | margin: 0.3em 0 0 0; 181 | color: var(--dracula-cyan); 182 | } 183 | ul > li > p, 184 | ol > li > p { 185 | margin: 0.6em 0 0 0; 186 | } 187 | 188 | code { 189 | display: inline-block; 190 | font-family: "IBM Plex Mono", monospace; 191 | font-size: 0.8em; 192 | letter-spacing: 0; 193 | margin: -0.1em 0.15em; 194 | padding: 0.1em 0.2em; 195 | vertical-align: baseline; 196 | color: var(--dracula-foreground); 197 | } 198 | 199 | pre { 200 | display: block; 201 | margin: 1em 0 0 0; 202 | min-height: 1em; 203 | overflow: visible; 204 | } 205 | pre code { 206 | box-sizing: border-box; 207 | margin: 0; 208 | min-width: 100%; 209 | padding: 0.5em; 210 | font-size: 0.7em; 211 | } 212 | pre code svg[data-marp-fitting=svg] { 213 | max-height: calc(580px - 1em); 214 | } 215 | 216 | blockquote { 217 | margin: 1em 0 0 0; 218 | padding: 0 1em; 219 | position: relative; 220 | color: var(--dracula-orange); 221 | } 222 | blockquote::after, blockquote::before { 223 | content: "“"; 224 | display: block; 225 | font-family: "Times New Roman", serif; 226 | font-weight: bold; 227 | position: absolute; 228 | color: var(--dracula-green); 229 | } 230 | blockquote::before { 231 | top: 0; 232 | left: 0; 233 | } 234 | blockquote::after { 235 | right: 0; 236 | bottom: 0; 237 | transform: rotate(180deg); 238 | } 239 | blockquote > *:first-child { 240 | margin-top: 0; 241 | } 242 | 243 | mark { 244 | background: transparent; 245 | } 246 | 247 | table { 248 | border-spacing: 0; 249 | border-collapse: collapse; 250 | margin: 1em 0 0 0; 251 | } 252 | table th, 253 | table td { 254 | padding: 0.2em 0.4em; 255 | border-width: 1px; 256 | border-style: solid; 257 | } 258 | 259 | section { 260 | font-size: 35px; 261 | font-family: "IBM Plex Sans", sans-serif; 262 | line-height: 1.35; 263 | letter-spacing: 1.25px; 264 | padding: 70px; 265 | color: var(--dracula-foreground); 266 | background-color: var(--dracula-background); 267 | justify-content: center; 268 | /* align-items: center; */ 269 | display: flex; 270 | flex-direction: column; 271 | } 272 | section > *:first-child, 273 | section > header:first-child + * { 274 | margin-top: 0; 275 | } 276 | section a, 277 | section mark { 278 | color: var(--dracula-red); 279 | } 280 | section code { 281 | background: var(--dracula-current-line); 282 | } 283 | section h1 strong, 284 | section h2 strong, 285 | section h3 strong, 286 | section h4 strong, 287 | section h5 strong, 288 | section h6 strong { 289 | color: var(--dracula-dark-red); 290 | } 291 | section pre > code { 292 | background: var(--dracula-current-line); 293 | color: var(--dracula-foreground); 294 | } 295 | section header, 296 | section footer, 297 | section section::after, 298 | section blockquote::before, 299 | section blockquote::after { 300 | color: var(--dracula-comment); 301 | } 302 | section table th, 303 | section table td { 304 | border-color: var(--dracula-current-line); 305 | } 306 | section table thead th { 307 | background: var(--dracula-current-line); 308 | color: var(--dracula-yellow); 309 | } 310 | section table tbody > tr:nth-child(even) td, 311 | section table tbody > tr:nth-child(even) th { 312 | background: var(--dracula-current-line); 313 | } 314 | 315 | section table tbody > tr:nth-child(even) code { 316 | background-color: var(--dracula-background); 317 | } 318 | 319 | .dark-background, 320 | td[rowspan] { 321 | background-color: var(--dracula-background) !important; 322 | } 323 | 324 | header, 325 | footer, 326 | section::after { 327 | box-sizing: border-box; 328 | font-size: 66%; 329 | height: 70px; 330 | line-height: 50px; 331 | overflow: hidden; 332 | padding: 10px 25px; 333 | position: absolute; 334 | color: var(--dracula-comment); 335 | content: attr(data-marpit-pagination) '/' attr(data-marpit-pagination-total); 336 | } 337 | 338 | header { 339 | left: 0; 340 | right: 0; 341 | top: 0; 342 | } 343 | 344 | footer { 345 | left: 0; 346 | right: 0; 347 | bottom: 0; 348 | } 349 | 350 | section::after { 351 | right: 0; 352 | bottom: 0; 353 | font-size: 80%; 354 | } 355 | 356 | h1,h2,h3 { 357 | font-family: "Alfa Slab One", serif; 358 | } 359 | 360 | section { 361 | font-family: 'Fira Sans', sans-serif; 362 | text-align: center; 363 | } 364 | 365 | code, pre { 366 | font-family: 'Fira Mono', monospace; 367 | text-align: left; 368 | } 369 | 370 | pre code svg[data-marp-fitting=svg] { 371 | max-height: calc(40vh - 1em); 372 | } 373 | 374 | section.plantuml { 375 | background-color: white !important; 376 | } 377 | 378 | section.plantuml > img { 379 | /* Reason: valid outside Firefox */ 380 | /* noinspection CssInvalidPropertyValue */ 381 | width: -webkit-fill-available; 382 | /* Reason: valid inside Firefox */ 383 | /* noinspection CssInvalidPropertyValue */ 384 | width: -moz-available; 385 | } 386 | 387 | section.split { 388 | overflow: visible; 389 | padding-top: 0; 390 | display: grid; 391 | grid-template-columns: 45% 45%; 392 | grid-template-rows: 3em min-content min-content auto min-content; 393 | grid-template-areas: 394 | "slideheading slideheading" 395 | "commontext commontext" 396 | "columnheading columnheading" 397 | "leftpanel rightpanel" 398 | "commonnote commonnote"; 399 | column-gap: 1em; 400 | align-items: center; 401 | } 402 | section.split h3 { 403 | grid-area: slideheading; 404 | font-size: 1.3em; 405 | margin: 1em 0 0 0; 406 | } 407 | section.split h4 { 408 | grid-area: columnheading; 409 | font-size: 1.3em; 410 | margin: auto; 411 | } 412 | section.split h6 { 413 | grid-area: columnheading; 414 | font-size: 1em; 415 | margin: auto; 416 | } 417 | section.split .common-text { 418 | grid-area: commontext; 419 | } 420 | section.split .left-column { 421 | grid-area: leftpanel; 422 | } 423 | section.split .right-column { 424 | grid-area: rightpanel; 425 | } 426 | section.split .common-note { 427 | grid-area: commonnote; 428 | } 429 | 430 | /* For debugging purposes only */ 431 | section.split .left-column, 432 | section.split .right-column, 433 | section.split .common-text, 434 | section.split h3 { 435 | /*border: 1.5pt dashed dimgray;*/ 436 | } 437 | 438 | .named-fence-block{ 439 | position: relative; 440 | } 441 | 442 | .named-fence-filename{ 443 | position: absolute; 444 | top: 0; 445 | right: 0; 446 | padding: 0.3em; 447 | 448 | font-size: 0.5em; 449 | font-weight: bold; 450 | 451 | background-color: var(--dracula-secondary-line); 452 | color: var(--dracula-foreground); 453 | opacity: 0.5; 454 | } 455 | -------------------------------------------------------------------------------- /2024/slides/themes/rust.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /*! 3 | * Marp Dracula theme. 4 | * @theme rust 5 | * @author Daniel Nicolas Gisolfi 6 | * 7 | * @auto-scaling true 8 | * @size 16:9 1280px 720px 9 | */ 10 | 11 | @import url("https://fonts.googleapis.com/css?family=Lato:400,900|IBM+Plex+Sans:400,700"); 12 | @import url('https://fonts.googleapis.com/css2?family=Alfa+Slab+One&display=swap'); 13 | @import url('https://fonts.googleapis.com/css2?family=Fira+Sans&display=swap'); 14 | @import url('https://fonts.googleapis.com/css2?family=Fira+Sans:wght@700&display=swap'); 15 | @import url('https://fonts.googleapis.com/css2?family=Fira+Mono&display=swap'); 16 | @import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap'); 17 | 18 | :root { 19 | --dracula-background: #282a36; 20 | --dracula-current-line: #44475a; 21 | --dracula-secondary-line: #6a6f8d; 22 | --dracula-foreground: #f8f8f2; 23 | --dracula-comment: #8AA1E6; 24 | --dracula-cyan: #8be9fd; 25 | --dracula-green: #50fa7b; 26 | --dracula-orange: #ffb86c; 27 | --dracula-pink: #ff79c6; 28 | --dracula-purple:#bd93f9; 29 | --dracula-red: #ff5555; 30 | --dracula-dark-red: #e73434; 31 | --dracula-yellow: #f1fa8c; 32 | } 33 | 34 | .hljs { 35 | display: block; 36 | overflow-x: auto; 37 | padding: 0.5em; 38 | background: var(--dracula-background); 39 | } 40 | 41 | /* Dracula Foreground */ 42 | .hljs, 43 | .hljs-subst, 44 | .hljs-typing, 45 | .hljs-variable, 46 | .hljs-template-variable { 47 | color: var(--dracula-foreground); 48 | } 49 | 50 | /* Dracula Comment */ 51 | .hljs-comment, 52 | .hljs-quote, 53 | .hljs-deletion { 54 | color: var(--dracula-comment); 55 | } 56 | 57 | /* Dracula Cyan */ 58 | .hljs-meta, 59 | .hljs-doctag, 60 | .hljs-built_in, 61 | .hljs-selector-tag, 62 | .hljs-selector-class, 63 | .hljs-selector-id, 64 | .hljs-section, 65 | .hljs-link, 66 | .hljs-class { 67 | color: var(--dracula-cyan); 68 | } 69 | 70 | /* Dracula Green */ 71 | .hljs-title { 72 | color: var(--dracula-green); 73 | } 74 | 75 | /* Dracula Orange */ 76 | .hljs-params { 77 | color: var(--dracula-orange); 78 | } 79 | 80 | /* Dracula Pink */ 81 | .hljs-keyword { 82 | color: var(--dracula-pink); 83 | } 84 | 85 | /* Dracula Purple */ 86 | .hljs-literal, 87 | .hljs-number { 88 | color: var(--dracula-purple); 89 | } 90 | 91 | /* Dracula Red */ 92 | .hljs-regexp { 93 | color: var(--dracula-red); 94 | } 95 | 96 | /* Dracula Yellow */ 97 | .hljs-string, 98 | .hljs-name, 99 | .hljs-type, 100 | .hljs-attr, 101 | .hljs-attribute, 102 | .hljs-selector-pseudo, 103 | .hljs-symbol, 104 | .hljs-bullet, 105 | .hljs-addition, 106 | .hljs-template-tag { 107 | color: var(--dracula-yellow); 108 | } 109 | 110 | .hljs-keyword, 111 | .hljs-selector-tag, 112 | .hljs-literal, 113 | .hljs-title, 114 | .hljs-section, 115 | .hljs-doctag, 116 | .hljs-type, 117 | .hljs-name, 118 | .hljs-strong { 119 | font-weight: bold; 120 | } 121 | 122 | .hljs-params, 123 | .hljs-emphasis { 124 | font-style: italic; 125 | } 126 | 127 | svg[data-marp-fitting=svg] { 128 | max-height: 580px; 129 | } 130 | 131 | h1, 132 | h2, 133 | h3, 134 | h4, 135 | h5, 136 | h6 { 137 | margin: 0.5em 0 0 0; 138 | color: var(--dracula-red); 139 | } 140 | h1 strong, 141 | h2 strong, 142 | h3 strong, 143 | h4 strong, 144 | h5 strong, 145 | h6 strong { 146 | font-weight: inherit; 147 | } 148 | 149 | h1 { 150 | font-size: 1.8em; 151 | } 152 | 153 | h2 { 154 | font-size: 1.5em; 155 | } 156 | 157 | h3 { 158 | font-size: 1.3em; 159 | } 160 | 161 | h4 { 162 | font-size: 1.1em; 163 | } 164 | 165 | h5 { 166 | font-size: 1em; 167 | } 168 | 169 | h6 { 170 | font-size: 0.9em; 171 | } 172 | 173 | p, 174 | blockquote { 175 | margin: 1em 0 0 0; 176 | } 177 | 178 | ul > li, 179 | ol > li { 180 | margin: 0.3em 0 0 0; 181 | color: var(--dracula-cyan); 182 | } 183 | ul > li > p, 184 | ol > li > p { 185 | margin: 0.6em 0 0 0; 186 | } 187 | 188 | code { 189 | display: inline-block; 190 | font-family: "IBM Plex Mono", monospace; 191 | font-size: 0.8em; 192 | letter-spacing: 0; 193 | margin: -0.1em 0.15em; 194 | padding: 0.1em 0.2em; 195 | vertical-align: baseline; 196 | color: var(--dracula-foreground); 197 | } 198 | 199 | pre { 200 | display: block; 201 | margin: 1em 0 0 0; 202 | min-height: 1em; 203 | overflow: visible; 204 | } 205 | pre code { 206 | box-sizing: border-box; 207 | margin: 0; 208 | min-width: 100%; 209 | padding: 0.5em; 210 | font-size: 0.7em; 211 | } 212 | pre code svg[data-marp-fitting=svg] { 213 | max-height: calc(580px - 1em); 214 | } 215 | 216 | blockquote { 217 | margin: 1em 0 0 0; 218 | padding: 0 1em; 219 | position: relative; 220 | color: var(--dracula-orange); 221 | } 222 | blockquote::after, blockquote::before { 223 | content: "“"; 224 | display: block; 225 | font-family: "Times New Roman", serif; 226 | font-weight: bold; 227 | position: absolute; 228 | color: var(--dracula-green); 229 | } 230 | blockquote::before { 231 | top: 0; 232 | left: 0; 233 | } 234 | blockquote::after { 235 | right: 0; 236 | bottom: 0; 237 | transform: rotate(180deg); 238 | } 239 | blockquote > *:first-child { 240 | margin-top: 0; 241 | } 242 | 243 | mark { 244 | background: transparent; 245 | } 246 | 247 | table { 248 | border-spacing: 0; 249 | border-collapse: collapse; 250 | margin: 1em 0 0 0; 251 | } 252 | table th, 253 | table td { 254 | padding: 0.2em 0.4em; 255 | border-width: 1px; 256 | border-style: solid; 257 | } 258 | 259 | section { 260 | font-size: 35px; 261 | font-family: "IBM Plex Sans", sans-serif; 262 | line-height: 1.35; 263 | letter-spacing: 1.25px; 264 | padding: 70px; 265 | color: var(--dracula-foreground); 266 | background-color: var(--dracula-background); 267 | justify-content: center; 268 | /* align-items: center; */ 269 | display: flex; 270 | flex-direction: column; 271 | } 272 | section > *:first-child, 273 | section > header:first-child + * { 274 | margin-top: 0; 275 | } 276 | section a, 277 | section mark { 278 | color: var(--dracula-red); 279 | } 280 | section code { 281 | background: var(--dracula-current-line); 282 | } 283 | section h1 strong, 284 | section h2 strong, 285 | section h3 strong, 286 | section h4 strong, 287 | section h5 strong, 288 | section h6 strong { 289 | color: var(--dracula-dark-red); 290 | } 291 | section pre > code { 292 | background: var(--dracula-current-line); 293 | color: var(--dracula-foreground); 294 | } 295 | section header, 296 | section footer, 297 | section section::after, 298 | section blockquote::before, 299 | section blockquote::after { 300 | color: var(--dracula-comment); 301 | } 302 | section table th, 303 | section table td { 304 | border-color: var(--dracula-current-line); 305 | } 306 | section table thead th { 307 | background: var(--dracula-current-line); 308 | color: var(--dracula-yellow); 309 | } 310 | section table tbody > tr:nth-child(even) td, 311 | section table tbody > tr:nth-child(even) th { 312 | background: var(--dracula-current-line); 313 | } 314 | 315 | section table tbody > tr:nth-child(even) code { 316 | background-color: var(--dracula-background); 317 | } 318 | 319 | .dark-background, 320 | td[rowspan] { 321 | background-color: var(--dracula-background) !important; 322 | } 323 | 324 | header, 325 | footer, 326 | section::after { 327 | box-sizing: border-box; 328 | font-size: 66%; 329 | height: 70px; 330 | line-height: 50px; 331 | overflow: hidden; 332 | padding: 10px 25px; 333 | position: absolute; 334 | color: var(--dracula-comment); 335 | content: attr(data-marpit-pagination) '/' attr(data-marpit-pagination-total); 336 | } 337 | 338 | header { 339 | left: 0; 340 | right: 0; 341 | top: 0; 342 | } 343 | 344 | footer { 345 | left: 0; 346 | right: 0; 347 | bottom: 0; 348 | } 349 | 350 | section::after { 351 | right: 0; 352 | bottom: 0; 353 | font-size: 80%; 354 | } 355 | 356 | h1,h2,h3 { 357 | font-family: "Alfa Slab One", serif; 358 | } 359 | 360 | section { 361 | font-family: 'Fira Sans', sans-serif; 362 | text-align: center; 363 | } 364 | 365 | code, pre { 366 | font-family: 'Fira Mono', monospace; 367 | text-align: left; 368 | } 369 | 370 | pre code svg[data-marp-fitting=svg] { 371 | max-height: calc(40vh - 1em); 372 | } 373 | 374 | section.plantuml { 375 | background-color: white !important; 376 | } 377 | 378 | section.plantuml > img { 379 | /* Reason: valid outside Firefox */ 380 | /* noinspection CssInvalidPropertyValue */ 381 | width: -webkit-fill-available; 382 | /* Reason: valid inside Firefox */ 383 | /* noinspection CssInvalidPropertyValue */ 384 | width: -moz-available; 385 | } 386 | 387 | section.split { 388 | overflow: visible; 389 | padding-top: 0; 390 | display: grid; 391 | grid-template-columns: 45% 45%; 392 | grid-template-rows: 3em min-content min-content auto min-content; 393 | grid-template-areas: 394 | "slideheading slideheading" 395 | "commontext commontext" 396 | "columnheading columnheading" 397 | "leftpanel rightpanel" 398 | "commonnote commonnote"; 399 | column-gap: 1em; 400 | align-items: center; 401 | } 402 | section.split h3 { 403 | grid-area: slideheading; 404 | font-size: 1.3em; 405 | margin: 1em 0 0 0; 406 | } 407 | section.split h4 { 408 | grid-area: columnheading; 409 | font-size: 1.3em; 410 | margin: auto; 411 | } 412 | section.split h6 { 413 | grid-area: columnheading; 414 | font-size: 1em; 415 | margin: auto; 416 | } 417 | section.split .common-text { 418 | grid-area: commontext; 419 | } 420 | section.split .left-column { 421 | grid-area: leftpanel; 422 | } 423 | section.split .right-column { 424 | grid-area: rightpanel; 425 | } 426 | section.split .common-note { 427 | grid-area: commonnote; 428 | } 429 | 430 | /* For debugging purposes only */ 431 | section.split .left-column, 432 | section.split .right-column, 433 | section.split .common-text, 434 | section.split h3 { 435 | /*border: 1.5pt dashed dimgray;*/ 436 | } 437 | 438 | .named-fence-block{ 439 | position: relative; 440 | } 441 | 442 | .named-fence-filename{ 443 | position: absolute; 444 | top: 0; 445 | right: 0; 446 | padding: 0.3em; 447 | 448 | font-size: 0.5em; 449 | font-weight: bold; 450 | 451 | background-color: var(--dracula-secondary-line); 452 | color: var(--dracula-foreground); 453 | opacity: 0.5; 454 | } 455 | -------------------------------------------------------------------------------- /slides/assets/01-images/computerlanguagebenchmarkgame2023.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 1 25 | 3 26 | 5 27 | 10 28 | 30 29 | 50 30 | 100 31 | 300 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | C gcc C gcc C gcc C gcc C gcc 44 | C++ g++ C++ g++ C++ g++ C++ g++ 45 | Rust Rust Rust Rust Rust Rust 46 | Julia Julia Julia Julia Julia 47 | Classic Fortran Classic Fortran 48 | C# .NET C# .NET C# .NET C# .NET 49 | Chapel Chapel Chapel Chapel Chapel 50 | Ada 2012 GNAT Ada 2012 GNAT Ada 2012 GNAT 51 | Haskell GHC Haskell GHC Haskell GHC 52 | Free Pascal Free Pascal Free Pascal 53 | Go Go Go Go Go Go Go Go Go Go 54 | F# .NET F# .NET F# .NET F# .NET 55 | Swift Swift Swift Swift Swift 56 | Java Java Java Java Java Java 57 | Lisp SBCL Lisp SBCL Lisp SBCL 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 01 Mar 2023 u64q 224 | benchmarks game 225 | 226 | program cpu seconds / lowest 227 | 228 | How many times more CPU seconds? 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /2024/slides/assets/01-images/computerlanguagebenchmarkgame2023.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 1 25 | 3 26 | 5 27 | 10 28 | 30 29 | 50 30 | 100 31 | 300 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | C gcc C gcc C gcc C gcc C gcc 44 | C++ g++ C++ g++ C++ g++ C++ g++ 45 | Rust Rust Rust Rust Rust Rust 46 | Julia Julia Julia Julia Julia 47 | Classic Fortran Classic Fortran 48 | C# .NET C# .NET C# .NET C# .NET 49 | Chapel Chapel Chapel Chapel Chapel 50 | Ada 2012 GNAT Ada 2012 GNAT Ada 2012 GNAT 51 | Haskell GHC Haskell GHC Haskell GHC 52 | Free Pascal Free Pascal Free Pascal 53 | Go Go Go Go Go Go Go Go Go Go 54 | F# .NET F# .NET F# .NET F# .NET 55 | Swift Swift Swift Swift Swift 56 | Java Java Java Java Java Java 57 | Lisp SBCL Lisp SBCL Lisp SBCL 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 01 Mar 2023 u64q 224 | benchmarks game 225 | 226 | program cpu seconds / lowest 227 | 228 | How many times more CPU seconds? 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /slides/assets/01-images/computerlanguagebenchmarkgame.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 1 23 | 3 24 | 5 25 | 10 26 | 30 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | C++ g++ C++ g++ C++ g++ C++ g++ 39 | C gcc C gcc C gcc C gcc C gcc 40 | Rust Rust Rust Rust Rust Rust 41 | Intel Fortran Intel Fortran Intel Fortran 42 | Julia Julia Julia Julia Julia 43 | C# .NET C# .NET C# .NET C# .NET 44 | Ada 2012 GNAT Ada 2012 GNAT Ada 2012 GNAT 45 | OCaml OCaml OCaml OCaml OCaml 46 | Free Pascal Free Pascal Free Pascal 47 | F# .NET F# .NET F# .NET F# .NET 48 | Haskell GHC Haskell GHC Haskell GHC 49 | Java Java Java Java Java Java 50 | Go Go Go Go Go Go Go Go Go Go 51 | Chapel Chapel Chapel Chapel Chapel 52 | Lisp SBCL Lisp SBCL Lisp SBCL 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 01 Sep 2021 u64q 235 | benchmarks game 236 | 237 | program busy time / least busy 238 | How many times slower? 239 | 2018:2020 programs contributed + - 240 | 241 | ave 242 | +40 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /2024/slides/assets/01-images/computerlanguagebenchmarkgame.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 1 23 | 3 24 | 5 25 | 10 26 | 30 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | C++ g++ C++ g++ C++ g++ C++ g++ 39 | C gcc C gcc C gcc C gcc C gcc 40 | Rust Rust Rust Rust Rust Rust 41 | Intel Fortran Intel Fortran Intel Fortran 42 | Julia Julia Julia Julia Julia 43 | C# .NET C# .NET C# .NET C# .NET 44 | Ada 2012 GNAT Ada 2012 GNAT Ada 2012 GNAT 45 | OCaml OCaml OCaml OCaml OCaml 46 | Free Pascal Free Pascal Free Pascal 47 | F# .NET F# .NET F# .NET F# .NET 48 | Haskell GHC Haskell GHC Haskell GHC 49 | Java Java Java Java Java Java 50 | Go Go Go Go Go Go Go Go Go Go 51 | Chapel Chapel Chapel Chapel Chapel 52 | Lisp SBCL Lisp SBCL Lisp SBCL 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 01 Sep 2021 u64q 235 | benchmarks game 236 | 237 | program busy time / least busy 238 | How many times slower? 239 | 2018:2020 programs contributed + - 240 | 241 | ave 242 | +40 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | -------------------------------------------------------------------------------- /slides/14-bonus.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust Bonus 5 | theme: rust 6 | paginate: true 7 | --- 8 | ![w:512 h:512](./assets/rust-logo-1.png) 9 | # PV281: Programování v Rustu 10 | 11 | --- 12 | 13 | # Obsah 14 | 15 | 1. Deployment aplikací 16 | 2. OAuth, OpenID 17 | 3. Zero-Trust architektury 18 | 4. Makra 19 | 20 | --- 21 | 22 | # Deployment aplikací 23 | 24 | --- 25 | 26 | # Produkční build 27 | 28 | Produkční build spustíme příkazem 29 | 30 | ```bash 31 | cargo build --release 32 | ``` 33 | 34 | Pro produkční build je vhodné nastavit úroveň optimalizací v Cargo.toml. Znamená to ovšem delší kompilační čas. 35 | 36 | ```toml 37 | [profile.dev] 38 | opt-level = 0 39 | 40 | [profile.release] 41 | opt-level = 3 42 | ``` 43 | 44 | --- 45 | 46 | # Nasazení webové služby 47 | 48 | 1. Na cílový server nahrajeme binárku aplikaci. 49 | 2. Registrujeme jako službu/démona nebo jiným způsobem zařídíme spuštění po startu systému. 50 | 3. V případě potřeby nasadíme proxy. 51 | 52 | --- 53 | 54 | # Nahrátí na cílový server 55 | 56 | Záleží na možnostech, které máme. Pokud nasazujeme u cloud providera, tak využijeme jeho možností (zip package, teracopy). Pokud fungujeme na vlastním serveru, tak máme možnosti přes FTP, git apod. 57 | 58 | --- 59 | 60 | # Kontejnery 61 | 62 | Jako lepší scénář se dnes nabízí kontejnerizace. Pro menší deployment jde použít Docker Swarm. Lepší alternativou (ale vyšší učící křivkou) je Kubernetes. Pro jednoduché deploymenty existuje k3s v lokální kombinaci s nerdctl. 63 | 64 | Větší deploymenty jde realizovat ve vlastním k8s clusteru nebo využít cloud-hosted. 65 | 66 | --- 67 | 68 | # CD pipeline 69 | 70 | Ideální je řešit nasazení přes continous delivery. Oblíbené se staly Github Actions. 71 | 72 | --- 73 | 74 | # OAuth, OpenID 75 | 76 | --- 77 | 78 | # OAuth 79 | 80 | Zavádí autorizační vrstvu oddělující klienta a resource. 81 | 82 | --- 83 | 84 | # Role 85 | 86 | resource owner - uživatel, který může získat přístup 87 | resource server - má data, která jsou chráněná proti neoprávněnému přístupu 88 | klient - klientská aplikace, která získává data třeba přes REST 89 | auth server - server starající se o identitu 90 | 91 | --- 92 | 93 | # Flow 94 | 95 | 1. Klient pošle auth request na vlastníka (nebo ideálně na auth server) 96 | 2. Klient dostává grant 97 | 3. Klient posílá auth grant na auth server 98 | 4. Klient dostává access token 99 | 5. Klient posílá access token na resource 100 | 6. Klient dostává data 101 | 102 | --- 103 | 104 | # 4 druhy flow 105 | 106 | 1. autorizační kód 107 | 2. implicit 108 | 3. resource owner password credentials 109 | 4. client credentials 110 | 111 | --- 112 | 113 | # Implicit flow 114 | 115 | Zjednodušené flow určené primárně pro webový prohlížeč. Je vynechána část pro získání autorizačního kódu/grantu. Klient získává přímo access token. 116 | Pozn. neautentizuje klienta. Identita klienta může být ověřena podle return URL. 117 | 118 | --- 119 | 120 | # Access token 121 | 122 | = kredenšly pro přístup k resourcu 123 | 124 | Je to string obsahující scope a délku trvání přístupu. Je nečitelný klientem. 125 | Může být restriktivnější než grant. 126 | 127 | --- 128 | 129 | # Refresh token 130 | 131 | Optional. Slouží pro obnovení expirovaného access tokenu. 132 | Nikdy se neposílá na resource server. 133 | 134 | --- 135 | 136 | # Redirect z auth server 137 | 138 | Klient je pro získání přístupu přesměrován na auth server. Ten má pri klienta zadanou return URL (je třeba registrovat předem všechny možné). Přesměrování je řešeno primárně přes 302 Found s uri v Location header. 139 | 140 | --- 141 | 142 | # Registrace klienta 143 | 144 | = typ klienta (confidential nebo public) + redirect url + další informace 145 | 146 | --- 147 | 148 | # OpenID Connect 1.0 149 | 150 | Identita nad OAuth 2.0. OID dává autentizaci a OAuth autorizaci. 151 | Informace je vrácena jako JWT. 152 | 153 | --- 154 | 155 | # Flow 156 | 157 | 1. Relying party pošle auth request na OpenID provider 158 | 2. User provede authn a authz na provideru 159 | 3. Relying party dostává AuthN response 160 | 4. Relying party posílá user info request na OpenID Provider 161 | 5. Relying party dostává user info. 162 | 163 | --- 164 | 165 | # Request 166 | 167 | scope = openid 168 | respose_type 169 | client_id 170 | redirect_uri 171 | state 172 | 173 | --- 174 | 175 | # Zero Trust Architectures 176 | 177 | --- 178 | 179 | Korporátní sítě se dlouho spoléhali na bezpečnost na úrovni perimetru. Ve vnitřní síti nebylo často žádné zabezpečení. 180 | 181 | Komunikace uvnitř sítě byla nešifrovaná. 182 | 183 | --- 184 | 185 | # Tradiční zabezpečení infrastruktury 186 | 187 | Zabezpečení na perimetru (tj. firewall), přičemž vnitřní komunikace je považována za důvěryhodnou. 188 | Pevné IP adresy a hardware pro určité aplikace. 189 | Identita založená na IP adrese. 190 | Služby jsou provozovány na známém, očekávaném místě. 191 | Specifické bezpečnostní požadavky zabudované do každé aplikace a vynucované samostatně. 192 | Omezená omezení způsobu vytváření a kontroly služeb. 193 | Omezený dohled nad bezpečnostními složkami. 194 | Složité a občasné nasazování. 195 | Appky jsou obvykle nasazovány jako virtuální počítače nebo na fyzické hosty a k zajištění izolace používají fyzický stroj nebo hypervizor. 196 | 197 | --- 198 | 199 | # Zásady bezpečnosti v cloudovém prostředí 200 | 201 | Ochrana sítě na okraji 202 | Žádná inherentní vzájemná důvěra mezi službami 203 | Důvěryhodné stroje, na kterých běží kód se známým původem 204 | Kontrolní body pro konzistentní prosazování zásad napříč službami 205 | Jednoduché, automatizované a standardizované zavádění změn 206 | Izolace mezi workloady sdílejícími operační systém 207 | 208 | --- 209 | 210 | # Makra 211 | 212 | --- 213 | 214 | ```rust 215 | // This is a simple macro named `say_hello`. 216 | macro_rules! say_hello { 217 | // `()` indicates that the macro takes no argument. 218 | () => { 219 | // The macro will expand into the contents of this block. 220 | println!("Hello!"); 221 | }; 222 | } 223 | 224 | fn main() { 225 | // This call will expand into `println!("Hello");` 226 | say_hello!() 227 | } 228 | 229 | ``` 230 | 231 | --- 232 | 233 | # Designator 234 | 235 | ```rust 236 | macro_rules! create_function { 237 | // This macro takes an argument of designator `ident` and 238 | // creates a function named `$func_name`. 239 | // The `ident` designator is used for variable/function names. 240 | ($func_name:ident) => { 241 | fn $func_name() { 242 | // The `stringify!` macro converts an `ident` into a string. 243 | println!("You called {:?}()", 244 | stringify!($func_name)); 245 | } 246 | }; 247 | } 248 | 249 | // Create functions named `foo` and `bar` with the above macro. 250 | create_function!(foo); 251 | create_function!(bar); 252 | 253 | fn main() { 254 | foo(); 255 | bar(); 256 | } 257 | 258 | ``` 259 | 260 | --- 261 | 262 | # Designator 263 | 264 | ```rust 265 | macro_rules! print_result { 266 | // This macro takes an expression of type `expr` and prints 267 | // it as a string along with its result. 268 | // The `expr` designator is used for expressions. 269 | ($expression:expr) => { 270 | // `stringify!` will convert the expression *as it is* into a string. 271 | println!("{:?} = {:?}", 272 | stringify!($expression), 273 | $expression); 274 | }; 275 | } 276 | 277 | fn main() { 278 | print_result!(1u32 + 1); 279 | 280 | // Recall that blocks are expressions too! 281 | print_result!({ 282 | let x = 1u32; 283 | 284 | x * x + 2 * x - 1 285 | }); 286 | } 287 | 288 | ``` 289 | 290 | --- 291 | 292 | # Možnosti 293 | 294 | block 295 | expr is used for expressions 296 | ident is used for variable/function names 297 | item 298 | literal is used for literal constants 299 | pat (pattern) 300 | path 301 | stmt (statement) 302 | tt (token tree) 303 | ty (type) 304 | vis (visibility qualifier) 305 | 306 | --- 307 | 308 | # Overload 309 | 310 | ```rust 311 | // `test!` will compare `$left` and `$right` 312 | // in different ways depending on how you invoke it: 313 | macro_rules! test { 314 | // Arguments don't need to be separated by a comma. 315 | // Any template can be used! 316 | ($left:expr; and $right:expr) => { 317 | println!("{:?} and {:?} is {:?}", 318 | stringify!($left), 319 | stringify!($right), 320 | $left && $right) 321 | }; 322 | // ^ each arm must end with a semicolon. 323 | ($left:expr; or $right:expr) => { 324 | println!("{:?} or {:?} is {:?}", 325 | stringify!($left), 326 | stringify!($right), 327 | $left || $right) 328 | }; 329 | } 330 | 331 | fn main() { 332 | test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32); 333 | test!(true; or false); 334 | } 335 | ``` 336 | 337 | --- 338 | 339 | # Repeat 340 | 341 | ```rust 342 | // `find_min!` will calculate the minimum of any number of arguments. 343 | macro_rules! find_min { 344 | // Base case: 345 | ($x:expr) => ($x); 346 | // `$x` followed by at least one `$y,` 347 | ($x:expr, $($y:expr),+) => ( 348 | // Call `find_min!` on the tail `$y` 349 | std::cmp::min($x, find_min!($($y),+)) 350 | ) 351 | } 352 | 353 | fn main() { 354 | println!("{}", find_min!(1u32)); 355 | println!("{}", find_min!(1u32 + 2, 2u32)); 356 | println!("{}", find_min!(5u32, 2u32 * 3, 4u32)); 357 | } 358 | 359 | ``` 360 | 361 | --- 362 | 363 | # Variadická rozhraní 364 | 365 | ```rust 366 | macro_rules! calculate { 367 | // The pattern for a single `eval` 368 | (eval $e:expr) => {{ 369 | { 370 | let val: usize = $e; // Force types to be integers 371 | println!("{} = {}", stringify!{$e}, val); 372 | } 373 | }}; 374 | 375 | // Decompose multiple `eval`s recursively 376 | (eval $e:expr, $(eval $es:expr),+) => {{ 377 | calculate! { eval $e } 378 | calculate! { $(eval $es),+ } 379 | }}; 380 | } 381 | 382 | fn main() { 383 | calculate! { // Look ma! Variadic `calculate!`! 384 | eval 1 + 2, 385 | eval 3 + 4, 386 | eval (2 * 3) + 1 387 | } 388 | } 389 | ``` 390 | 391 | --- 392 | 393 | # Procedurální makra 394 | 395 | 1. funkční 396 | 2. derive 397 | 3. atributová 398 | 399 | --- 400 | 401 | # Funkční 402 | 403 | ```rust 404 | extern crate proc_macro; 405 | use proc_macro::TokenStream; 406 | 407 | #[proc_macro] 408 | pub fn make_answer(_item: TokenStream) -> TokenStream { 409 | "fn answer() -> u32 { 42 }".parse().unwrap() 410 | } 411 | ``` 412 | 413 | --- 414 | 415 | # Funkční 416 | 417 | ```rust 418 | extern crate proc_macro_examples; 419 | use proc_macro_examples::make_answer; 420 | 421 | make_answer!(); 422 | 423 | fn main() { 424 | println!("{}", answer()); 425 | } 426 | ``` 427 | 428 | --- 429 | 430 | # Derive 431 | 432 | ```rust 433 | extern crate proc_macro; 434 | use proc_macro::TokenStream; 435 | 436 | #[proc_macro_derive(AnswerFn)] 437 | pub fn derive_answer_fn(_item: TokenStream) -> TokenStream { 438 | "fn answer() -> u32 { 42 }".parse().unwrap() 439 | } 440 | ``` 441 | 442 | --- 443 | 444 | # Derive 445 | 446 | ```rust 447 | extern crate proc_macro_examples; 448 | use proc_macro_examples::AnswerFn; 449 | 450 | #[derive(AnswerFn)] 451 | struct Struct; 452 | 453 | fn main() { 454 | assert_eq!(42, answer()); 455 | } 456 | ``` 457 | 458 | --- 459 | 460 | # Derive trait 461 | 462 | ```rust 463 | #[proc_macro_derive(Trait)] 464 | pub fn derive_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 465 | let input = parse_macro_input!(input as DeriveInput); 466 | 467 | let name = input.ident; 468 | 469 | let expanded = quote! { 470 | impl Trait for #name { 471 | fn print(&self) -> usize { 472 | println!("{}","hello from #name") 473 | } 474 | } 475 | }; 476 | 477 | proc_macro::TokenStream::from(expanded) 478 | } 479 | ``` 480 | 481 | --- 482 | 483 | # Atribut 484 | 485 | ```rust 486 | // my-macro/src/lib.rs 487 | 488 | #[proc_macro_attribute] 489 | pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream { 490 | println!("attr: \"{}\"", attr.to_string()); 491 | println!("item: \"{}\"", item.to_string()); 492 | item 493 | } 494 | ``` 495 | 496 | --- 497 | 498 | # Atribut 499 | 500 | ```rust 501 | // src/lib.rs 502 | extern crate my_macro; 503 | 504 | use my_macro::show_streams; 505 | 506 | // Example: Basic function 507 | #[show_streams] 508 | fn invoke1() {} 509 | // out: attr: "" 510 | // out: item: "fn invoke1() { }" 511 | 512 | // Example: Attribute with input 513 | #[show_streams(bar)] 514 | fn invoke2() {} 515 | // out: attr: "bar" 516 | // out: item: "fn invoke2() {}" 517 | 518 | ``` 519 | 520 | --- 521 | 522 | # Dotazy? 523 | 524 | --- 525 | 526 | # Děkuji za pozornost 527 | 528 | -------------------------------------------------------------------------------- /2024/slides/14-bonus.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust Bonus 5 | theme: rust 6 | paginate: true 7 | --- 8 | ![w:512 h:512](./assets/rust-logo-1.png) 9 | # PV281: Programování v Rustu 10 | 11 | --- 12 | 13 | # Obsah 14 | 15 | 1. Deployment aplikací 16 | 2. OAuth, OpenID 17 | 3. Zero-Trust architektury 18 | 4. Makra 19 | 20 | --- 21 | 22 | # Deployment aplikací 23 | 24 | --- 25 | 26 | # Produkční build 27 | 28 | Produkční build spustíme příkazem 29 | 30 | ```bash 31 | cargo build --release 32 | ``` 33 | 34 | Pro produkční build je vhodné nastavit úroveň optimalizací v Cargo.toml. Znamená to ovšem delší kompilační čas. 35 | 36 | ```toml 37 | [profile.dev] 38 | opt-level = 0 39 | 40 | [profile.release] 41 | opt-level = 3 42 | ``` 43 | 44 | --- 45 | 46 | # Nasazení webové služby 47 | 48 | 1. Na cílový server nahrajeme binárku aplikaci. 49 | 2. Registrujeme jako službu/démona nebo jiným způsobem zařídíme spuštění po startu systému. 50 | 3. V případě potřeby nasadíme proxy. 51 | 52 | --- 53 | 54 | # Nahrátí na cílový server 55 | 56 | Záleží na možnostech, které máme. Pokud nasazujeme u cloud providera, tak využijeme jeho možností (zip package, teracopy). Pokud fungujeme na vlastním serveru, tak máme možnosti přes FTP, git apod. 57 | 58 | --- 59 | 60 | # Kontejnery 61 | 62 | Jako lepší scénář se dnes nabízí kontejnerizace. Pro menší deployment jde použít Docker Swarm. Lepší alternativou (ale vyšší učící křivkou) je Kubernetes. Pro jednoduché deploymenty existuje k3s v lokální kombinaci s nerdctl. 63 | 64 | Větší deploymenty jde realizovat ve vlastním k8s clusteru nebo využít cloud-hosted. 65 | 66 | --- 67 | 68 | # CD pipeline 69 | 70 | Ideální je řešit nasazení přes continous delivery. Oblíbené se staly Github Actions. 71 | 72 | --- 73 | 74 | # OAuth, OpenID 75 | 76 | --- 77 | 78 | # OAuth 79 | 80 | Zavádí autorizační vrstvu oddělující klienta a resource. 81 | 82 | --- 83 | 84 | # Role 85 | 86 | resource owner - uživatel, který může získat přístup 87 | resource server - má data, která jsou chráněná proti neoprávněnému přístupu 88 | klient - klientská aplikace, která získává data třeba přes REST 89 | auth server - server starající se o identitu 90 | 91 | --- 92 | 93 | # Flow 94 | 95 | 1. Klient pošle auth request na vlastníka (nebo ideálně na auth server) 96 | 2. Klient dostává grant 97 | 3. Klient posílá auth grant na auth server 98 | 4. Klient dostává access token 99 | 5. Klient posílá access token na resource 100 | 6. Klient dostává data 101 | 102 | --- 103 | 104 | # 4 druhy flow 105 | 106 | 1. autorizační kód 107 | 2. implicit 108 | 3. resource owner password credentials 109 | 4. client credentials 110 | 111 | --- 112 | 113 | # Implicit flow 114 | 115 | Zjednodušené flow určené primárně pro webový prohlížeč. Je vynechána část pro získání autorizačního kódu/grantu. Klient získává přímo access token. 116 | Pozn. neautentizuje klienta. Identita klienta může být ověřena podle return URL. 117 | 118 | --- 119 | 120 | # Access token 121 | 122 | = kredenšly pro přístup k resourcu 123 | 124 | Je to string obsahující scope a délku trvání přístupu. Je nečitelný klientem. 125 | Může být restriktivnější než grant. 126 | 127 | --- 128 | 129 | # Refresh token 130 | 131 | Optional. Slouží pro obnovení expirovaného access tokenu. 132 | Nikdy se neposílá na resource server. 133 | 134 | --- 135 | 136 | # Redirect z auth server 137 | 138 | Klient je pro získání přístupu přesměrován na auth server. Ten má pri klienta zadanou return URL (je třeba registrovat předem všechny možné). Přesměrování je řešeno primárně přes 302 Found s uri v Location header. 139 | 140 | --- 141 | 142 | # Registrace klienta 143 | 144 | = typ klienta (confidential nebo public) + redirect url + další informace 145 | 146 | --- 147 | 148 | # OpenID Connect 1.0 149 | 150 | Identita nad OAuth 2.0. OID dává autentizaci a OAuth autorizaci. 151 | Informace je vrácena jako JWT. 152 | 153 | --- 154 | 155 | # Flow 156 | 157 | 1. Relying party pošle auth request na OpenID provider 158 | 2. User provede authn a authz na provideru 159 | 3. Relying party dostává AuthN response 160 | 4. Relying party posílá user info request na OpenID Provider 161 | 5. Relying party dostává user info. 162 | 163 | --- 164 | 165 | # Request 166 | 167 | scope = openid 168 | respose_type 169 | client_id 170 | redirect_uri 171 | state 172 | 173 | --- 174 | 175 | # Zero Trust Architectures 176 | 177 | --- 178 | 179 | Korporátní sítě se dlouho spoléhali na bezpečnost na úrovni perimetru. Ve vnitřní síti nebylo často žádné zabezpečení. 180 | 181 | Komunikace uvnitř sítě byla nešifrovaná. 182 | 183 | --- 184 | 185 | # Tradiční zabezpečení infrastruktury 186 | 187 | Zabezpečení na perimetru (tj. firewall), přičemž vnitřní komunikace je považována za důvěryhodnou. 188 | Pevné IP adresy a hardware pro určité aplikace. 189 | Identita založená na IP adrese. 190 | Služby jsou provozovány na známém, očekávaném místě. 191 | Specifické bezpečnostní požadavky zabudované do každé aplikace a vynucované samostatně. 192 | Omezená omezení způsobu vytváření a kontroly služeb. 193 | Omezený dohled nad bezpečnostními složkami. 194 | Složité a občasné nasazování. 195 | Appky jsou obvykle nasazovány jako virtuální počítače nebo na fyzické hosty a k zajištění izolace používají fyzický stroj nebo hypervizor. 196 | 197 | --- 198 | 199 | # Zásady bezpečnosti v cloudovém prostředí 200 | 201 | Ochrana sítě na okraji 202 | Žádná inherentní vzájemná důvěra mezi službami 203 | Důvěryhodné stroje, na kterých běží kód se známým původem 204 | Kontrolní body pro konzistentní prosazování zásad napříč službami 205 | Jednoduché, automatizované a standardizované zavádění změn 206 | Izolace mezi workloady sdílejícími operační systém 207 | 208 | --- 209 | 210 | # Makra 211 | 212 | --- 213 | 214 | ```rust 215 | // This is a simple macro named `say_hello`. 216 | macro_rules! say_hello { 217 | // `()` indicates that the macro takes no argument. 218 | () => { 219 | // The macro will expand into the contents of this block. 220 | println!("Hello!"); 221 | }; 222 | } 223 | 224 | fn main() { 225 | // This call will expand into `println!("Hello");` 226 | say_hello!() 227 | } 228 | 229 | ``` 230 | 231 | --- 232 | 233 | # Designator 234 | 235 | ```rust 236 | macro_rules! create_function { 237 | // This macro takes an argument of designator `ident` and 238 | // creates a function named `$func_name`. 239 | // The `ident` designator is used for variable/function names. 240 | ($func_name:ident) => { 241 | fn $func_name() { 242 | // The `stringify!` macro converts an `ident` into a string. 243 | println!("You called {:?}()", 244 | stringify!($func_name)); 245 | } 246 | }; 247 | } 248 | 249 | // Create functions named `foo` and `bar` with the above macro. 250 | create_function!(foo); 251 | create_function!(bar); 252 | 253 | fn main() { 254 | foo(); 255 | bar(); 256 | } 257 | 258 | ``` 259 | 260 | --- 261 | 262 | # Designator 263 | 264 | ```rust 265 | macro_rules! print_result { 266 | // This macro takes an expression of type `expr` and prints 267 | // it as a string along with its result. 268 | // The `expr` designator is used for expressions. 269 | ($expression:expr) => { 270 | // `stringify!` will convert the expression *as it is* into a string. 271 | println!("{:?} = {:?}", 272 | stringify!($expression), 273 | $expression); 274 | }; 275 | } 276 | 277 | fn main() { 278 | print_result!(1u32 + 1); 279 | 280 | // Recall that blocks are expressions too! 281 | print_result!({ 282 | let x = 1u32; 283 | 284 | x * x + 2 * x - 1 285 | }); 286 | } 287 | 288 | ``` 289 | 290 | --- 291 | 292 | # Možnosti 293 | 294 | block 295 | expr is used for expressions 296 | ident is used for variable/function names 297 | item 298 | literal is used for literal constants 299 | pat (pattern) 300 | path 301 | stmt (statement) 302 | tt (token tree) 303 | ty (type) 304 | vis (visibility qualifier) 305 | 306 | --- 307 | 308 | # Overload 309 | 310 | ```rust 311 | // `test!` will compare `$left` and `$right` 312 | // in different ways depending on how you invoke it: 313 | macro_rules! test { 314 | // Arguments don't need to be separated by a comma. 315 | // Any template can be used! 316 | ($left:expr; and $right:expr) => { 317 | println!("{:?} and {:?} is {:?}", 318 | stringify!($left), 319 | stringify!($right), 320 | $left && $right) 321 | }; 322 | // ^ each arm must end with a semicolon. 323 | ($left:expr; or $right:expr) => { 324 | println!("{:?} or {:?} is {:?}", 325 | stringify!($left), 326 | stringify!($right), 327 | $left || $right) 328 | }; 329 | } 330 | 331 | fn main() { 332 | test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32); 333 | test!(true; or false); 334 | } 335 | ``` 336 | 337 | --- 338 | 339 | # Repeat 340 | 341 | ```rust 342 | // `find_min!` will calculate the minimum of any number of arguments. 343 | macro_rules! find_min { 344 | // Base case: 345 | ($x:expr) => ($x); 346 | // `$x` followed by at least one `$y,` 347 | ($x:expr, $($y:expr),+) => ( 348 | // Call `find_min!` on the tail `$y` 349 | std::cmp::min($x, find_min!($($y),+)) 350 | ) 351 | } 352 | 353 | fn main() { 354 | println!("{}", find_min!(1u32)); 355 | println!("{}", find_min!(1u32 + 2, 2u32)); 356 | println!("{}", find_min!(5u32, 2u32 * 3, 4u32)); 357 | } 358 | 359 | ``` 360 | 361 | --- 362 | 363 | # Variadická rozhraní 364 | 365 | ```rust 366 | macro_rules! calculate { 367 | // The pattern for a single `eval` 368 | (eval $e:expr) => {{ 369 | { 370 | let val: usize = $e; // Force types to be integers 371 | println!("{} = {}", stringify!{$e}, val); 372 | } 373 | }}; 374 | 375 | // Decompose multiple `eval`s recursively 376 | (eval $e:expr, $(eval $es:expr),+) => {{ 377 | calculate! { eval $e } 378 | calculate! { $(eval $es),+ } 379 | }}; 380 | } 381 | 382 | fn main() { 383 | calculate! { // Look ma! Variadic `calculate!`! 384 | eval 1 + 2, 385 | eval 3 + 4, 386 | eval (2 * 3) + 1 387 | } 388 | } 389 | ``` 390 | 391 | --- 392 | 393 | # Procedurální makra 394 | 395 | 1. funkční 396 | 2. derive 397 | 3. atributová 398 | 399 | --- 400 | 401 | # Funkční 402 | 403 | ```rust 404 | extern crate proc_macro; 405 | use proc_macro::TokenStream; 406 | 407 | #[proc_macro] 408 | pub fn make_answer(_item: TokenStream) -> TokenStream { 409 | "fn answer() -> u32 { 42 }".parse().unwrap() 410 | } 411 | ``` 412 | 413 | --- 414 | 415 | # Funkční 416 | 417 | ```rust 418 | extern crate proc_macro_examples; 419 | use proc_macro_examples::make_answer; 420 | 421 | make_answer!(); 422 | 423 | fn main() { 424 | println!("{}", answer()); 425 | } 426 | ``` 427 | 428 | --- 429 | 430 | # Derive 431 | 432 | ```rust 433 | extern crate proc_macro; 434 | use proc_macro::TokenStream; 435 | 436 | #[proc_macro_derive(AnswerFn)] 437 | pub fn derive_answer_fn(_item: TokenStream) -> TokenStream { 438 | "fn answer() -> u32 { 42 }".parse().unwrap() 439 | } 440 | ``` 441 | 442 | --- 443 | 444 | # Derive 445 | 446 | ```rust 447 | extern crate proc_macro_examples; 448 | use proc_macro_examples::AnswerFn; 449 | 450 | #[derive(AnswerFn)] 451 | struct Struct; 452 | 453 | fn main() { 454 | assert_eq!(42, answer()); 455 | } 456 | ``` 457 | 458 | --- 459 | 460 | # Derive trait 461 | 462 | ```rust 463 | #[proc_macro_derive(Trait)] 464 | pub fn derive_trait(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 465 | let input = parse_macro_input!(input as DeriveInput); 466 | 467 | let name = input.ident; 468 | 469 | let expanded = quote! { 470 | impl Trait for #name { 471 | fn print(&self) -> usize { 472 | println!("{}","hello from #name") 473 | } 474 | } 475 | }; 476 | 477 | proc_macro::TokenStream::from(expanded) 478 | } 479 | ``` 480 | 481 | --- 482 | 483 | # Atribut 484 | 485 | ```rust 486 | // my-macro/src/lib.rs 487 | 488 | #[proc_macro_attribute] 489 | pub fn show_streams(attr: TokenStream, item: TokenStream) -> TokenStream { 490 | println!("attr: \"{}\"", attr.to_string()); 491 | println!("item: \"{}\"", item.to_string()); 492 | item 493 | } 494 | ``` 495 | 496 | --- 497 | 498 | # Atribut 499 | 500 | ```rust 501 | // src/lib.rs 502 | extern crate my_macro; 503 | 504 | use my_macro::show_streams; 505 | 506 | // Example: Basic function 507 | #[show_streams] 508 | fn invoke1() {} 509 | // out: attr: "" 510 | // out: item: "fn invoke1() { }" 511 | 512 | // Example: Attribute with input 513 | #[show_streams(bar)] 514 | fn invoke2() {} 515 | // out: attr: "bar" 516 | // out: item: "fn invoke2() {}" 517 | 518 | ``` 519 | 520 | --- 521 | 522 | # Dotazy? 523 | 524 | --- 525 | 526 | # Děkuji za pozornost 527 | 528 | -------------------------------------------------------------------------------- /slides/13-graphql.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust GraphQL 5 | theme: rust 6 | paginate: true 7 | --- 8 | ![w:512 h:512](./assets/rust-logo-1.png) 9 | # PV281: Programování v Rustu 10 | 11 | --- 12 | 13 | # Obsah 14 | 15 | 1. Úvod do GraphQL 16 | 2. Dotazy 17 | 3. Mutace 18 | 4. Architektury s GraphQL 19 | 5. Juniper 20 | 6. Integrace do Actix 21 | 22 | --- 23 | 24 | # GraphQL 25 | 26 | --- 27 | 28 | # Field 29 | 30 | Field je jedna properta objektu. Z důvodu ušetření velikosti musíme vyjmenovat všechny fieldy, které chceme získat. 31 | 32 | ```graphql 33 | { 34 | hero { 35 | name 36 | } 37 | } 38 | ``` 39 | 40 | --- 41 | 42 | # Argumenty 43 | 44 | Objekty můžou mít argumenty, které zužují výběr. Mohou být různého typu nebo i výčtu. 45 | 46 | ```graphql 47 | { 48 | human(id: "1000") { 49 | name 50 | height(unit: FOOT) 51 | } 52 | } 53 | ``` 54 | 55 | --- 56 | 57 | # Alias 58 | 59 | V případě, kdy potřebujeme získat jeden objekt s různými filtry je vhodné upravit pojmenování pomocí aliasu. 60 | 61 | ```graphql 62 | { 63 | empireHero: hero(episode: EMPIRE) { 64 | name 65 | } 66 | jediHero: hero(episode: JEDI) { 67 | name 68 | } 69 | } 70 | ``` 71 | 72 | --- 73 | 74 | # Fragment 75 | 76 | Pro opakující se položky vyžijeme fragment. 77 | 78 | ```graphql 79 | { 80 | leftComparison: hero(episode: EMPIRE) { 81 | ...comparisonFields 82 | } 83 | rightComparison: hero(episode: JEDI) { 84 | ...comparisonFields 85 | } 86 | } 87 | 88 | fragment comparisonFields on Character { 89 | name 90 | appearsIn 91 | friends { 92 | name 93 | } 94 | } 95 | ``` 96 | 97 | --- 98 | 99 | # Struktura dotazu 100 | 101 | Dotaz je pojmenovaný. Jednotlivé proměnné jsou v dotazu otypovány a samotné hodnoty předány v samostatném objektu mimo dotaz. 102 | 103 | ```grapqhl 104 | query HeroNameAndFriends($episode: Episode) { 105 | hero(episode: $episode) { 106 | name 107 | friends { 108 | name 109 | } 110 | } 111 | } 112 | 113 | { 114 | "episode": "JEDI" 115 | } 116 | ``` 117 | 118 | --- 119 | 120 | # Výchozí hodnoty 121 | 122 | V případě kdy hodnota proměnné zadaná, může být doplněna výchozí hodnotou. 123 | 124 | ```graphql 125 | query HeroNameAndFriends($episode: Episode = JEDI) { 126 | hero(episode: $episode) { 127 | name 128 | friends { 129 | name 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | --- 136 | 137 | # Direktivy 138 | 139 | ```graphql 140 | query Hero($episode: Episode, $withFriends: Boolean!) { 141 | hero(episode: $episode) { 142 | name 143 | friends @include(if: $withFriends) { 144 | name 145 | } 146 | } 147 | } 148 | 149 | { 150 | "episode": "JEDI", 151 | "withFriends": false 152 | } 153 | ``` 154 | 155 | --- 156 | 157 | # Mutace 158 | 159 | Mutace slouží k modifikaci položek, vytvoření, mazání apod. 160 | 161 | ```graphql 162 | mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { 163 | createReview(episode: $ep, review: $review) { 164 | stars 165 | commentary 166 | } 167 | } 168 | 169 | { 170 | "ep": "JEDI", 171 | "review": { 172 | "stars": 5, 173 | "commentary": "This is a great movie!" 174 | } 175 | } 176 | ``` 177 | 178 | --- 179 | 180 | # Architektury s GQL 181 | 182 | --- 183 | 184 | # GraphQL nad REST 185 | 186 | @startuml 187 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml 188 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml 189 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml 190 | 191 | Container(fe, "Frontend", "") 192 | 193 | Container(gql, "Vrstva GraphQL", "Rust, Juniper") 194 | 195 | Container(s1, "REST služba 1", "Rust, Actix") 196 | Container(s2, "REST služba 2", "Node.js, Express") 197 | Container(s3, "REST služba 3", "C#, ASP.NET") 198 | 199 | Container(db1, "SQL DB 1", "Postgres") 200 | Container(db2, "SQL DB 2", "Postgres") 201 | Container(db3, "noSQL DB", "Mongo") 202 | 203 | Rel_D(fe, gql, "") 204 | 205 | Rel_D(gql, s1, "") 206 | Rel_D(gql, s2, "") 207 | Rel_D(gql, s3, "") 208 | 209 | Rel_D(s1, db1, "") 210 | Rel_D(s2, db2, "") 211 | Rel_D(s3, db3, "") 212 | 213 | @enduml 214 | 215 | --- 216 | 217 | # GRPC backend 218 | 219 | @startuml 220 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml 221 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml 222 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml 223 | 224 | Container(fe, "Frontend", "") 225 | 226 | Container(gql, "Vrstva GraphQL", "Rust, Juniper") 227 | 228 | Container(s1, "GRPC služba 1", "Rust, Tonic") 229 | Container(s2, "GRPC služba 2", "C#") 230 | Container(s3, "REST služba 3", "C#, ASP.NET") 231 | 232 | Container(db1, "SQL DB 1", "Postgres") 233 | Container(db2, "SQL DB 2", "Postgres") 234 | Container(db3, "SQL DB 3", "MS SQL") 235 | 236 | Rel_D(fe, gql, "") 237 | 238 | Rel_D(gql, s1, "") 239 | Rel_D(gql, s2, "") 240 | Rel_D(gql, s3, "") 241 | 242 | Rel_D(s1, db1, "") 243 | Rel_D(s2, db2, "") 244 | Rel_D(s3, db3, "") 245 | 246 | @enduml 247 | 248 | --- 249 | 250 | # GraphQL nad databází 251 | 252 | @startuml 253 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml 254 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml 255 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml 256 | 257 | Container(fe, "Frontend", "") 258 | 259 | Container(gql, "GraphQL", "Rust, Juniper") 260 | 261 | Container(db1, "SQL DB 1", "Postgres") 262 | 263 | Rel_D(fe, gql, "") 264 | 265 | Rel_D(gql, db1, "") 266 | 267 | @enduml 268 | 269 | --- 270 | 271 | # Juniper 272 | 273 | --- 274 | 275 | # Závislosti 276 | 277 | ```toml 278 | [dependencies] 279 | juniper = "0.14.2" 280 | ``` 281 | 282 | --- 283 | 284 | # Integrace s Actix 285 | 286 | ```rust 287 | use std::io; 288 | use std::sync::Arc; 289 | 290 | use actix_cors::Cors; 291 | use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; 292 | use juniper::http::graphiql::graphiql_source; 293 | use juniper::http::GraphQLRequest; 294 | 295 | use crate::schema::{create_schema, Schema}; 296 | ``` 297 | 298 | --- 299 | 300 | # Integrace s Actix 301 | 302 | ```rust 303 | #[actix_web::main] 304 | async fn main() -> io::Result<()> { 305 | std::env::set_var("RUST_LOG", "actix_web=info"); 306 | env_logger::init(); 307 | 308 | // Create Juniper schema 309 | let schema = std::sync::Arc::new(create_schema()); 310 | 311 | // Start http server 312 | HttpServer::new(move || { 313 | App::new() 314 | .data(schema.clone()) 315 | .wrap(middleware::Logger::default()) 316 | .wrap( 317 | Cors::new() 318 | .allowed_methods(vec!["POST", "GET"]) 319 | .supports_credentials() 320 | .max_age(3600) 321 | .finish(), 322 | ) 323 | .service(web::resource("/graphql").route(web::post().to(graphql))) 324 | .service(web::resource("/graphiql").route(web::get().to(graphiql))) 325 | }) 326 | .bind("127.0.0.1:8080")? 327 | .run() 328 | .await 329 | } 330 | ``` 331 | 332 | --- 333 | 334 | # Registrace endpointů 335 | 336 | ```rust 337 | async fn graphiql() -> HttpResponse { 338 | let html = graphiql_source("http://127.0.0.1:8080/graphql"); 339 | HttpResponse::Ok() 340 | .content_type("text/html; charset=utf-8") 341 | .body(html) 342 | } 343 | 344 | async fn graphql( 345 | st: web::Data>, 346 | data: web::Json, 347 | ) -> Result { 348 | let user = web::block(move || { 349 | let res = data.execute(&st, &()); 350 | Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?) 351 | }) 352 | .await?; 353 | Ok(HttpResponse::Ok() 354 | .content_type("application/json") 355 | .body(user)) 356 | } 357 | ``` 358 | 359 | --- 360 | 361 | # Schéma - výčet 362 | 363 | ```rust 364 | use juniper::FieldResult; 365 | use juniper::RootNode; 366 | use juniper::{GraphQLEnum, GraphQLInputObject, GraphQLObject}; 367 | 368 | #[derive(GraphQLEnum)] 369 | enum Episode { 370 | NewHope, 371 | Empire, 372 | Jedi, 373 | } 374 | 375 | ``` 376 | 377 | --- 378 | 379 | # Schéma - struktura 380 | 381 | ```rust 382 | use juniper::FieldResult; 383 | use juniper::RootNode; 384 | use juniper::{GraphQLEnum, GraphQLInputObject, GraphQLObject}; 385 | 386 | #[derive(GraphQLObject)] 387 | #[graphql(description = "A humanoid creature in the Star Wars universe")] 388 | struct Human { 389 | id: String, 390 | name: String, 391 | appears_in: Vec, 392 | home_planet: String, 393 | } 394 | 395 | ``` 396 | 397 | --- 398 | 399 | # Schéma - struktura pro mutaci 400 | 401 | ```rust 402 | use juniper::FieldResult; 403 | use juniper::RootNode; 404 | use juniper::{GraphQLEnum, GraphQLInputObject, GraphQLObject}; 405 | 406 | #[derive(GraphQLInputObject)] 407 | #[graphql(description = "A humanoid creature in the Star Wars universe")] 408 | struct NewHuman { 409 | name: String, 410 | appears_in: Vec, 411 | home_planet: String, 412 | } 413 | 414 | ``` 415 | 416 | --- 417 | 418 | # Kořen pro dotazy 419 | 420 | ```rust 421 | pub struct QueryRoot; 422 | 423 | #[juniper::object] 424 | impl QueryRoot { 425 | fn human(id: String) -> FieldResult { 426 | Ok(Human { 427 | id: "1234".to_owned(), 428 | name: "Luke".to_owned(), 429 | appears_in: vec![Episode::NewHope], 430 | home_planet: "Mars".to_owned(), 431 | }) 432 | } 433 | } 434 | ``` 435 | 436 | --- 437 | 438 | # Kořen pro mutace 439 | 440 | ```rust 441 | pub struct MutationRoot; 442 | 443 | #[juniper::object] 444 | impl MutationRoot { 445 | fn create_human(new_human: NewHuman) -> FieldResult { 446 | Ok(Human { 447 | id: "1234".to_owned(), 448 | name: new_human.name, 449 | appears_in: new_human.appears_in, 450 | home_planet: new_human.home_planet, 451 | }) 452 | } 453 | } 454 | ``` 455 | 456 | --- 457 | 458 | # Vytvoření schéma z kořenů 459 | 460 | ```rust 461 | pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; 462 | 463 | pub fn create_schema() -> Schema { 464 | Schema::new(QueryRoot {}, MutationRoot {}) 465 | } 466 | ``` 467 | 468 | --- 469 | 470 | # Kontext 471 | 472 | ```rust 473 | pub struct Context { 474 | pub dbpool: Pool, 475 | } 476 | 477 | impl juniper::Context for Context {} 478 | 479 | pub struct QueryRoot; 480 | 481 | #[juniper::graphql_object(Context = Context)] 482 | impl QueryRoot { 483 | #[graphql(description = "List of all users")] 484 | fn users(context: &Context) -> FieldResult> { 485 | let mut conn = context.dbpool.get().unwrap(); 486 | let users = conn 487 | .prep_exec("select * from user", ()) 488 | .map(|result| { 489 | result 490 | .map(|x| x.unwrap()) 491 | .map(|row| { 492 | let (id, name, email) = from_row(row); 493 | User { id, name, email } 494 | }) 495 | .collect() 496 | }) 497 | .unwrap(); 498 | Ok(users) 499 | } 500 | } 501 | 502 | ``` 503 | 504 | --- 505 | 506 | # Naplnění kontextu z Actixu 507 | 508 | ```rust 509 | pub async fn graphql( 510 | pool: web::Data, 511 | schema: web::Data, 512 | data: web::Json, 513 | ) -> Result { 514 | let ctx = Context { 515 | dbpool: pool.get_ref().to_owned(), 516 | }; 517 | 518 | let res = web::block(move || { 519 | let res = data.execute_sync(&schema, &ctx); 520 | serde_json::to_string(&res) 521 | }) 522 | .await 523 | .map_err(Error::from)?; 524 | 525 | Ok(HttpResponse::Ok() 526 | .content_type("application/json") 527 | .body(res)) 528 | } 529 | 530 | ``` 531 | 532 | --- 533 | 534 | # Playground 535 | 536 | ```rust 537 | use juniper::http::graphiql::graphiql_source; 538 | 539 | pub async fn graphql_playground() -> HttpResponse { 540 | HttpResponse::Ok() 541 | .content_type("text/html; charset=utf-8") 542 | .body(graphiql_source("/graphql", None)) 543 | } 544 | 545 | // register to be called in actix builder 546 | pub fn register(config: &mut web::ServiceConfig) { 547 | config 548 | .data(create_schema()) 549 | .route("/graphql", web::post().to(graphql)) 550 | .route("/graphiql", web::get().to(graphql_playground)); 551 | } 552 | ``` 553 | 554 | --- 555 | 556 | # Like this 557 | 558 | ```rust 559 | #[actix_web::main] 560 | async fn main() -> std::io::Result<()> { 561 | dotenv::dotenv().ok(); 562 | std::env::set_var("RUST_LOG", "actix_web=info,info"); 563 | env_logger::init(); 564 | let pool = get_db_pool(); 565 | 566 | HttpServer::new(move || { 567 | App::new() 568 | .data(pool.clone()) 569 | .wrap(middleware::Logger::default()) 570 | .configure(register) 571 | .default_service(web::to(|| async { "404" })) 572 | }) 573 | .bind("127.0.0.1:8080")? 574 | .run() 575 | .await 576 | } 577 | ``` 578 | 579 | --- 580 | 581 | # Dotazy? 582 | 583 | --- 584 | 585 | # Děkuji za pozornost 586 | 587 | -------------------------------------------------------------------------------- /2024/slides/13-graphql.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust GraphQL 5 | theme: rust 6 | paginate: true 7 | --- 8 | ![w:512 h:512](./assets/rust-logo-1.png) 9 | # PV281: Programování v Rustu 10 | 11 | --- 12 | 13 | # Obsah 14 | 15 | 1. Úvod do GraphQL 16 | 2. Dotazy 17 | 3. Mutace 18 | 4. Architektury s GraphQL 19 | 5. Juniper 20 | 6. Integrace do Actix 21 | 22 | --- 23 | 24 | # GraphQL 25 | 26 | --- 27 | 28 | # Field 29 | 30 | Field je jedna properta objektu. Z důvodu ušetření velikosti musíme vyjmenovat všechny fieldy, které chceme získat. 31 | 32 | ```graphql 33 | { 34 | hero { 35 | name 36 | } 37 | } 38 | ``` 39 | 40 | --- 41 | 42 | # Argumenty 43 | 44 | Objekty můžou mít argumenty, které zužují výběr. Mohou být různého typu nebo i výčtu. 45 | 46 | ```graphql 47 | { 48 | human(id: "1000") { 49 | name 50 | height(unit: FOOT) 51 | } 52 | } 53 | ``` 54 | 55 | --- 56 | 57 | # Alias 58 | 59 | V případě, kdy potřebujeme získat jeden objekt s různými filtry je vhodné upravit pojmenování pomocí aliasu. 60 | 61 | ```graphql 62 | { 63 | empireHero: hero(episode: EMPIRE) { 64 | name 65 | } 66 | jediHero: hero(episode: JEDI) { 67 | name 68 | } 69 | } 70 | ``` 71 | 72 | --- 73 | 74 | # Fragment 75 | 76 | Pro opakující se položky vyžijeme fragment. 77 | 78 | ```graphql 79 | { 80 | leftComparison: hero(episode: EMPIRE) { 81 | ...comparisonFields 82 | } 83 | rightComparison: hero(episode: JEDI) { 84 | ...comparisonFields 85 | } 86 | } 87 | 88 | fragment comparisonFields on Character { 89 | name 90 | appearsIn 91 | friends { 92 | name 93 | } 94 | } 95 | ``` 96 | 97 | --- 98 | 99 | # Struktura dotazu 100 | 101 | Dotaz je pojmenovaný. Jednotlivé proměnné jsou v dotazu otypovány a samotné hodnoty předány v samostatném objektu mimo dotaz. 102 | 103 | ```grapqhl 104 | query HeroNameAndFriends($episode: Episode) { 105 | hero(episode: $episode) { 106 | name 107 | friends { 108 | name 109 | } 110 | } 111 | } 112 | 113 | { 114 | "episode": "JEDI" 115 | } 116 | ``` 117 | 118 | --- 119 | 120 | # Výchozí hodnoty 121 | 122 | V případě kdy hodnota proměnné zadaná, může být doplněna výchozí hodnotou. 123 | 124 | ```graphql 125 | query HeroNameAndFriends($episode: Episode = JEDI) { 126 | hero(episode: $episode) { 127 | name 128 | friends { 129 | name 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | --- 136 | 137 | # Direktivy 138 | 139 | ```graphql 140 | query Hero($episode: Episode, $withFriends: Boolean!) { 141 | hero(episode: $episode) { 142 | name 143 | friends @include(if: $withFriends) { 144 | name 145 | } 146 | } 147 | } 148 | 149 | { 150 | "episode": "JEDI", 151 | "withFriends": false 152 | } 153 | ``` 154 | 155 | --- 156 | 157 | # Mutace 158 | 159 | Mutace slouží k modifikaci položek, vytvoření, mazání apod. 160 | 161 | ```graphql 162 | mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { 163 | createReview(episode: $ep, review: $review) { 164 | stars 165 | commentary 166 | } 167 | } 168 | 169 | { 170 | "ep": "JEDI", 171 | "review": { 172 | "stars": 5, 173 | "commentary": "This is a great movie!" 174 | } 175 | } 176 | ``` 177 | 178 | --- 179 | 180 | # Architektury s GQL 181 | 182 | --- 183 | 184 | # GraphQL nad REST 185 | 186 | @startuml 187 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml 188 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml 189 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml 190 | 191 | Container(fe, "Frontend", "") 192 | 193 | Container(gql, "Vrstva GraphQL", "Rust, Juniper") 194 | 195 | Container(s1, "REST služba 1", "Rust, Actix") 196 | Container(s2, "REST služba 2", "Node.js, Express") 197 | Container(s3, "REST služba 3", "C#, ASP.NET") 198 | 199 | Container(db1, "SQL DB 1", "Postgres") 200 | Container(db2, "SQL DB 2", "Postgres") 201 | Container(db3, "noSQL DB", "Mongo") 202 | 203 | Rel_D(fe, gql, "") 204 | 205 | Rel_D(gql, s1, "") 206 | Rel_D(gql, s2, "") 207 | Rel_D(gql, s3, "") 208 | 209 | Rel_D(s1, db1, "") 210 | Rel_D(s2, db2, "") 211 | Rel_D(s3, db3, "") 212 | 213 | @enduml 214 | 215 | --- 216 | 217 | # GRPC backend 218 | 219 | @startuml 220 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml 221 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml 222 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml 223 | 224 | Container(fe, "Frontend", "") 225 | 226 | Container(gql, "Vrstva GraphQL", "Rust, Juniper") 227 | 228 | Container(s1, "GRPC služba 1", "Rust, Tonic") 229 | Container(s2, "GRPC služba 2", "C#") 230 | Container(s3, "REST služba 3", "C#, ASP.NET") 231 | 232 | Container(db1, "SQL DB 1", "Postgres") 233 | Container(db2, "SQL DB 2", "Postgres") 234 | Container(db3, "SQL DB 3", "MS SQL") 235 | 236 | Rel_D(fe, gql, "") 237 | 238 | Rel_D(gql, s1, "") 239 | Rel_D(gql, s2, "") 240 | Rel_D(gql, s3, "") 241 | 242 | Rel_D(s1, db1, "") 243 | Rel_D(s2, db2, "") 244 | Rel_D(s3, db3, "") 245 | 246 | @enduml 247 | 248 | --- 249 | 250 | # GraphQL nad databází 251 | 252 | @startuml 253 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml 254 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml 255 | !include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml 256 | 257 | Container(fe, "Frontend", "") 258 | 259 | Container(gql, "GraphQL", "Rust, Juniper") 260 | 261 | Container(db1, "SQL DB 1", "Postgres") 262 | 263 | Rel_D(fe, gql, "") 264 | 265 | Rel_D(gql, db1, "") 266 | 267 | @enduml 268 | 269 | --- 270 | 271 | # Juniper 272 | 273 | --- 274 | 275 | # Závislosti 276 | 277 | ```toml 278 | [dependencies] 279 | juniper = "0.14.2" 280 | ``` 281 | 282 | --- 283 | 284 | # Integrace s Actix 285 | 286 | ```rust 287 | use std::io; 288 | use std::sync::Arc; 289 | 290 | use actix_cors::Cors; 291 | use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; 292 | use juniper::http::graphiql::graphiql_source; 293 | use juniper::http::GraphQLRequest; 294 | 295 | use crate::schema::{create_schema, Schema}; 296 | ``` 297 | 298 | --- 299 | 300 | # Integrace s Actix 301 | 302 | ```rust 303 | #[actix_web::main] 304 | async fn main() -> io::Result<()> { 305 | std::env::set_var("RUST_LOG", "actix_web=info"); 306 | env_logger::init(); 307 | 308 | // Create Juniper schema 309 | let schema = std::sync::Arc::new(create_schema()); 310 | 311 | // Start http server 312 | HttpServer::new(move || { 313 | App::new() 314 | .data(schema.clone()) 315 | .wrap(middleware::Logger::default()) 316 | .wrap( 317 | Cors::new() 318 | .allowed_methods(vec!["POST", "GET"]) 319 | .supports_credentials() 320 | .max_age(3600) 321 | .finish(), 322 | ) 323 | .service(web::resource("/graphql").route(web::post().to(graphql))) 324 | .service(web::resource("/graphiql").route(web::get().to(graphiql))) 325 | }) 326 | .bind("127.0.0.1:8080")? 327 | .run() 328 | .await 329 | } 330 | ``` 331 | 332 | --- 333 | 334 | # Registrace endpointů 335 | 336 | ```rust 337 | async fn graphiql() -> HttpResponse { 338 | let html = graphiql_source("http://127.0.0.1:8080/graphql"); 339 | HttpResponse::Ok() 340 | .content_type("text/html; charset=utf-8") 341 | .body(html) 342 | } 343 | 344 | async fn graphql( 345 | st: web::Data>, 346 | data: web::Json, 347 | ) -> Result { 348 | let user = web::block(move || { 349 | let res = data.execute(&st, &()); 350 | Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?) 351 | }) 352 | .await?; 353 | Ok(HttpResponse::Ok() 354 | .content_type("application/json") 355 | .body(user)) 356 | } 357 | ``` 358 | 359 | --- 360 | 361 | # Schéma - výčet 362 | 363 | ```rust 364 | use juniper::FieldResult; 365 | use juniper::RootNode; 366 | use juniper::{GraphQLEnum, GraphQLInputObject, GraphQLObject}; 367 | 368 | #[derive(GraphQLEnum)] 369 | enum Episode { 370 | NewHope, 371 | Empire, 372 | Jedi, 373 | } 374 | 375 | ``` 376 | 377 | --- 378 | 379 | # Schéma - struktura 380 | 381 | ```rust 382 | use juniper::FieldResult; 383 | use juniper::RootNode; 384 | use juniper::{GraphQLEnum, GraphQLInputObject, GraphQLObject}; 385 | 386 | #[derive(GraphQLObject)] 387 | #[graphql(description = "A humanoid creature in the Star Wars universe")] 388 | struct Human { 389 | id: String, 390 | name: String, 391 | appears_in: Vec, 392 | home_planet: String, 393 | } 394 | 395 | ``` 396 | 397 | --- 398 | 399 | # Schéma - struktura pro mutaci 400 | 401 | ```rust 402 | use juniper::FieldResult; 403 | use juniper::RootNode; 404 | use juniper::{GraphQLEnum, GraphQLInputObject, GraphQLObject}; 405 | 406 | #[derive(GraphQLInputObject)] 407 | #[graphql(description = "A humanoid creature in the Star Wars universe")] 408 | struct NewHuman { 409 | name: String, 410 | appears_in: Vec, 411 | home_planet: String, 412 | } 413 | 414 | ``` 415 | 416 | --- 417 | 418 | # Kořen pro dotazy 419 | 420 | ```rust 421 | pub struct QueryRoot; 422 | 423 | #[juniper::object] 424 | impl QueryRoot { 425 | fn human(id: String) -> FieldResult { 426 | Ok(Human { 427 | id: "1234".to_owned(), 428 | name: "Luke".to_owned(), 429 | appears_in: vec![Episode::NewHope], 430 | home_planet: "Mars".to_owned(), 431 | }) 432 | } 433 | } 434 | ``` 435 | 436 | --- 437 | 438 | # Kořen pro mutace 439 | 440 | ```rust 441 | pub struct MutationRoot; 442 | 443 | #[juniper::object] 444 | impl MutationRoot { 445 | fn create_human(new_human: NewHuman) -> FieldResult { 446 | Ok(Human { 447 | id: "1234".to_owned(), 448 | name: new_human.name, 449 | appears_in: new_human.appears_in, 450 | home_planet: new_human.home_planet, 451 | }) 452 | } 453 | } 454 | ``` 455 | 456 | --- 457 | 458 | # Vytvoření schéma z kořenů 459 | 460 | ```rust 461 | pub type Schema = RootNode<'static, QueryRoot, MutationRoot>; 462 | 463 | pub fn create_schema() -> Schema { 464 | Schema::new(QueryRoot {}, MutationRoot {}) 465 | } 466 | ``` 467 | 468 | --- 469 | 470 | # Kontext 471 | 472 | ```rust 473 | pub struct Context { 474 | pub dbpool: Pool, 475 | } 476 | 477 | impl juniper::Context for Context {} 478 | 479 | pub struct QueryRoot; 480 | 481 | #[juniper::graphql_object(Context = Context)] 482 | impl QueryRoot { 483 | #[graphql(description = "List of all users")] 484 | fn users(context: &Context) -> FieldResult> { 485 | let mut conn = context.dbpool.get().unwrap(); 486 | let users = conn 487 | .prep_exec("select * from user", ()) 488 | .map(|result| { 489 | result 490 | .map(|x| x.unwrap()) 491 | .map(|row| { 492 | let (id, name, email) = from_row(row); 493 | User { id, name, email } 494 | }) 495 | .collect() 496 | }) 497 | .unwrap(); 498 | Ok(users) 499 | } 500 | } 501 | 502 | ``` 503 | 504 | --- 505 | 506 | # Naplnění kontextu z Actixu 507 | 508 | ```rust 509 | pub async fn graphql( 510 | pool: web::Data, 511 | schema: web::Data, 512 | data: web::Json, 513 | ) -> Result { 514 | let ctx = Context { 515 | dbpool: pool.get_ref().to_owned(), 516 | }; 517 | 518 | let res = web::block(move || { 519 | let res = data.execute_sync(&schema, &ctx); 520 | serde_json::to_string(&res) 521 | }) 522 | .await 523 | .map_err(Error::from)?; 524 | 525 | Ok(HttpResponse::Ok() 526 | .content_type("application/json") 527 | .body(res)) 528 | } 529 | 530 | ``` 531 | 532 | --- 533 | 534 | # Playground 535 | 536 | ```rust 537 | use juniper::http::graphiql::graphiql_source; 538 | 539 | pub async fn graphql_playground() -> HttpResponse { 540 | HttpResponse::Ok() 541 | .content_type("text/html; charset=utf-8") 542 | .body(graphiql_source("/graphql", None)) 543 | } 544 | 545 | // register to be called in actix builder 546 | pub fn register(config: &mut web::ServiceConfig) { 547 | config 548 | .data(create_schema()) 549 | .route("/graphql", web::post().to(graphql)) 550 | .route("/graphiql", web::get().to(graphql_playground)); 551 | } 552 | ``` 553 | 554 | --- 555 | 556 | # Like this 557 | 558 | ```rust 559 | #[actix_web::main] 560 | async fn main() -> std::io::Result<()> { 561 | dotenv::dotenv().ok(); 562 | std::env::set_var("RUST_LOG", "actix_web=info,info"); 563 | env_logger::init(); 564 | let pool = get_db_pool(); 565 | 566 | HttpServer::new(move || { 567 | App::new() 568 | .data(pool.clone()) 569 | .wrap(middleware::Logger::default()) 570 | .configure(register) 571 | .default_service(web::to(|| async { "404" })) 572 | }) 573 | .bind("127.0.0.1:8080")? 574 | .run() 575 | .await 576 | } 577 | ``` 578 | 579 | --- 580 | 581 | # Dotazy? 582 | 583 | --- 584 | 585 | # Děkuji za pozornost 586 | 587 | -------------------------------------------------------------------------------- /slides/10-2023-actix-htmx.md: -------------------------------------------------------------------------------- 1 | --- 2 | marp: true 3 | title: PV281 Programming in Rust 4 | description: Programming in Rust Actix & HTMX 5 | theme: rust 6 | paginate: true 7 | --- 8 | ![w:512 h:512](./assets/rust-logo-1.png) 9 | # PV281: Programování v Rustu 10 | 11 | 12 | --- 13 | 14 | # Obsah 15 | 16 | - HTMX 17 | - Askama rozšíření 18 | - Actix Cookie 19 | - Actix Session 20 | 21 | --- 22 | 23 | # AJAX (Asynchronous Javascript and XML) 24 | 25 | Pojďme se podívat 10 let zpátky: 26 | - místo celé stránky posíláme pouze vykreslené části 27 | - není třeba překreslit celou stránku 28 | 29 | --- 30 | 31 | # Kdy se mi to bude hodit? 32 | 33 | --- 34 | 35 | # Instalace přes npm 36 | 37 | ```sh 38 | npm install htmx.org 39 | ``` 40 | 41 | následně přidat do stránky `node_modules/htmx.org/dist/htmx.js` 42 | 43 | --- 44 | 45 | # Instalace přes CDN (unpkg) 46 | 47 | ```html 48 | 49 | ``` 50 | 51 | --- 52 | 53 | # Load data 54 | 55 | ```html 56 |
57 | ``` 58 | 59 | Response je vykreslená v elementu, který provedl request. 60 | 61 | --- 62 | 63 | # Requesty 64 | 65 | | Atribut | Typ requestu | 66 | |-----------------------------------------------------|-------------------------| 67 | | [hx-get](https://htmx.org/attributes/hx-get/) | Provede `GET` na URL | 68 | | [hx-post](https://htmx.org/attributes/hx-post/) | Provede `POST` na URL | 69 | | [hx-put](https://htmx.org/attributes/hx-put/) | Provede `PUT` na URL | 70 | | [hx-patch](https://htmx.org/attributes/hx-patch/) | Provede `PATCH` na URL | 71 | | [hx-delete](https://htmx.org/attributes/hx-delete/) | Provede `DELETE` na URL | 72 | 73 | --- 74 | 75 | # Předání parametrů 76 | 77 | ```html 78 |
Get Some HTML, Including A Value in the Request
79 | ``` 80 | 81 | --- 82 | 83 | # Target 84 | 85 | ```html 86 |
87 | Products 88 |
89 |
90 | ``` 91 | 92 | Pro změnu cíle pro vykreslení je nutné použít `hx-target`. 93 | 94 | --- 95 | 96 | # Extended selector 97 | 98 | Atributy (jako `hx-target`), které očekávají CSS selector, ve větši případů podporují rozšířenou syntaxi. 99 | 100 | `closest ` najde nejbližšího rodiče elementu 101 | `next ` najde element níže v DOMu (následující) 102 | `previous ` najde element výše v DOMu (předcházející) 103 | `find ` najde nejbližšího potomka elementu 104 | 105 | --- 106 | 107 | # Swapping 108 | 109 | | Hodnota | Popis | 110 | |-------------|------------------------------------------------------| 111 | | `innerHTML` | výchozí nastavení - nahradí obsah elementu (potomky) | 112 | | `outerHTML` | nahradí celý element včetně potomků | 113 | | `none` | bez vypsání odpovědi | 114 | 115 | --- 116 | 117 | # Loading indikátor 118 | 119 | ```html 120 | 124 | ``` 125 | 126 | Standardně se loading indikátor použije potomek s třídou `htmx-indicator`. Jeho `opacity` se nastaví na 1. Element jde vybrat pomocí `hx-indicator="#indicator"`. 127 | 128 | --- 129 | 130 | # View Transition 131 | 132 | Využítím View Transitions API lze animatovat změny v rámci DOMu. 133 | 134 | ```html 135 |
136 | Products 137 |
138 |
139 | ``` 140 | 141 | --- 142 | 143 | # Vykreslení JSONu 144 | 145 | Vzhledem k potenciálním problémům s CORS je doporučené použít server jako proxy pro získání dat. 146 | 147 | Pokud ale k tak potřebujete udělat request na klientu, tak lze použít klientské šablony. 148 | 149 | --- 150 | 151 | # Klientské šablony 152 | 153 | ```html 154 | 155 | 156 | ``` 157 | 158 | --- 159 | 160 | # Mustache šablona 161 | 162 | ```html 163 | 164 |
165 | 171 | 172 |
Loading...
173 | 174 | 175 | 176 |
177 | 178 | ``` 179 | 180 | --- 181 | 182 | ```html 183 | 184 |
185 | 186 | 187 | 192 |
193 | 194 | ``` 195 | 196 | Pozor na mixování s Askamou 197 | 198 | --- 199 | # Formulář 200 | 201 | ```html 202 |
203 | 207 | 208 |
209 | ``` 210 | 211 | --- 212 | 213 | # Přidání parametru mimo form 214 | 215 | ```html 216 |
217 | 220 | Enter email: 221 |
222 | ``` 223 | 224 | --- 225 | 226 | # Odeslaní dat jako JSON 227 | 228 | ```html 229 | 230 | ``` 231 | 232 | Přidáním `hx-ext='json-enc'` je request odeslaný jako typ `applicaton/json` s převodem na JSON v těle. 233 | 234 | ```html 235 |
236 | 237 | 238 |
239 | ``` 240 | 241 | --- 242 | 243 | # Odeslání souboru 244 | 245 | Periodicky se vyvolává `htmx:xhr:progress`. Odpovídá standardní `progress` události během uploadu. 246 | 247 | ```html 248 |
250 | 251 | 254 | 255 |
256 | ``` 257 | 258 | --- 259 | 260 | # Zpracování v Rustu 261 | 262 | ```rust 263 | async fn save_files( 264 | MultipartForm(form): MultipartForm, 265 | ) -> Result { 266 | for f in form.files { 267 | let path = format!("./tmp/{}", f.file_name.unwrap()); 268 | log::info!("saving to {path}"); 269 | f.file.persist(path).unwrap(); 270 | } 271 | 272 | Ok(HttpResponse::Ok()) 273 | } 274 | ``` 275 | 276 | --- 277 | 278 | # Zpracování v Rustu 279 | 280 | ```rust 281 | #[actix_web::main] 282 | async fn main() -> std::io::Result<()> { 283 | env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); 284 | 285 | log::info!("creating temporary upload directory"); 286 | std::fs::create_dir_all("./tmp")?; 287 | 288 | log::info!("starting HTTP server at http://localhost:8080"); 289 | 290 | HttpServer::new(|| { 291 | App::new() 292 | .wrap(middleware::Logger::default()) 293 | .app_data(TempFileConfig::default().directory("./tmp")) 294 | .service( 295 | web::resource("/") 296 | .route(web::get().to(index)) 297 | .route(web::post().to(save_files)), 298 | ) 299 | }) 300 | .bind(("127.0.0.1", 8080))? 301 | .workers(2) 302 | .run() 303 | .await 304 | } 305 | ``` 306 | 307 | --- 308 | 309 | # Klientská validace 310 | 311 | Provádí se na formulářových prvcích. Jinde je třeba zapnout přes `hx-validate='true'`. 312 | 313 | Skriptování je přes hyperscript. Pro instalaci: 314 | přidat: 315 | 316 | 317 | --- 318 | # Klientská validace 319 | 320 | ```html 321 |
322 | 329 |
330 | ``` 331 | 332 | --- 333 | # CSS extensions 334 | 335 | ```html 336 | 337 | ``` 338 | 339 | ```html 340 |
341 |
342 |
343 |
345 |
347 |
348 |
349 | ``` 350 | 351 | --- 352 | 353 | # Multiswap 354 | 355 | ```html 356 | 357 | ``` 358 | 359 | ```html 360 | 361 |
...
362 | 363 | 367 | 368 | 369 |
...
370 |
...
371 | 372 | ``` 373 | 374 | --- 375 | 376 | # Práce s historii 377 | 378 | ```html 379 |
380 | Go to My Account 381 |
382 | ``` 383 | 384 | Pozor - pokud měníte URL musíte i umět překreslit celou stránku. 385 | 386 | --- 387 | 388 | # Práce s historií 389 | 390 | ```html 391 |
392 | Go to My Account 393 |
394 | ``` 395 | 396 | A nebo chytře rozdělit url komponenty (partial view) a URL stránky. 397 | 398 | --- 399 | 400 | # Websocket 401 | 402 | ```html 403 |
404 |
405 | ... 406 |
407 |
408 | 409 |
410 |
411 | ``` 412 | 413 | --- 414 | 415 | # Šablony pomocí Askamy 416 | 417 | Jeden ze šablonovacích enginů. Šablony jsou kompilované s typovou kontrolou. 418 | 419 | ``` 420 | [dependencies] 421 | actix-web = "4" 422 | askama = "0.10" 423 | 424 | [build-dependencies] 425 | askama = "0.10" 426 | ``` 427 | 428 | Alternativou může být např. crate Tera. 429 | 430 | --- 431 | 432 | 433 | 434 | ### Rust kód šablony 435 | 436 |
437 | 438 | ```rust 439 | use std::collections::HashMap; 440 | use actix_web::{web, App, HttpResponse, HttpServer, Result}; 441 | use askama::Template; 442 | 443 | #[derive(Template)] 444 | #[template(path = "user.html")] 445 | struct UserTemplate<'a> { 446 | name: &'a str, 447 | text: &'a str, 448 | } 449 | 450 | #[derive(Template)] 451 | #[template(path = "index.html")] 452 | struct Index; 453 | ``` 454 | 455 |
456 |
457 | 458 | ```rust 459 | async fn index( 460 | query: web::Query> 461 | ) -> Result { 462 | let s = if let Some(name) = query.get("name") { 463 | UserTemplate { 464 | name, 465 | text: "Welcome!", 466 | } 467 | .render() 468 | .unwrap() 469 | } else { 470 | Index.render().unwrap() 471 | }; 472 | Ok(HttpResponse::Ok().content_type("text/html").body(s)) 473 | } 474 | ``` 475 | 476 |
477 | 478 | --- 479 | 480 | # Dědičnost šablon 481 | 482 | Definice bloků `title`, `head` a `content` pro použití v potomcích. 483 | 484 | ```html 485 | 486 | 487 | 488 | {% block title %}{{ title }} - My Site{% endblock %} 489 | {% block head %}{% endblock %} 490 | 491 | 492 |
493 | {% block content %}

Placeholder content

{% endblock %} 494 |
495 | 496 | 497 | ``` 498 | 499 | --- 500 | 501 | # Child template 502 | 503 | ```html 504 | {% extends "base.html" %} 505 | 506 | {% block title %}Index{% endblock %} 507 | 508 | {% block head %} 509 | 511 | {% endblock %} 512 | 513 | {% block content %} 514 |

Index

515 |

Hello, world!

516 | {% call super() %} 517 | {% endblock %} 518 | ``` 519 | 520 | --- 521 | 522 | # Include 523 | 524 | ```html 525 |
526 | {% include "item.html" %} 527 |
528 | ``` 529 | 530 | --- 531 | 532 | # For 533 | 534 | ```html 535 |

Users

536 |
    537 | {% for user in users %} 538 |
  • {{ user.name|e }}
  • 539 | {% endfor %} 540 |
541 | ``` 542 | 543 | --- 544 | 545 | # If 546 | 547 | ```html 548 | {% if users.len() == 0 %} 549 | No users 550 | {% else if users.len() == 1 %} 551 | 1 user 552 | {% else %} 553 | {{ users.len() }} users 554 | {% endif %} 555 | ``` 556 | 557 | --- 558 | 559 | # If 560 | 561 | ```html 562 | {% match item %} 563 | {% when Some with ("foo") %} 564 | Found literal foo 565 | {% when Some with (val) %} 566 | Found {{ val }} 567 | {% when None %} 568 | {% endmatch %} 569 | ``` 570 | 571 | --- 572 | 573 | # Co je cookie? 574 | 575 | --- 576 | 577 | # Actix Cookie 578 | 579 | ```rust 580 | use cookie::Cookie; 581 | 582 | let cookie = Cookie::build("name", "value") 583 | .domain("www.rust-lang.org") 584 | .path("/") 585 | .secure(true) 586 | .http_only(true) 587 | .finish(); 588 | ``` 589 | 590 | --- 591 | 592 | # Actix Session 593 | 594 | - umožňuje držet stav uživatele. 595 | - session je postavená na cookie (Set-Cookie). 596 | - je třeba řešit tak, aby řešení bylo škálovatelné (Redis vs vše v cookie) 597 | - OWASP cheatsheat na práci se session: 598 | https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html 599 | 600 | --- 601 | 602 | ```rust 603 | use actix_web::{App, cookie::{Key, time}, Error, HttpResponse, HttpServer, web}; 604 | use actix_session::{Session, SessionMiddleware, storage::RedisActorSessionStore}; 605 | use actix_session::config::PersistentSession; 606 | 607 | #[actix_web::main] 608 | async fn main() -> std::io::Result<()> { 609 | let secret_key = get_secret_key_from_config(); 610 | let redis_connection_string = "127.0.0.1:6379"; 611 | HttpServer::new(move || 612 | App::new() 613 | .wrap( 614 | SessionMiddleware::builder( 615 | RedisActorSessionStore::new(redis_connection_string), 616 | secret_key.clone() 617 | ) 618 | .session_lifecycle( 619 | PersistentSession::default() 620 | .session_ttl(time::Duration::days(5)) 621 | ) 622 | .build(), 623 | ) 624 | .default_service(web::to(|| HttpResponse::Ok()))) 625 | .bind(("127.0.0.1", 8080))? 626 | .run() 627 | .await 628 | } 629 | ``` 630 | 631 | --- 632 | 633 | # Výběr backendu 634 | 635 | ```toml 636 | [dependencies] 637 | # ... 638 | actix-session = { version = "...", features = ["cookie-session"] } 639 | 640 | # pro Actix Redis 641 | actix-session = { version = "...", features = ["redis-actor-session"] } 642 | 643 | ``` 644 | 645 | --- 646 | 647 | # Získání session 648 | 649 | ```rust 650 | use actix_session::Session; 651 | 652 | #[get("/")] 653 | async fn index(session: Session) -> Result { 654 | // access session data 655 | if let Some(count) = session.get::("counter")? { 656 | session.insert("counter", count + 1)?; 657 | } else { 658 | session.insert("counter", 1)?; 659 | } 660 | 661 | let count = session.get::("counter")?.unwrap(); 662 | Ok(format!("Counter: {}", count)) 663 | } 664 | ``` 665 | 666 | --- 667 | 668 | # Dotazy? 669 | 670 | --- 671 | 672 | # Děkuji za pozornost 673 | --------------------------------------------------------------------------------