use actix_web::{ get, http::header::ContentType, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder, }; use async_std::task; use futures::future::join_all; use futures::try_join; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, env::args, io::{stdin, stdout, IsTerminal}, sync::{Arc, Mutex}, }; use uuid::Uuid; #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] struct Registration { url: String, } trait AppState: Send + Sync { fn register(&mut self, registration: &Registration) -> RegistrationResult; } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] enum RegistrationResult { RegistrationSuccess { id: String, url: String }, UrlAlreadyRegistered { url: String }, } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] enum RegistrationFailure { UrlAlreadyRegistered { url: String }, } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] struct Client { id: Uuid, url: String, } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] struct State { clients: HashMap, } impl State { fn new() -> Self { Self { clients: HashMap::new(), } } } impl AppState for State { fn register(&mut self, registration: &Registration) -> RegistrationResult { if self.clients.contains_key(®istration.url) { RegistrationResult::UrlAlreadyRegistered { url: registration.url.clone(), } } else { let id = Uuid::new_v4(); self.clients.insert( registration.url.clone(), Client { id, url: registration.url.clone(), }, ); RegistrationResult::RegistrationSuccess { id: id.to_string(), url: registration.url.clone(), } } } } #[post("/register")] async fn register( app_state: web::Data>>, registration: web::Json, ) -> impl Responder { let result = app_state.lock().unwrap().register(®istration); match result { RegistrationResult::RegistrationSuccess { .. } => HttpResponse::Ok().json(result), RegistrationResult::UrlAlreadyRegistered { .. } => HttpResponse::BadRequest().json(result), } } #[actix_web::main] async fn main() -> std::io::Result<()> { let app_state = Arc::new(Mutex::new(State::new())); let send_state: Arc> = app_state.clone(); let http_state: Arc> = app_state.clone(); env_logger::init(); // let it run in the background task::spawn(async move { send_tests(send_state).await }); HttpServer::new(move || { App::new() .wrap(Logger::default()) .app_data(web::Data::new(http_state.clone())) .service(register) }) .bind(("127.0.0.1", 8080))? .run() .await } async fn send_tests(clients: Arc>) -> ! { loop { task::sleep(std::time::Duration::from_secs(1)).await; let clients = clients.lock().unwrap(); for client in clients.clients.values() { send_test(client); } } } fn send_test(client: &Client) -> Result<(), String> { println!("Sending test to {}", client.url); Ok(()) } #[cfg(test)] mod app_tests { use std::sync::Arc; use actix_web::http::header::TryIntoHeaderValue; use actix_web::{body, http::header::ContentType, middleware::Logger, test, App}; use super::*; struct DummyAppState { id: String, } impl DummyAppState { fn new(id: String) -> Self { Self { id } } } impl AppState for DummyAppState { fn register(&mut self, registration: &Registration) -> RegistrationResult { if self.id == "" { RegistrationResult::UrlAlreadyRegistered { url: registration.url.clone(), } } else { RegistrationResult::RegistrationSuccess { id: self.id.clone(), url: registration.url.clone(), } } } } #[actix_web::test] async fn post_registration_returns_success_with_unique_id() { let id = "0123456789abcdef0123456789abcdef".to_string(); let dummy_state: Arc> = Arc::new(Mutex::new(DummyAppState::new(id.clone()))); // FIXME should only be called once, move to setup env_logger::init(); let app = test::init_service( App::new() .wrap(Logger::default()) .app_data(web::Data::new(dummy_state)) .service(register), ) .await; let url = "http://192.168.1.1".to_string(); let req = test::TestRequest::post() .uri("/register") .set_json(Registration { url: url.clone() }) .insert_header(ContentType::json()) .to_request(); let resp = test::call_service(&app, req).await; assert!(resp.status().is_success()); let body = resp.into_body(); let bytes = body::to_bytes(body).await; assert_eq!( RegistrationResult::RegistrationSuccess { id, url }, serde_json::from_slice(&bytes.unwrap()).unwrap() ); } #[actix_web::test] async fn post_registration_returns_400_when_register_fails() { let dummy_state: Arc> = Arc::new(Mutex::new(DummyAppState::new("".to_string()))); let app = test::init_service( App::new() .wrap(Logger::default()) .app_data(web::Data::new(dummy_state)) .service(register), ) .await; let url = "http://192.168.1.1".to_string(); let req = test::TestRequest::post() .uri("/register") .set_json(Registration { url: url.clone() }) .insert_header(ContentType::json()) .to_request(); let resp = test::call_service(&app, req).await; assert!(resp.status().is_client_error()); assert_eq!( ContentType::json().try_into_value().unwrap(), resp.headers().get("content-type").unwrap() ); } #[test] async fn app_does_not_register_same_url_twice() { let mut app_state = State::new(); let registration = Registration { url: "http://1.2.3.4".to_string(), }; app_state.register(®istration); let result = app_state.register(®istration); assert_eq!( RegistrationResult::UrlAlreadyRegistered { url: "http://1.2.3.4".to_string() }, result ); } }