diff options
| author | Arnaud Bailly <arnaud.bailly@iohk.io> | 2024-10-10 08:16:44 +0200 |
|---|---|---|
| committer | Arnaud Bailly <arnaud.bailly@iohk.io> | 2024-10-10 08:16:44 +0200 |
| commit | 492a9e7ae1b50bd2e46daf589a6e52388703ec7e (patch) | |
| tree | d4eb6370f2bfb3bc57392463c30a196ae910a151 | |
| parent | e3fdf12d472b24a9a91a0bdebe667b86797b2483 (diff) | |
| download | lambda-nantes-492a9e7ae1b50bd2e46daf589a6e52388703ec7e.tar.gz | |
Render leaderboard template from clients data
| -rw-r--r-- | rust/Cargo.lock | 117 | ||||
| -rw-r--r-- | rust/Cargo.toml | 1 | ||||
| -rw-r--r-- | rust/src/web.rs | 120 | ||||
| -rw-r--r-- | rust/templates/leaderboard.html | 16 |
4 files changed, 252 insertions, 2 deletions
diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 0d9e14f..bdb2dd9 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1026,6 +1026,21 @@ dependencies = [ ] [[package]] +name = "handlebars" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", + "walkdir", +] + +[[package]] name = "hashbrown" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1521,6 +1536,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] +name = "pest" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1774,6 +1834,7 @@ dependencies = [ "clap", "env_logger", "futures", + "handlebars", "log", "proptest", "rand", @@ -1870,6 +1931,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] name = "schannel" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1969,6 +2039,17 @@ dependencies = [ ] [[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2090,6 +2171,26 @@ dependencies = [ ] [[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2237,6 +2338,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2337,6 +2444,16 @@ dependencies = [ ] [[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1006d29..b2eab9c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -17,6 +17,7 @@ reqwest = "0.12.8" tokio = { version = "1.40.0", features = ["rt", "macros", "rt-multi-thread"]} clap = { version = "4.5.19", features = ["derive"] } proptest = "1.0.0" +handlebars = { version = "5", features = ["dir_source"] } [dependencies.uuid] version = "1.10.0" diff --git a/rust/src/web.rs b/rust/src/web.rs index 2b540c2..1be9ebc 100644 --- a/rust/src/web.rs +++ b/rust/src/web.rs @@ -1,6 +1,10 @@ -use actix_web::{middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::{ + get, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder, + Result as ActixResult, +}; use clap::Parser; use futures::lock::Mutex; +use handlebars::{DirectorySourceOptions, Handlebars}; use log::info; use proptest::test_runner::{Config, RngAlgorithm, TestRng, TestRunner}; use rand::Rng; @@ -19,10 +23,41 @@ struct Registration { name: String, } +#[derive(Debug, Serialize, Deserialize, Clone)] +struct ClientData { + name: String, + grade: u8, +} + +impl ClientData { + fn from(client: &Client) -> Self { + ClientData { + name: client.name.clone(), + grade: client.grade, + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +struct Leaderboard { + clients: Vec<ClientData>, +} + trait AppState: Send + Sync { fn register(&mut self, registration: &Registration) -> RegistrationResult; } +fn make_leaderboard(clients: Vec<Client>) -> Leaderboard { + let clients = clients + .iter() + .map(|c| ClientData { + name: c.name.clone(), + grade: c.grade, + }) + .collect(); + Leaderboard { clients } +} + #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] enum RegistrationResult { RegistrationSuccess { id: String, url: String }, @@ -32,8 +67,8 @@ enum RegistrationResult { #[derive(Debug, Clone)] struct Client { id: Uuid, - url: String, name: String, + url: String, grade: u8, runner: TestRunner, results: Vec<TestResult>, @@ -190,6 +225,30 @@ async fn eval(input: String) -> impl Responder { } } +#[get("/leaderboard")] +async fn leaderboard( + app_state: web::Data<Arc<Mutex<State>>>, + hb: web::Data<Handlebars<'_>>, +) -> impl Responder { + let clients = &app_state.lock().await.clients; + let mut client_data = vec![]; + for client in clients.values() { + let client = client.lock().await; + client_data.push(ClientData::from(&client)); + } + + let body = hb + .render( + "leaderboard", + &Leaderboard { + clients: client_data, + }, + ) + .unwrap(); + + web::Html::new(body) +} + #[derive(Parser, Debug)] struct Options { /// The port to listen on @@ -208,12 +267,29 @@ async fn main() -> std::io::Result<()> { let app_state = Arc::new(Mutex::new(State::new())); let http_state: Arc<Mutex<dyn AppState>> = app_state; env_logger::init(); + // Handlebars uses a repository for the compiled templates. This object must be + // shared between the application threads, and is therefore passed to the + // Application Builder as an atomic reference-counted pointer. + let mut handlebars = Handlebars::new(); + handlebars + .register_templates_directory( + "./templates", + DirectorySourceOptions { + tpl_extension: ".html".to_owned(), + hidden: false, + temporary: false, + }, + ) + .unwrap(); + HttpServer::new(move || { App::new() .wrap(Logger::default()) .app_data(web::Data::new(http_state.clone())) + .app_data(web::Data::new(handlebars.clone())) .service(register) .service(eval) + .service(leaderboard) }) .bind((options.host, options.port))? .run() @@ -255,6 +331,7 @@ async fn send_test(input: &String, url: &String) -> Result<String, TestResult> { #[cfg(test)] mod app_tests { + use std::str::from_utf8; use std::sync::Arc; use actix_web::http::header::TryIntoHeaderValue; @@ -376,6 +453,45 @@ mod app_tests { assert_eq!(bytes, "1".to_string().into_bytes()); } + #[actix_web::test] + async fn get_leaderboard_returns_html_page_listing_clients_state() { + let app_state = Arc::new(Mutex::new(State::new())); + app_state.lock().await.register(&Registration { + url: "http://1.2.3.4".to_string(), + name: "client1".to_string(), + }); + + let mut handlebars = Handlebars::new(); + handlebars + .register_templates_directory( + "./templates", + DirectorySourceOptions { + tpl_extension: ".html".to_owned(), + hidden: false, + temporary: false, + }, + ) + .unwrap(); + + let app = test::init_service( + App::new() + .wrap(Logger::default()) + .app_data(web::Data::new(app_state.clone())) + .app_data(web::Data::new(handlebars.clone())) + .service(leaderboard), + ) + .await; + + let req = test::TestRequest::get().uri("/leaderboard").to_request(); + + let resp = test::call_service(&app, req).await; + + assert!(resp.status().is_success()); + + let bytes = body::to_bytes(resp.into_body()).await.unwrap(); + assert!(from_utf8(&bytes).unwrap().contains("client1")); + } + #[test] async fn app_does_not_register_same_url_twice() { let mut app_state = State::new(); diff --git a/rust/templates/leaderboard.html b/rust/templates/leaderboard.html new file mode 100644 index 0000000..f062323 --- /dev/null +++ b/rust/templates/leaderboard.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Leaderboard</title> +</head> + +<body> + <table> + <tr><th>Name</th><th>Grade</th></tr> + {{#each this.clients}} + <tr><td>{{this.name}}</td><td>{{this.grade}}</td></tr> + {{/each}} + </table> +</body> +</html> |
