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 /rust/src | |
| parent | e3fdf12d472b24a9a91a0bdebe667b86797b2483 (diff) | |
| download | lambda-nantes-492a9e7ae1b50bd2e46daf589a6e52388703ec7e.tar.gz | |
Render leaderboard template from clients data
Diffstat (limited to 'rust/src')
| -rw-r--r-- | rust/src/web.rs | 120 |
1 files changed, 118 insertions, 2 deletions
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(); |
