summaryrefslogtreecommitdiff
path: root/rust/src/web.rs
diff options
context:
space:
mode:
authorArnaud Bailly <arnaud.bailly@iohk.io>2024-10-10 08:16:44 +0200
committerArnaud Bailly <arnaud.bailly@iohk.io>2024-10-10 08:16:44 +0200
commit492a9e7ae1b50bd2e46daf589a6e52388703ec7e (patch)
treed4eb6370f2bfb3bc57392463c30a196ae910a151 /rust/src/web.rs
parente3fdf12d472b24a9a91a0bdebe667b86797b2483 (diff)
downloadlambda-nantes-492a9e7ae1b50bd2e46daf589a6e52388703ec7e.tar.gz
Render leaderboard template from clients data
Diffstat (limited to 'rust/src/web.rs')
-rw-r--r--rust/src/web.rs120
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();