use rand::distributions::{Alphanumeric, DistString}; use std::{ sync::mpsc::{channel, Receiver, Sender}, thread::spawn, }; pub enum Session { Ok, New(String), } struct ValidateSession { id: Option<String>, tx: Sender<Session>, } impl ValidateSession { fn new(id: Option<String>, tx: Sender<Session>) -> Self { Self { id: id, tx: tx } } } enum SendMsg { ValidateSess(ValidateSession), } struct Cache { data: Vec<String>, rx: Receiver<SendMsg>, } impl Cache { fn new(recv: Receiver<SendMsg>) -> Self { Self { rx: recv, data: Vec::new(), } } fn gen_id(&self) -> String { Alphanumeric.sample_string(&mut rand::thread_rng(), 16) } fn listen(&mut self) { loop { match self.rx.recv().unwrap() { SendMsg::ValidateSess(vsess) => { vsess.tx.send(self.validate_session(vsess.id)).unwrap() } } } } fn validate_session(&mut self, sess: Option<String>) -> Session { let session: Session; if sess.is_some_and(|sess| self.data.contains(&sess)) { session = Session::Ok; } else { let id = self.gen_id(); self.data.push(id.clone()); session = Session::New(id); } session } } #[derive(Clone)] pub struct MoreThanText { tx: Sender<SendMsg>, } impl MoreThanText { pub fn new() -> Self { let (tx, rx) = channel(); spawn(move || { let mut cache = Cache::new(rx); cache.listen(); }); Self { tx: tx } } pub fn get_session(&self, id: Option<String>) -> Session { let (tx, rx) = channel(); self.tx .send(SendMsg::ValidateSess(ValidateSession::new(id, tx))) .unwrap(); rx.recv().unwrap() } } #[cfg(test)] mod client { use super::*; #[test] fn session_ids_are_unique() { let conn = MoreThanText::new(); let mut ids: Vec<String> = Vec::new(); for _ in 1..10 { match conn.get_session(None) { Session::New(id) => { if ids.contains(&id) { assert!(false, "{} is a duplicate id", id); } ids.push(id) } Session::Ok => assert!(false, "Should have returned a new id."), } } } #[test] fn existing_ids_return_ok() { let conn = MoreThanText::new(); let sid: String; match conn.get_session(None) { Session::New(id) => sid = id, Session::Ok => unreachable!(), } match conn.get_session(Some(sid.clone())) { Session::New(_) => assert!(false, "should not create a new session"), Session::Ok => {} } } #[test] fn bad_ids_get_new_session() { let conn = MoreThanText::new(); let sid = "bad id"; match conn.get_session(Some(sid.to_string())) { Session::New(id) => assert_ne!(sid, id, "do not reuse original id"), Session::Ok => assert!(false, "shouuld generate a new id"), } } }