Moved cache and messages. Added docs.

This commit is contained in:
Jeff Baskin 2024-05-12 14:10:36 -04:00
parent 3955048157
commit bb1cdfa890
5 changed files with 236 additions and 231 deletions

124
src/cache.rs Normal file
View File

@ -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<Uuid>,
rx: Receiver<SendMsg>,
}
impl Cache {
/// Create database cache
///
/// This should not be called directly.
/// It is part of the MoreThanText::new function.
pub fn new(rx: Receiver<SendMsg>) -> 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<String>) -> 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<Uuid> = 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);
}
}

View File

@ -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<Self::Item> {
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();
}
}

View File

@ -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::{ use std::{
collections::HashMap, sync::mpsc::{channel, Sender},
fmt,
sync::mpsc::{channel, Receiver, Sender},
thread::spawn, thread::spawn,
}; };
use uuid::Uuid;
pub enum Session { /// Application connection to the database
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: HashMap<String, DataType>,
rx: Receiver<SendMsg>,
}
impl Cache {
fn new(recv: Receiver<SendMsg>) -> 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<String>) -> 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
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct MoreThanText { pub struct MoreThanText {
id: Option<Uuid>,
tx: Sender<SendMsg>, tx: Sender<SendMsg>,
} }
impl MoreThanText { impl MoreThanText {
/// Create a MoreThanText database.
///
/// Example:
///
/// ```
/// use morethantext::MoreThanText;
///
/// MoreThanText::new();
/// ```
pub fn new() -> Self { pub fn new() -> Self {
let (tx, rx) = channel(); let (tx, rx) = channel();
spawn(move || { spawn(move || {
let mut cache = Cache::new(rx); let mut cache = Cache::new(rx);
cache.listen(); cache.listen();
}); });
Self { tx: tx } Self { id: None, tx: tx }
} }
pub fn get_session(&self, id: Option<String>) -> 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<String>) {
let (tx, rx) = channel(); let (tx, rx) = channel();
self.tx let request = SessionRequest {
.send(SendMsg::ValidateSess(ValidateSession::new(id, tx))) id: id,
.unwrap(); tx: tx,
rx.recv().unwrap() };
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)] #[cfg(test)]
mod client { mod mtt_client {
use super::*; use super::*;
#[test] #[test]
fn session_ids_are_unique() { fn uniques_ids() {
let conn = MoreThanText::new(); let mut mtt = MoreThanText::new();
let mut ids: Vec<String> = Vec::new(); let mut ids: Vec<Uuid> = Vec::new();
for _ in 1..10 { for _ in 1..10 {
match conn.get_session(None) { mtt.open_session(None);
Session::New(id) => { let id = mtt.id.clone().unwrap();
if ids.contains(&id) { if ids.contains(&id) {
assert!(false, "{} is a duplicate id", id); assert!(false, "{} is a duplicate id", id);
}
ids.push(id)
}
Session::Ok => assert!(false, "Should have returned a new id."),
} }
ids.push(id);
} }
} }
#[test] #[test]
fn existing_ids_return_ok() { fn existing_ids_are_reused() {
let conn = MoreThanText::new(); let mut mtt = MoreThanText::new();
let sid: String; mtt.open_session(None);
match conn.get_session(None) { let holder = mtt.id.clone().unwrap().to_string();
Session::New(id) => sid = id, mtt.open_session(Some(holder.clone()));
Session::Ok => unreachable!(), assert_eq!(mtt.id.clone().unwrap().to_string(), holder);
}
match conn.get_session(Some(sid.clone())) {
Session::New(_) => assert!(false, "should not create a new session"),
Session::Ok => {}
}
} }
#[test] #[test]
fn bad_ids_get_new_session() { fn bad_ids_generate_new_ones() {
let conn = MoreThanText::new(); let mut mtt = MoreThanText::new();
let sid = "bad id"; mtt.open_session(Some("bad test string".to_string()));
match conn.get_session(Some(sid.to_string())) { assert!(mtt.id.is_some());
Session::New(id) => assert_ne!(sid, id, "do not reuse original id"),
Session::Ok => assert!(false, "shouuld generate a new id"),
}
} }
}
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<String, Field>,
}
impl Record {
fn new(data: HashMap<String, Field>) -> 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] #[test]
fn create_record() { fn incorrect_ids_generate_new_ones() {
let input = HashMap::from([ let mut mtt = MoreThanText::new();
("one".to_string(), Field::StaticString("1".to_string())), let holder = Uuid::new_v4();
("two".to_string(), Field::StaticString("2".to_string())), mtt.open_session(Some(holder.clone().to_string()));
("three".to_string(), Field::StaticString("3".to_string())), assert_ne!(mtt.id, Some(holder));
]);
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");
} }
} }
struct Column;
struct Table {
columns: HashMap<String, Column>,
}
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),
}

View File

@ -1,7 +1,8 @@
use axum::{extract::State, response::IntoResponse, routing::get, Router}; use axum::{extract::State, response::IntoResponse, routing::get, Router};
use axum_extra::extract::cookie::{Cookie, CookieJar}; use axum_extra::extract::cookie::{Cookie, CookieJar};
use clap::Parser; use clap::Parser;
use morethantext::{MoreThanText, Session}; //use morethantext::{MoreThanText, Session};
use morethantext::MoreThanText;
const LOCALHOST: &str = "127.0.0.1"; const LOCALHOST: &str = "127.0.0.1";
const SESSION_KEY: &str = "sessionid"; const SESSION_KEY: &str = "sessionid";
@ -40,19 +41,17 @@ async fn main() {
.unwrap(); .unwrap();
} }
async fn handler(jar: CookieJar, state: State<MoreThanText>) -> impl IntoResponse { async fn handler(jar: CookieJar, mut state: State<MoreThanText>) -> impl IntoResponse {
let cookies: CookieJar;
let sid: Option<String>; let sid: Option<String>;
let mut cookies = jar.clone();
match jar.get(SESSION_KEY) { match jar.get(SESSION_KEY) {
Some(cookie) => sid = Some(cookie.value().to_string()), Some(cookie) => sid = Some(cookie.value().to_string()),
None => sid = None, None => sid = None,
} }
match state.get_session(sid) { state.open_session(sid.clone());
Session::Ok => cookies = jar, if !sid.is_some_and(|x| x == state.get_id()) {
Session::New(id) => { let cookie = Cookie::build((SESSION_KEY, state.get_id()));
let cookie = Cookie::build((SESSION_KEY, id.clone())).domain("example.com"); cookies = jar.add(cookie);
cookies = jar.add(cookie);
}
} }
(cookies, "Something goes here.") (cookies, "Something goes here.")
} }

22
src/messages.rs Normal file
View File

@ -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<String>,
/// Channel Sender for the reply.
pub tx: Sender<ReceiveMsg>,
}