diff --git a/src/cache.rs b/src/cache.rs new file mode 100644 index 0000000..a3dca46 --- /dev/null +++ b/src/cache.rs @@ -0,0 +1,124 @@ +use super::messages::{ReceiveMsg, SendMsg}; +use std::sync::mpsc::Receiver; +use uuid::Uuid; + +/// MoreThanText database Cache +pub struct Cache { + data: Vec, + rx: Receiver, +} + +impl Cache { + /// Create database cache + /// + /// This should not be called directly. + /// It is part of the MoreThanText::new function. + pub fn new(rx: Receiver) -> Self { + Self { data: Vec::new(), rx: rx } + } + + /// Starts listening for database requests. + /// + /// Should not be directly called. + /// Part of the MoreThanText::new function. + pub fn listen(&mut self) { + loop { + match self.rx.recv().unwrap() { + SendMsg::OpenSession(msg) => msg.tx.send(self.get_session(msg.id)).unwrap(), + } + } + } + + fn get_session(&mut self, id: Option) -> ReceiveMsg { + let sid: ReceiveMsg; + match id { + Some(input) => { + match Uuid::parse_str(&input) { + Ok(vid) => { + if self.data.contains(&vid) { + sid = ReceiveMsg::Session(vid); + } else { + sid = self.new_session(); + } + }, + Err(_) => sid = self.new_session(), + } + }, + None => sid = self.new_session(), + } + sid + } + + fn new_session(&mut self) -> ReceiveMsg { + let mut id = Uuid::new_v4(); + while self.data.contains(&id) { + id = Uuid::new_v4(); + } + self.data.push(id.clone()); + ReceiveMsg::Session(id) + } +} + +#[cfg(test)] +mod session { + use super::*; + use std::sync::mpsc::channel; + + #[test] + fn unique_ids() { + let (_, rx) = channel(); + let mut cache = Cache::new(rx); + let mut ids: Vec = Vec::new(); + for _ in 1..10 { + let id = cache.get_session(None); + match id { + ReceiveMsg::Session(sid) => { + if ids.contains(&sid) { + assert!(false, "{} was a duplicate id.", sid) + } + ids.push(sid); + } + } + } + } + + #[test] + fn existing_ids_are_reused() { + let (_, rx) = channel(); + let mut cache = Cache::new(rx); + let id1: Uuid; + let id2: Uuid; + match cache.get_session(None) { + ReceiveMsg::Session(sid) => id1 = sid, + } + match cache.get_session(Some(id1.to_string())) { + ReceiveMsg::Session(sid) => id2 = sid, + } + assert_eq!(id2, id1); + } + + #[test] + fn bad_ids_generate_new_ones() { + let (_, rx) = channel(); + let mut cache = Cache::new(rx); + let id: Uuid; + let bad_id = "A very bad id"; + match cache.get_session(Some(bad_id.to_string())) + { + ReceiveMsg::Session(sid) => id = sid, + } + assert_ne!(id.to_string(), bad_id); + } + + #[test] + fn expired_ids_generate_new_ids() { + let (_, rx) = channel(); + let mut cache = Cache::new(rx); + let old_id = Uuid::new_v4(); + let id: Uuid; + match cache.get_session(Some(old_id.to_string())) { + ReceiveMsg::Session(sid) => id = sid, + } + assert_ne!(id, old_id); + } +} diff --git a/src/counter.rs b/src/counter.rs deleted file mode 100644 index 9ca2c6c..0000000 --- a/src/counter.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::iter::Iterator; -use uuid::Uuid; - -struct Counter { - id: Uuid, - counter: u128, -} - -impl Counter { - fn new() -> Self { - Self { - id: Uuid::new_v4(), - counter: 0, - } - } -} - -impl Iterator for Counter { - type Item: Counter; - - fn next(&mut self) -> Option { - Counter::new() - } -} - -#[cfg(test)] -mod counters { - use super::*; - - #[test] - fn create_counter() { - let count1 = Counter::new(); - let count2 = Counter::new(); - assert_ne!(count1.id, count2.id); - assert_eq!(count1.counter, 0); - assert_eq!(count2.counter, 0); - } - - #[test] - fn iterate_counter() { - let count = Counter::new(); - let first = count.next().unwrap(); - let second = count.next().unwrap(); - let third = count.next().unwrap(); - } -} diff --git a/src/lib.rs b/src/lib.rs index 1d0a599..5111cb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,217 +1,123 @@ -mod counter; +mod cache; +mod messages; -use rand::distributions::{Alphanumeric, DistString}; +use cache::Cache; +use messages::{ReceiveMsg, SendMsg, SessionRequest}; use std::{ - collections::HashMap, - fmt, - sync::mpsc::{channel, Receiver, Sender}, + sync::mpsc::{channel, Sender}, thread::spawn, }; +use uuid::Uuid; -pub enum Session { - Ok, - New(String), -} - -struct ValidateSession { - id: Option, - tx: Sender, -} - -impl ValidateSession { - fn new(id: Option, tx: Sender) -> Self { - Self { id: id, tx: tx } - } -} - -enum SendMsg { - ValidateSess(ValidateSession), -} - -struct Cache { - data: HashMap, - rx: Receiver, -} - -impl Cache { - fn new(recv: Receiver) -> Self { - Self { - rx: recv, - data: HashMap::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) -> Session { - let session: Session; - if sess.is_some_and(|sess| true) {// self.data.contains(&sess)) { - session = Session::Ok; - } else { - let id = self.gen_id(); - // `self.data.push(id.clone()); - session = Session::New(id); - } - session - } -} - +/// Application connection to the database #[derive(Clone)] pub struct MoreThanText { + id: Option, tx: Sender, } impl MoreThanText { + /// Create a MoreThanText database. + /// + /// Example: + /// + /// ``` + /// use morethantext::MoreThanText; + /// + /// MoreThanText::new(); + /// ``` pub fn new() -> Self { let (tx, rx) = channel(); spawn(move || { let mut cache = Cache::new(rx); cache.listen(); }); - Self { tx: tx } + Self { id: None, tx: tx } } - pub fn get_session(&self, id: Option) -> Session { + /// Opens an existing or new session with the database. + /// + /// If the string is None, incorrect, or expired, + /// a new session will be created. + /// + /// Example: + /// + /// ``` + /// use morethantext::MoreThanText; + /// + /// let mut mtt = MoreThanText::new(); + /// mtt.open_session(None); + /// mtt.open_session(Some("7b1ff340-7dfa-4f29-b144-601384e54423".to_string())); + /// ``` + pub fn open_session(&mut self, id: Option) { let (tx, rx) = channel(); - self.tx - .send(SendMsg::ValidateSess(ValidateSession::new(id, tx))) - .unwrap(); - rx.recv().unwrap() + let request = SessionRequest { + id: id, + tx: tx, + }; + self.tx.send(SendMsg::OpenSession(request)).unwrap(); + match rx.recv().unwrap() { + ReceiveMsg::Session(sid) => self.id = Some(sid), + } + } + + /// Get the session id + /// + /// Example: + /// + /// ``` + /// use morethantext::MoreThanText; + /// + /// let mut mtt = MoreThanText::new(); + /// mtt.get_id(); + /// ``` + pub fn get_id(&self) -> String { + match self.id { + Some(sid) => sid.to_string(), + None => "none".to_string(), + } } } #[cfg(test)] -mod client { +mod mtt_client { use super::*; #[test] - fn session_ids_are_unique() { - let conn = MoreThanText::new(); - let mut ids: Vec = Vec::new(); + fn uniques_ids() { + let mut mtt = MoreThanText::new(); + let mut ids: Vec = 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."), + mtt.open_session(None); + let id = mtt.id.clone().unwrap(); + if ids.contains(&id) { + assert!(false, "{} is a duplicate id", id); } + ids.push(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 => {} - } + fn existing_ids_are_reused() { + let mut mtt = MoreThanText::new(); + mtt.open_session(None); + let holder = mtt.id.clone().unwrap().to_string(); + mtt.open_session(Some(holder.clone())); + assert_eq!(mtt.id.clone().unwrap().to_string(), holder); } #[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"), - } + fn bad_ids_generate_new_ones() { + let mut mtt = MoreThanText::new(); + mtt.open_session(Some("bad test string".to_string())); + assert!(mtt.id.is_some()); } -} - -enum Field { - StaticString(String), -} - -impl fmt::Display for Field { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Field::StaticString(data) => write!(f, "{}", data), - } - } -} - -struct Record { - data: HashMap, -} - -impl Record { - fn new(data: HashMap) -> Self { - Self { - data: data, - } - } - - fn get(&self, fieldname: &str) -> &Field { - match self.data.get(fieldname) { - Some(field) => field, - None => unreachable!(), - } - } -} - -#[cfg(test)] -mod records { - use super::*; #[test] - fn create_record() { - let input = HashMap::from([ - ("one".to_string(), Field::StaticString("1".to_string())), - ("two".to_string(), Field::StaticString("2".to_string())), - ("three".to_string(), Field::StaticString("3".to_string())), - ]); - let rec = Record::new(input); - assert_eq!(rec.get("one").to_string(), "1"); - assert_eq!(rec.get("two").to_string(), "2"); - assert_eq!(rec.get("three").to_string(), "3"); + fn incorrect_ids_generate_new_ones() { + let mut mtt = MoreThanText::new(); + let holder = Uuid::new_v4(); + mtt.open_session(Some(holder.clone().to_string())); + assert_ne!(mtt.id, Some(holder)); } } - -struct Column; - -struct Table { - columns: HashMap, -} - -impl Table { - fn new() -> Self { - Self { - columns: HashMap::new(), - } - } -} - -#[cfg(test)] -mod tables { - use super::*; - - #[test] - fn create_table() { - let tbl = Table::new(); - } -} - -enum DataType { - Table(Table), - Record(Record), -} diff --git a/src/main.rs b/src/main.rs index 53d362a..2783658 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ use axum::{extract::State, response::IntoResponse, routing::get, Router}; use axum_extra::extract::cookie::{Cookie, CookieJar}; use clap::Parser; -use morethantext::{MoreThanText, Session}; +//use morethantext::{MoreThanText, Session}; +use morethantext::MoreThanText; const LOCALHOST: &str = "127.0.0.1"; const SESSION_KEY: &str = "sessionid"; @@ -40,19 +41,17 @@ async fn main() { .unwrap(); } -async fn handler(jar: CookieJar, state: State) -> impl IntoResponse { - let cookies: CookieJar; +async fn handler(jar: CookieJar, mut state: State) -> impl IntoResponse { let sid: Option; + let mut cookies = jar.clone(); match jar.get(SESSION_KEY) { Some(cookie) => sid = Some(cookie.value().to_string()), None => sid = None, } - match state.get_session(sid) { - Session::Ok => cookies = jar, - Session::New(id) => { - let cookie = Cookie::build((SESSION_KEY, id.clone())).domain("example.com"); - cookies = jar.add(cookie); - } + state.open_session(sid.clone()); + if !sid.is_some_and(|x| x == state.get_id()) { + let cookie = Cookie::build((SESSION_KEY, state.get_id())); + cookies = jar.add(cookie); } (cookies, "Something goes here.") } diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 0000000..cb0fff6 --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,22 @@ +/// These are the nessages that are used by the database. +use std::sync::mpsc::Sender; +use uuid::Uuid; + +/// Requests of the database. +pub enum SendMsg { + OpenSession(SessionRequest), +} + +/// Responses to database requests +pub enum ReceiveMsg { + Session(Uuid), +} + + +/// Items needed for a session request. +pub struct SessionRequest { + /// Optional string reprosentation of the session id. + pub id: Option, + /// Channel Sender for the reply. + pub tx: Sender, +}