2025-12-26 13:48:02 -05:00
|
|
|
use crate::{
|
2026-01-09 11:39:14 -05:00
|
|
|
data_director::{Include, Path, RegMsg, Register, Route},
|
2026-01-12 15:05:45 -05:00
|
|
|
document::{
|
|
|
|
|
create::IndexType,
|
|
|
|
|
definition::{DocDef, DocFuncType},
|
2026-01-14 12:03:40 -05:00
|
|
|
field::{Field, FieldType},
|
2026-01-12 15:05:45 -05:00
|
|
|
},
|
2026-01-06 16:19:15 -05:00
|
|
|
mtterror::MTTError,
|
2025-12-26 13:48:02 -05:00
|
|
|
name::{Name, NameType, Names},
|
2025-12-28 15:46:50 -05:00
|
|
|
router::Queue,
|
2025-12-26 13:48:02 -05:00
|
|
|
};
|
2025-09-16 09:05:42 -04:00
|
|
|
use chrono::prelude::*;
|
2025-07-17 09:12:23 -04:00
|
|
|
use std::{
|
2025-12-23 22:15:49 -05:00
|
|
|
collections::{HashMap, HashSet},
|
2025-12-28 15:46:50 -05:00
|
|
|
sync::mpsc::{channel, Receiver},
|
2026-01-08 15:02:03 -05:00
|
|
|
thread::spawn,
|
2025-09-18 13:30:20 -04:00
|
|
|
time::Duration,
|
2025-07-17 09:12:23 -04:00
|
|
|
};
|
2025-07-04 10:25:37 -04:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
2025-07-17 09:12:23 -04:00
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub enum Action {
|
2025-08-05 09:56:48 -04:00
|
|
|
Addition,
|
2025-07-25 11:08:47 -04:00
|
|
|
Create,
|
2025-09-25 09:41:17 -04:00
|
|
|
Delete,
|
2025-08-01 10:58:40 -04:00
|
|
|
Error,
|
2025-12-02 14:48:26 -05:00
|
|
|
GetLog,
|
|
|
|
|
Log,
|
2025-11-26 20:20:43 -05:00
|
|
|
OnAddition,
|
|
|
|
|
OnDelete,
|
2025-11-18 17:10:28 -05:00
|
|
|
OnQuery,
|
2025-11-26 20:20:43 -05:00
|
|
|
OnUpdate,
|
2025-07-28 10:49:34 -04:00
|
|
|
Query,
|
2025-10-16 00:47:40 -04:00
|
|
|
Records,
|
2025-09-25 09:41:17 -04:00
|
|
|
Register,
|
2025-07-04 10:25:37 -04:00
|
|
|
Reply,
|
2025-08-03 12:21:45 -04:00
|
|
|
Show,
|
2025-08-26 08:06:22 -04:00
|
|
|
Update,
|
2025-07-04 10:25:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-07-28 10:49:34 -04:00
|
|
|
impl From<MsgAction> for Action {
|
|
|
|
|
fn from(value: MsgAction) -> Self {
|
|
|
|
|
match value {
|
2025-08-05 09:56:48 -04:00
|
|
|
MsgAction::Addition(_) => Action::Addition,
|
2025-07-28 10:49:34 -04:00
|
|
|
MsgAction::Create(_) => Action::Create,
|
2025-09-25 09:41:17 -04:00
|
|
|
MsgAction::Delete(_) => Action::Delete,
|
2025-08-01 10:58:40 -04:00
|
|
|
MsgAction::Error(_) => Action::Error,
|
2025-12-02 14:48:26 -05:00
|
|
|
MsgAction::GetLog(_) => Action::GetLog,
|
|
|
|
|
MsgAction::Log(_) => Action::Log,
|
2025-11-26 20:20:43 -05:00
|
|
|
MsgAction::OnAddition(_) => Action::OnAddition,
|
|
|
|
|
MsgAction::OnDelete(_) => Action::OnDelete,
|
2025-11-26 00:32:10 -05:00
|
|
|
MsgAction::OnQuery(_) => Action::OnQuery,
|
2025-11-26 20:20:43 -05:00
|
|
|
MsgAction::OnUpdate(_) => Action::OnUpdate,
|
2025-07-28 10:49:34 -04:00
|
|
|
MsgAction::Query(_) => Action::Query,
|
2025-10-16 00:47:40 -04:00
|
|
|
MsgAction::Records(_) => Action::Records,
|
2025-09-25 09:41:17 -04:00
|
|
|
MsgAction::Register(_) => Action::Register,
|
2025-07-30 08:37:58 -04:00
|
|
|
MsgAction::Reply(_) => Action::Reply,
|
2025-08-03 12:21:45 -04:00
|
|
|
MsgAction::Show => Action::Show,
|
2025-08-26 08:06:22 -04:00
|
|
|
MsgAction::Update(_) => Action::Update,
|
2025-07-28 10:49:34 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&MsgAction> for Action {
|
|
|
|
|
fn from(value: &MsgAction) -> Self {
|
|
|
|
|
let action = value.clone();
|
|
|
|
|
Self::from(action)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-01 10:58:40 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub enum MsgAction {
|
2025-08-05 09:56:48 -04:00
|
|
|
Addition(Addition),
|
2025-07-28 10:49:34 -04:00
|
|
|
Create(DocDef),
|
2025-08-02 08:58:50 -04:00
|
|
|
// Alter
|
|
|
|
|
// Remove
|
2025-08-01 10:58:40 -04:00
|
|
|
Error(MTTError),
|
2025-12-02 14:48:26 -05:00
|
|
|
GetLog(Uuid),
|
|
|
|
|
Log(Vec<MsgEntry>),
|
2025-11-26 20:20:43 -05:00
|
|
|
OnAddition(Records),
|
|
|
|
|
OnDelete(Records),
|
2025-11-26 00:32:10 -05:00
|
|
|
OnQuery(Records),
|
2025-11-26 20:20:43 -05:00
|
|
|
OnUpdate(Records),
|
2025-11-18 17:10:28 -05:00
|
|
|
Query(Query),
|
|
|
|
|
Records(Records),
|
2025-09-25 09:41:17 -04:00
|
|
|
Register(Register),
|
2025-08-05 09:56:48 -04:00
|
|
|
Reply(Reply),
|
2025-08-03 12:21:45 -04:00
|
|
|
Show,
|
2025-09-25 09:41:17 -04:00
|
|
|
Delete(Delete),
|
2025-08-26 08:06:22 -04:00
|
|
|
Update(Update),
|
2025-08-02 08:58:50 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-05 09:56:48 -04:00
|
|
|
impl From<Addition> for MsgAction {
|
|
|
|
|
fn from(value: Addition) -> Self {
|
|
|
|
|
MsgAction::Addition(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 09:41:17 -04:00
|
|
|
impl From<Delete> for MsgAction {
|
|
|
|
|
fn from(value: Delete) -> Self {
|
|
|
|
|
MsgAction::Delete(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-02 08:58:50 -04:00
|
|
|
impl From<DocDef> for MsgAction {
|
|
|
|
|
fn from(value: DocDef) -> Self {
|
|
|
|
|
MsgAction::Create(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<MTTError> for MsgAction {
|
|
|
|
|
fn from(value: MTTError) -> Self {
|
|
|
|
|
MsgAction::Error(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 09:56:48 -04:00
|
|
|
impl From<Query> for MsgAction {
|
|
|
|
|
fn from(value: Query) -> Self {
|
2025-11-18 17:10:28 -05:00
|
|
|
MsgAction::Query(value)
|
2025-08-02 08:58:50 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 17:10:28 -05:00
|
|
|
impl From<Records> for MsgAction {
|
|
|
|
|
fn from(value: Records) -> Self {
|
2025-10-16 00:47:40 -04:00
|
|
|
MsgAction::Records(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-25 09:41:17 -04:00
|
|
|
impl From<Register> for MsgAction {
|
|
|
|
|
fn from(value: Register) -> Self {
|
|
|
|
|
MsgAction::Register(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 09:56:48 -04:00
|
|
|
impl From<Reply> for MsgAction {
|
|
|
|
|
fn from(value: Reply) -> Self {
|
2025-08-02 08:58:50 -04:00
|
|
|
MsgAction::Reply(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 08:06:22 -04:00
|
|
|
impl From<Update> for MsgAction {
|
|
|
|
|
fn from(value: Update) -> Self {
|
|
|
|
|
MsgAction::Update(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 14:48:26 -05:00
|
|
|
impl From<Uuid> for MsgAction {
|
|
|
|
|
fn from(value: Uuid) -> Self {
|
|
|
|
|
MsgAction::GetLog(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&Uuid> for MsgAction {
|
|
|
|
|
fn from(value: &Uuid) -> Self {
|
|
|
|
|
Self::from(value.clone())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-02 08:58:50 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod msgactions {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn turn_document_definition_into_action() {
|
2025-10-21 07:27:48 -04:00
|
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
2025-10-08 19:00:02 -04:00
|
|
|
let value = DocDef::new(name.clone());
|
2025-08-02 08:58:50 -04:00
|
|
|
let result: MsgAction = value.into();
|
|
|
|
|
match result {
|
2025-11-08 11:29:17 -05:00
|
|
|
MsgAction::Create(def) => assert_eq!(def.get_document_names(), &[name].to_vec()),
|
2025-08-02 08:58:50 -04:00
|
|
|
_ => unreachable!("Got {:?}: dhould have been create", result),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn turn_error_into_action() {
|
|
|
|
|
let data = "data".to_string();
|
|
|
|
|
let value = MTTError::DocumentAlreadyExists(data.clone());
|
|
|
|
|
let result: MsgAction = value.into();
|
|
|
|
|
match result {
|
|
|
|
|
MsgAction::Error(result) => match result {
|
|
|
|
|
MTTError::DocumentAlreadyExists(output) => assert_eq!(output, data),
|
|
|
|
|
_ => unreachable!("Got {:?}: dhould have been create", result),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("Got {:?}: dhould have been create", result),
|
|
|
|
|
}
|
|
|
|
|
let value = MTTError::DocumentNotFound(data.clone());
|
|
|
|
|
let result: MsgAction = value.into();
|
|
|
|
|
match result {
|
|
|
|
|
MsgAction::Error(result) => match result {
|
|
|
|
|
MTTError::DocumentNotFound(output) => assert_eq!(output, data),
|
|
|
|
|
_ => unreachable!("Got {:?}: dhould have been create", result),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("Got {:?}: dhould have been create", result),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn turn_query_into_action() {
|
2025-08-05 09:56:48 -04:00
|
|
|
let value = Query::new();
|
2025-08-02 08:58:50 -04:00
|
|
|
let result: MsgAction = value.into();
|
|
|
|
|
match result {
|
2025-08-02 09:55:13 -04:00
|
|
|
MsgAction::Query(_) => {}
|
2025-08-02 08:58:50 -04:00
|
|
|
_ => unreachable!("Got {:?}: dhould have been query", result),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn turn_reply_into_action() {
|
2025-08-05 09:56:48 -04:00
|
|
|
let value = Reply::new();
|
2025-08-02 08:58:50 -04:00
|
|
|
let result: MsgAction = value.into();
|
|
|
|
|
match result {
|
2025-08-02 09:55:13 -04:00
|
|
|
MsgAction::Reply(_) => {}
|
2025-08-02 08:58:50 -04:00
|
|
|
_ => unreachable!("Got {:?}: dhould have been reply", result),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-28 10:49:34 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-03 12:21:45 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Message {
|
2025-07-04 10:25:37 -04:00
|
|
|
msg_id: Uuid,
|
2025-10-08 19:00:02 -04:00
|
|
|
document_id: NameType,
|
2025-07-28 10:49:34 -04:00
|
|
|
action: MsgAction,
|
2026-01-05 09:59:56 -05:00
|
|
|
route: Route,
|
2025-10-13 14:13:10 -04:00
|
|
|
// session: Option<?>
|
2025-07-04 10:25:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Message {
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn new<D, A>(doc_id: D, action: A) -> Self
|
2025-07-17 09:12:23 -04:00
|
|
|
where
|
2025-10-08 19:00:02 -04:00
|
|
|
D: Into<NameType>,
|
2025-08-02 08:58:50 -04:00
|
|
|
A: Into<MsgAction>,
|
2025-07-17 09:12:23 -04:00
|
|
|
{
|
2025-07-04 10:25:37 -04:00
|
|
|
Self {
|
|
|
|
|
msg_id: Uuid::new_v4(),
|
|
|
|
|
document_id: doc_id.into(),
|
2025-08-02 08:58:50 -04:00
|
|
|
action: action.into(),
|
2026-01-05 09:59:56 -05:00
|
|
|
route: Route::default(),
|
2025-07-04 10:25:37 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn get_message_id(&self) -> &Uuid {
|
2025-07-04 10:25:37 -04:00
|
|
|
&self.msg_id
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn get_action(&self) -> &MsgAction {
|
2025-07-17 09:12:23 -04:00
|
|
|
&self.action
|
2025-07-04 10:25:37 -04:00
|
|
|
}
|
2025-07-30 10:06:12 -04:00
|
|
|
|
2026-01-05 09:59:56 -05:00
|
|
|
pub fn get_path(&self) -> Path {
|
2025-10-14 14:29:11 -04:00
|
|
|
Path::new(
|
2025-12-17 12:14:06 -05:00
|
|
|
Include::Just(self.msg_id.clone()),
|
|
|
|
|
Include::Just(self.document_id.clone()),
|
|
|
|
|
Include::Just(self.action.clone().into()),
|
2025-10-14 14:29:11 -04:00
|
|
|
)
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-05 09:59:56 -05:00
|
|
|
pub fn get_route(&self) -> Route {
|
|
|
|
|
self.route.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_route(&mut self, route: Route) {
|
|
|
|
|
self.route = route;
|
2025-11-10 15:36:56 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn response<A>(&self, action: A) -> Self
|
2025-08-10 16:20:47 -04:00
|
|
|
where
|
|
|
|
|
A: Into<MsgAction>,
|
|
|
|
|
{
|
2025-08-10 11:14:32 -04:00
|
|
|
Self {
|
|
|
|
|
msg_id: self.msg_id.clone(),
|
|
|
|
|
document_id: self.document_id.clone(),
|
|
|
|
|
action: action.into(),
|
2026-01-05 09:59:56 -05:00
|
|
|
route: Route::default(),
|
2025-08-10 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-13 17:33:37 -05:00
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn forward<D, A>(&self, doc_id: D, action: A) -> Self
|
2025-12-13 17:33:37 -05:00
|
|
|
where
|
|
|
|
|
D: Into<NameType>,
|
|
|
|
|
A: Into<MsgAction>,
|
|
|
|
|
{
|
|
|
|
|
Self {
|
|
|
|
|
msg_id: self.msg_id.clone(),
|
|
|
|
|
document_id: doc_id.into(),
|
|
|
|
|
action: action.into(),
|
2026-01-05 09:59:56 -05:00
|
|
|
route: Route::default(),
|
2025-12-13 17:33:37 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-04 10:25:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod messages {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-01-05 09:59:56 -05:00
|
|
|
fn can_the_document_be_a_named_reference() {
|
2025-10-18 13:03:36 -04:00
|
|
|
let dts = [Name::english("one"), Name::english("two")];
|
2025-07-04 10:25:37 -04:00
|
|
|
for document in dts.into_iter() {
|
2025-10-14 14:29:11 -04:00
|
|
|
let msg = Message::new(
|
|
|
|
|
document.clone(),
|
|
|
|
|
MsgAction::Create(DocDef::new(document.clone())),
|
|
|
|
|
);
|
2026-01-05 09:59:56 -05:00
|
|
|
match &msg.document_id {
|
2025-10-08 19:00:02 -04:00
|
|
|
NameType::Name(data) => assert_eq!(data, &document),
|
2025-09-25 09:41:17 -04:00
|
|
|
_ => unreachable!("should have been a string id"),
|
2025-07-04 10:25:37 -04:00
|
|
|
}
|
2025-07-28 10:49:34 -04:00
|
|
|
match msg.get_action() {
|
|
|
|
|
MsgAction::Create(_) => {}
|
2026-01-05 09:59:56 -05:00
|
|
|
_ => unreachable!("should have been a create document"),
|
|
|
|
|
}
|
2025-11-08 12:08:34 -05:00
|
|
|
}
|
2025-09-25 09:41:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-01-05 09:59:56 -05:00
|
|
|
fn can_the_document_be_an_id() {
|
|
|
|
|
let document = Uuid::new_v4();
|
|
|
|
|
let msg = Message::new(document.clone(), Query::new());
|
|
|
|
|
match msg.document_id {
|
|
|
|
|
NameType::ID(data) => assert_eq!(data, document),
|
|
|
|
|
_ => unreachable!("should have been an id"),
|
|
|
|
|
}
|
|
|
|
|
match msg.get_action() {
|
|
|
|
|
MsgAction::Query(_) => {}
|
|
|
|
|
_ => unreachable!("should have been an access query"),
|
2025-09-25 09:41:17 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-04 10:25:37 -04:00
|
|
|
|
2025-09-25 09:41:17 -04:00
|
|
|
#[test]
|
2026-01-05 09:59:56 -05:00
|
|
|
fn do_messages_contain_routes() {
|
|
|
|
|
let mut msg = Message::new(Name::english("whatever"), Query::new());
|
|
|
|
|
let default_route = msg.get_route();
|
|
|
|
|
match default_route.msg_id {
|
|
|
|
|
Include::Just(_) => unreachable!("should defalt to all"),
|
|
|
|
|
Include::All => {}
|
2025-09-28 17:15:18 -04:00
|
|
|
}
|
2026-01-05 09:59:56 -05:00
|
|
|
match default_route.doc_id {
|
|
|
|
|
Include::Just(_) => unreachable!("should defalt to all"),
|
|
|
|
|
Include::All => {}
|
2025-09-25 09:41:17 -04:00
|
|
|
}
|
2026-01-05 09:59:56 -05:00
|
|
|
match default_route.action {
|
|
|
|
|
Include::Just(_) => unreachable!("should defalt to all"),
|
|
|
|
|
Include::All => {}
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
2026-01-05 09:59:56 -05:00
|
|
|
let doc_id = Uuid::new_v4();
|
|
|
|
|
let route = Route::new(
|
|
|
|
|
Include::Just(msg.get_message_id().clone()),
|
|
|
|
|
Include::Just(doc_id.clone()),
|
|
|
|
|
Include::Just(msg.get_action().into()),
|
|
|
|
|
);
|
|
|
|
|
msg.set_route(route);
|
|
|
|
|
let result = msg.get_route();
|
|
|
|
|
match result.msg_id {
|
|
|
|
|
Include::Just(data) => assert_eq!(&data, msg.get_message_id()),
|
|
|
|
|
Include::All => unreachable!("should have message id"),
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
2026-01-05 09:59:56 -05:00
|
|
|
match result.doc_id {
|
|
|
|
|
Include::Just(data) => assert_eq!(data, doc_id),
|
|
|
|
|
Include::All => unreachable!("should have document id"),
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
2026-01-05 09:59:56 -05:00
|
|
|
match result.action {
|
|
|
|
|
Include::Just(data) => assert_eq!(data, msg.get_action().into()),
|
|
|
|
|
Include::All => unreachable!("should have action"),
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-01-05 09:59:56 -05:00
|
|
|
fn is_the_message_id_random() {
|
|
|
|
|
let mut ids: Vec<Uuid> = Vec::new();
|
|
|
|
|
for _ in 0..5 {
|
|
|
|
|
let msg = Message::new(Name::english("tester"), Query::new());
|
|
|
|
|
let id = msg.get_message_id().clone();
|
|
|
|
|
assert!(!ids.contains(&id), "{:?} containts {}", ids, id);
|
|
|
|
|
ids.push(id);
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-01-05 09:59:56 -05:00
|
|
|
fn can_make_reply_message() {
|
|
|
|
|
let name = Name::english("testing");
|
|
|
|
|
let msg = Message::new(name.clone(), Query::new());
|
|
|
|
|
let responce = Reply::new();
|
|
|
|
|
let reply = msg.response(responce);
|
|
|
|
|
assert_eq!(reply.get_message_id(), msg.get_message_id());
|
|
|
|
|
match &reply.document_id {
|
|
|
|
|
NameType::Name(data) => assert_eq!(data, &name),
|
|
|
|
|
_ => unreachable!("should have been a name"),
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
2026-01-05 09:59:56 -05:00
|
|
|
match reply.get_action() {
|
|
|
|
|
MsgAction::Reply(_) => {}
|
|
|
|
|
_ => unreachable!("should have been a reply"),
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-01-05 09:59:56 -05:00
|
|
|
fn can_make_error_message() {
|
|
|
|
|
let name = Name::english("testing");
|
|
|
|
|
let msg = Message::new(name.clone(), Query::new());
|
|
|
|
|
let err_msg = Uuid::new_v4().to_string();
|
|
|
|
|
let result = msg.response(MTTError::DocumentNotFound(err_msg.clone()));
|
|
|
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
|
|
|
match &result.document_id {
|
|
|
|
|
NameType::Name(data) => assert_eq!(data, &name),
|
|
|
|
|
_ => unreachable!("should have been a name"),
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
2026-01-05 09:59:56 -05:00
|
|
|
match result.get_action() {
|
|
|
|
|
MsgAction::Error(data) => match data {
|
|
|
|
|
MTTError::DocumentNotFound(txt) => assert_eq!(txt, &err_msg),
|
|
|
|
|
_ => unreachable!("got {:?}, should have received not found", data),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("should have been a reply"),
|
2025-10-08 19:00:02 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-12-17 12:14:06 -05:00
|
|
|
|
|
|
|
|
#[test]
|
2026-01-05 09:59:56 -05:00
|
|
|
fn can_make_a_response_message() {
|
|
|
|
|
let doc_id = Uuid::new_v4();
|
|
|
|
|
let msg = Message::new(doc_id.clone(), Query::new());
|
|
|
|
|
let data = Uuid::new_v4().to_string();
|
|
|
|
|
let result1 = msg.response(MTTError::DocumentNotFound(data.clone()));
|
|
|
|
|
let result2 = msg.response(Reply::new());
|
|
|
|
|
assert_eq!(result1.get_message_id(), msg.get_message_id());
|
|
|
|
|
assert_eq!(result2.get_message_id(), msg.get_message_id());
|
|
|
|
|
assert_eq!(result1.document_id, msg.document_id);
|
|
|
|
|
assert_eq!(result2.document_id, msg.document_id);
|
|
|
|
|
let action1 = result1.get_action();
|
|
|
|
|
match action1 {
|
|
|
|
|
MsgAction::Error(err) => match err {
|
|
|
|
|
MTTError::DocumentNotFound(output) => assert_eq!(output, &data),
|
|
|
|
|
_ => unreachable!("got {:?}: should have received document not found", err),
|
2025-12-17 12:14:06 -05:00
|
|
|
},
|
2026-01-05 09:59:56 -05:00
|
|
|
_ => unreachable!("got {:?}: should have received error", action1),
|
|
|
|
|
}
|
|
|
|
|
let action2 = result2.get_action();
|
|
|
|
|
match action2 {
|
|
|
|
|
MsgAction::Reply(data) => assert_eq!(data.len(), 0),
|
|
|
|
|
_ => unreachable!("got {:?}: should have received a reply", action2),
|
2025-12-17 12:14:06 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-04 10:25:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-05 09:56:48 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Addition {
|
2025-08-06 13:41:35 -04:00
|
|
|
data: Document,
|
2025-08-05 11:27:18 -04:00
|
|
|
}
|
2025-08-05 09:56:48 -04:00
|
|
|
|
|
|
|
|
impl Addition {
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn new() -> Self {
|
2025-08-05 11:27:18 -04:00
|
|
|
Self {
|
2025-08-06 13:41:35 -04:00
|
|
|
data: Document::new(),
|
2025-08-05 11:27:18 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn add_field<NT, CV>(&mut self, name: NT, field: CV)
|
2025-08-06 13:41:35 -04:00
|
|
|
where
|
2025-09-21 10:12:07 -04:00
|
|
|
CV: Into<CalcValue>,
|
2025-10-08 19:00:02 -04:00
|
|
|
NT: Into<NameType>,
|
2025-08-06 13:41:35 -04:00
|
|
|
{
|
|
|
|
|
self.data.add_field(name, field);
|
2025-08-05 11:27:18 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-11-01 11:56:36 -04:00
|
|
|
fn get_field<NT>(&self, name: NT) -> &CalcValue
|
2025-10-14 14:29:11 -04:00
|
|
|
where
|
|
|
|
|
NT: Into<NameType>,
|
|
|
|
|
{
|
2025-08-06 13:41:35 -04:00
|
|
|
self.data.get_field(name)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-08-06 13:41:35 -04:00
|
|
|
fn get_document(&self) -> Document {
|
|
|
|
|
self.data.clone()
|
2025-08-05 11:27:18 -04:00
|
|
|
}
|
2025-11-06 08:07:43 -05:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item = (&NameType, &CalcValue)> {
|
2025-11-06 08:07:43 -05:00
|
|
|
self.data.iter()
|
|
|
|
|
}
|
2025-08-05 11:27:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod additions {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_add_static_string() {
|
|
|
|
|
let mut add = Addition::new();
|
2025-10-21 07:27:48 -04:00
|
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
2025-08-05 11:27:18 -04:00
|
|
|
let data = Uuid::new_v4().to_string();
|
|
|
|
|
add.add_field(name.clone(), data.clone());
|
2025-11-01 11:56:36 -04:00
|
|
|
let result = add.get_field(&name);
|
2025-08-05 11:27:18 -04:00
|
|
|
match result {
|
2025-11-01 11:56:36 -04:00
|
|
|
CalcValue::Value(result) => match result {
|
|
|
|
|
Field::StaticString(output) => assert_eq!(output, &data),
|
|
|
|
|
_ => unreachable!("got {:?}, should have been a string", result),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("got {:?}: should have received value", result),
|
2025-08-05 11:27:18 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 11:56:36 -04:00
|
|
|
#[test]
|
2025-08-05 11:27:18 -04:00
|
|
|
fn can_add_uuid() {
|
|
|
|
|
let mut add = Addition::new();
|
2025-10-21 07:27:48 -04:00
|
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
2025-08-05 11:27:18 -04:00
|
|
|
let data = Uuid::new_v4();
|
|
|
|
|
add.add_field(name.clone(), data.clone());
|
2025-11-01 11:56:36 -04:00
|
|
|
let output = add.get_field(&name);
|
|
|
|
|
match output {
|
|
|
|
|
CalcValue::Value(result) => match result {
|
|
|
|
|
Field::Uuid(result) => assert_eq!(result, &data),
|
|
|
|
|
_ => unreachable!("got {:?}: should have received uuid", result),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("got {:?}: should have received value", output),
|
2025-08-05 11:27:18 -04:00
|
|
|
}
|
2025-08-05 09:56:48 -04:00
|
|
|
}
|
2025-08-06 13:41:35 -04:00
|
|
|
|
2025-11-01 11:56:36 -04:00
|
|
|
#[test]
|
2025-08-06 13:41:35 -04:00
|
|
|
fn can_get_document() {
|
|
|
|
|
let mut add = Addition::new();
|
2025-10-21 07:27:48 -04:00
|
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
2025-08-06 13:41:35 -04:00
|
|
|
let data = Uuid::new_v4();
|
|
|
|
|
add.add_field(name.clone(), data.clone());
|
2025-11-01 11:56:36 -04:00
|
|
|
let doc = add.get_document();
|
|
|
|
|
let output = doc.get_field(&name);
|
|
|
|
|
match output {
|
|
|
|
|
CalcValue::Value(holder) => match holder {
|
|
|
|
|
Field::Uuid(result) => assert_eq!(result, &data),
|
|
|
|
|
_ => unreachable!("should have received uuid"),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("got {:?}: should have received value", output),
|
2025-08-06 13:41:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-05 09:56:48 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-01 10:58:40 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub enum Operand {
|
2025-09-18 13:30:20 -04:00
|
|
|
Add,
|
2025-08-11 12:17:37 -04:00
|
|
|
Equal,
|
2025-12-06 01:03:43 -05:00
|
|
|
GreaterThan,
|
|
|
|
|
GreaterThanEqual,
|
|
|
|
|
LessThan,
|
|
|
|
|
LessThanEqual,
|
2025-08-11 12:17:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-13 12:45:20 -04:00
|
|
|
impl Operand {
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-09-14 10:53:47 -04:00
|
|
|
fn validate(&self, x: &Field, y: &Field) -> bool {
|
2025-09-13 12:45:20 -04:00
|
|
|
match self {
|
|
|
|
|
Self::Equal => x == y,
|
2025-12-06 01:03:43 -05:00
|
|
|
Self::GreaterThan => x > y,
|
|
|
|
|
Self::GreaterThanEqual => x >= y,
|
|
|
|
|
Self::LessThan => x < y,
|
|
|
|
|
Self::LessThanEqual => x <= y,
|
2025-09-18 13:30:20 -04:00
|
|
|
_ => false,
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod operands {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn equals_true() {
|
|
|
|
|
let data: Field = Uuid::new_v4().into();
|
2025-09-14 10:53:47 -04:00
|
|
|
assert!(Operand::Equal.validate(&data, &data));
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn equals_false() {
|
|
|
|
|
let x: Field = Uuid::new_v4().into();
|
|
|
|
|
let mut y: Field = Uuid::new_v4().into();
|
|
|
|
|
while x == y {
|
|
|
|
|
y = Uuid::new_v4().into();
|
|
|
|
|
}
|
2025-09-14 10:53:47 -04:00
|
|
|
assert!(!Operand::Equal.validate(&x, &y));
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
2025-12-06 01:03:43 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn does_greater() {
|
|
|
|
|
let data: Vec<Field> = vec![1.into(), 2.into(), 3.into()];
|
|
|
|
|
assert!(!Operand::GreaterThan.validate(&data[0], &data[1]));
|
|
|
|
|
assert!(!Operand::GreaterThan.validate(&data[1], &data[1]));
|
|
|
|
|
assert!(Operand::GreaterThan.validate(&data[2], &data[1]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn does_greater_equal() {
|
|
|
|
|
let data: Vec<Field> = vec![1.into(), 2.into(), 3.into()];
|
|
|
|
|
assert!(!Operand::GreaterThanEqual.validate(&data[0], &data[1]));
|
|
|
|
|
assert!(Operand::GreaterThanEqual.validate(&data[1], &data[1]));
|
|
|
|
|
assert!(Operand::GreaterThanEqual.validate(&data[2], &data[1]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn does_lesser() {
|
|
|
|
|
let data: Vec<Field> = vec![1.into(), 2.into(), 3.into()];
|
|
|
|
|
assert!(Operand::LessThan.validate(&data[0], &data[1]));
|
|
|
|
|
assert!(!Operand::LessThan.validate(&data[1], &data[1]));
|
|
|
|
|
assert!(!Operand::LessThan.validate(&data[2], &data[1]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn does_lesser_equal() {
|
|
|
|
|
let data: Vec<Field> = vec![1.into(), 2.into(), 3.into()];
|
|
|
|
|
assert!(Operand::LessThanEqual.validate(&data[0], &data[1]));
|
|
|
|
|
assert!(Operand::LessThanEqual.validate(&data[1], &data[1]));
|
|
|
|
|
assert!(!Operand::LessThanEqual.validate(&data[2], &data[1]));
|
|
|
|
|
}
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-18 13:30:20 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub enum CalcValue {
|
2025-09-21 11:11:45 -04:00
|
|
|
Calculate(Calculation),
|
2025-10-28 15:23:40 -04:00
|
|
|
Existing(FieldType),
|
2025-09-18 13:30:20 -04:00
|
|
|
FType(FieldType),
|
2025-11-01 02:52:36 -04:00
|
|
|
None,
|
2025-09-18 13:30:20 -04:00
|
|
|
Value(Field),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CalcValue {
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn get(&self, existing: &Field) -> Field {
|
2025-09-18 13:30:20 -04:00
|
|
|
match self {
|
2025-10-28 15:23:40 -04:00
|
|
|
Self::Calculate(calc) => calc.calculate(existing),
|
2025-10-31 15:35:17 -04:00
|
|
|
Self::Existing(_) => existing.clone(),
|
2025-09-18 13:30:20 -04:00
|
|
|
Self::FType(ftype) => ftype.get_default(),
|
2025-11-01 02:52:36 -04:00
|
|
|
Self::None => Field::None,
|
2025-09-18 13:30:20 -04:00
|
|
|
Self::Value(field) => field.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
|
|
|
|
|
fn get_type(&self) -> FieldType {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Calculate(calc) => calc.get_type(),
|
|
|
|
|
Self::Existing(ftype) => ftype.clone(),
|
|
|
|
|
Self::FType(ftype) => ftype.clone(),
|
2025-11-01 02:52:36 -04:00
|
|
|
Self::None => FieldType::None,
|
2025-10-31 15:35:17 -04:00
|
|
|
Self::Value(field) => field.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-21 11:11:45 -04:00
|
|
|
impl From<Calculation> for CalcValue {
|
|
|
|
|
fn from(value: Calculation) -> Self {
|
|
|
|
|
Self::Calculate(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-18 13:30:20 -04:00
|
|
|
impl From<Field> for CalcValue {
|
|
|
|
|
fn from(value: Field) -> Self {
|
|
|
|
|
Self::Value(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-31 15:35:17 -04:00
|
|
|
impl From<&Field> for CalcValue {
|
|
|
|
|
fn from(value: &Field) -> Self {
|
|
|
|
|
Self::from(value.clone())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-18 13:30:20 -04:00
|
|
|
impl From<FieldType> for CalcValue {
|
|
|
|
|
fn from(value: FieldType) -> Self {
|
|
|
|
|
Self::FType(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<bool> for CalcValue {
|
|
|
|
|
fn from(value: bool) -> Self {
|
|
|
|
|
let output: Field = value.into();
|
|
|
|
|
Self::from(output).into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<DateTime<Utc>> for CalcValue {
|
|
|
|
|
fn from(value: DateTime<Utc>) -> Self {
|
|
|
|
|
let output: Field = value.into();
|
|
|
|
|
Self::from(output).into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Duration> for CalcValue {
|
|
|
|
|
fn from(value: Duration) -> Self {
|
|
|
|
|
let output: Field = value.into();
|
|
|
|
|
Self::from(output).into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 08:03:05 -04:00
|
|
|
impl From<i128> for CalcValue {
|
|
|
|
|
fn from(value: i128) -> Self {
|
2025-09-18 13:30:20 -04:00
|
|
|
let output: Field = value.into();
|
|
|
|
|
Self::from(output).into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&str> for CalcValue {
|
|
|
|
|
fn from(value: &str) -> Self {
|
|
|
|
|
let output: Field = value.into();
|
|
|
|
|
Self::from(output).into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<String> for CalcValue {
|
|
|
|
|
fn from(value: String) -> Self {
|
|
|
|
|
let output: Field = value.into();
|
|
|
|
|
Self::from(output).into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Uuid> for CalcValue {
|
|
|
|
|
fn from(value: Uuid) -> Self {
|
|
|
|
|
let output: Field = value.into();
|
|
|
|
|
Self::from(output).into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod calcvalues {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_uuid() {
|
|
|
|
|
let value = Uuid::new_v4();
|
|
|
|
|
let expected: Field = value.into();
|
|
|
|
|
let result: CalcValue = value.into();
|
2025-09-21 11:11:45 -04:00
|
|
|
match result.clone() {
|
2025-09-18 13:30:20 -04:00
|
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
2025-09-21 11:11:45 -04:00
|
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(result.get(&Field::None), expected);
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_str() {
|
|
|
|
|
let value = "something";
|
|
|
|
|
let expected: Field = value.into();
|
|
|
|
|
let result: CalcValue = value.into();
|
2025-09-21 11:11:45 -04:00
|
|
|
match result.clone() {
|
2025-09-18 13:30:20 -04:00
|
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
2025-09-21 11:11:45 -04:00
|
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(result.get(&Field::None), expected);
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_string() {
|
|
|
|
|
let value = "data".to_string();
|
|
|
|
|
let expected: Field = value.clone().into();
|
|
|
|
|
let result: CalcValue = value.into();
|
2025-09-21 11:11:45 -04:00
|
|
|
match result.clone() {
|
2025-09-18 13:30:20 -04:00
|
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
2025-09-21 11:11:45 -04:00
|
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(result.get(&Field::None), expected);
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_boolean() {
|
|
|
|
|
let value = true;
|
|
|
|
|
let expected: Field = value.clone().into();
|
|
|
|
|
let result: CalcValue = value.into();
|
2025-09-21 11:11:45 -04:00
|
|
|
match result.clone() {
|
2025-09-18 13:30:20 -04:00
|
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
2025-09-21 11:11:45 -04:00
|
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(result.get(&Field::None), expected);
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_datetime() {
|
|
|
|
|
let value = Utc::now();
|
|
|
|
|
let expected: Field = value.clone().into();
|
|
|
|
|
let result: CalcValue = value.into();
|
2025-09-21 11:11:45 -04:00
|
|
|
match result.clone() {
|
2025-09-18 13:30:20 -04:00
|
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
2025-09-21 11:11:45 -04:00
|
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(result.get(&Field::None), expected);
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_duration() {
|
|
|
|
|
let value = Duration::from_secs(5);
|
|
|
|
|
let expected: Field = value.clone().into();
|
|
|
|
|
let result: CalcValue = value.into();
|
2025-09-21 11:11:45 -04:00
|
|
|
match result.clone() {
|
2025-09-18 13:30:20 -04:00
|
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
2025-09-21 11:11:45 -04:00
|
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(result.get(&Field::None), expected);
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_integer() {
|
2025-09-19 08:03:05 -04:00
|
|
|
let value: i128 = 5;
|
2025-09-18 13:30:20 -04:00
|
|
|
let expected: Field = value.clone().into();
|
|
|
|
|
let result: CalcValue = value.into();
|
2025-09-21 11:11:45 -04:00
|
|
|
match result.clone() {
|
2025-09-18 13:30:20 -04:00
|
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
2025-09-21 11:11:45 -04:00
|
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(result.get(&Field::None), expected);
|
2025-09-21 11:11:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn from_calculation() {
|
|
|
|
|
let duration = Duration::from_secs(300);
|
|
|
|
|
let start = Utc::now() + duration;
|
|
|
|
|
let mut calc = Calculation::new(Operand::Add);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(FieldType::DateTime).unwrap();
|
|
|
|
|
calc.add_value(duration.clone()).unwrap();
|
2025-09-21 11:11:45 -04:00
|
|
|
let result: CalcValue = calc.into();
|
2025-10-31 15:35:17 -04:00
|
|
|
let data = match result.get(&Field::None) {
|
2025-09-21 11:11:45 -04:00
|
|
|
Field::DateTime(data) => data,
|
|
|
|
|
_ => unreachable!(),
|
|
|
|
|
};
|
|
|
|
|
let stop = Utc::now() + duration;
|
|
|
|
|
assert!(
|
|
|
|
|
data > start && data < stop,
|
|
|
|
|
"should be about 5 minutes ahead"
|
|
|
|
|
);
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Calculation {
|
2025-09-18 13:30:20 -04:00
|
|
|
operation: Operand,
|
|
|
|
|
values: Vec<CalcValue>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Calculation {
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn new(operand: Operand) -> Self {
|
2025-09-18 13:30:20 -04:00
|
|
|
Self {
|
|
|
|
|
operation: operand,
|
|
|
|
|
values: Vec::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-09-20 21:57:48 -04:00
|
|
|
fn operation(&self) -> &Operand {
|
|
|
|
|
&self.operation
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-10-28 15:23:40 -04:00
|
|
|
fn get_fields(&self, existing: Field) -> Vec<Field> {
|
2025-09-18 13:30:20 -04:00
|
|
|
let mut output = Vec::new();
|
|
|
|
|
for item in self.values.iter() {
|
2025-10-31 15:35:17 -04:00
|
|
|
output.push(item.get(&existing));
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-12 15:05:45 -05:00
|
|
|
pub fn get_type(&self) -> FieldType {
|
2025-10-31 15:35:17 -04:00
|
|
|
if self.values.is_empty() {
|
|
|
|
|
FieldType::None
|
|
|
|
|
} else {
|
|
|
|
|
self.values[0].get_type()
|
2025-10-28 15:23:40 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn add_value<CV>(&mut self, data: CV) -> Result<(), MTTError>
|
2025-09-18 13:30:20 -04:00
|
|
|
where
|
|
|
|
|
CV: Into<CalcValue>,
|
|
|
|
|
{
|
|
|
|
|
let holder: CalcValue = data.into();
|
2025-10-28 15:23:40 -04:00
|
|
|
if self.values.is_empty() {
|
2025-09-18 13:30:20 -04:00
|
|
|
self.values.push(holder);
|
2025-10-28 15:23:40 -04:00
|
|
|
return Ok(());
|
|
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
let mut base = self.get_type();
|
2025-10-28 15:23:40 -04:00
|
|
|
match self.operation {
|
2025-11-06 08:32:10 -05:00
|
|
|
Operand::Add => match base {
|
|
|
|
|
FieldType::DateTime => base = FieldType::Duration,
|
|
|
|
|
_ => {}
|
|
|
|
|
},
|
|
|
|
|
_ => {}
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-11-06 08:07:43 -05:00
|
|
|
let ftype = holder.get_type();
|
|
|
|
|
if base == ftype {
|
|
|
|
|
self.values.push(holder);
|
|
|
|
|
} else {
|
|
|
|
|
return Err(MTTError::DocumentFieldWrongDataType(base, ftype));
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-10-31 15:35:17 -04:00
|
|
|
fn validate_value<CV>(&self, value: CV) -> Result<(), MTTError>
|
|
|
|
|
where
|
|
|
|
|
CV: Into<CalcValue>,
|
|
|
|
|
{
|
|
|
|
|
if self.values.is_empty() {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
let holder = value.into();
|
|
|
|
|
let mut base = self.get_type();
|
2025-09-18 13:30:20 -04:00
|
|
|
match self.operation {
|
|
|
|
|
Operand::Add => {
|
2025-10-31 15:35:17 -04:00
|
|
|
if base == FieldType::DateTime {
|
|
|
|
|
base = FieldType::Duration;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
let ftype = holder.get_type();
|
|
|
|
|
if base == ftype {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err(MTTError::DocumentFieldWrongDataType(base, ftype))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn calculate(&self, existing: &Field) -> Field {
|
2025-10-31 15:35:17 -04:00
|
|
|
let mut result = Field::None;
|
|
|
|
|
match self.operation {
|
|
|
|
|
Operand::Add => {
|
|
|
|
|
let mut first = true;
|
|
|
|
|
for value in self.values.iter() {
|
|
|
|
|
let data = value.get(existing);
|
|
|
|
|
if first {
|
|
|
|
|
result = data;
|
|
|
|
|
first = false;
|
|
|
|
|
} else {
|
|
|
|
|
result += data;
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-28 15:23:40 -04:00
|
|
|
Operand::Equal => {
|
2025-10-31 15:35:17 -04:00
|
|
|
if self.values.len() >= 2 {
|
|
|
|
|
result = self.values[0]
|
|
|
|
|
.get(existing)
|
|
|
|
|
.equal(&self.values[1].get(existing));
|
|
|
|
|
}
|
2025-10-28 15:23:40 -04:00
|
|
|
}
|
2025-12-06 01:03:43 -05:00
|
|
|
Operand::GreaterThan => {
|
|
|
|
|
if self.values.len() >= 2 {
|
|
|
|
|
result = self.values[0]
|
|
|
|
|
.get(existing)
|
|
|
|
|
.greater(&self.values[1].get(existing));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Operand::GreaterThanEqual => {
|
|
|
|
|
if self.values.len() >= 2 {
|
|
|
|
|
result = self.values[0]
|
|
|
|
|
.get(existing)
|
|
|
|
|
.greater_equal(&self.values[1].get(existing));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Operand::LessThan => {
|
|
|
|
|
if self.values.len() >= 2 {
|
|
|
|
|
result = self.values[0]
|
|
|
|
|
.get(existing)
|
|
|
|
|
.lesser(&self.values[1].get(existing));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Operand::LessThanEqual => {
|
|
|
|
|
if self.values.len() >= 2 {
|
|
|
|
|
result = self.values[0]
|
|
|
|
|
.get(existing)
|
|
|
|
|
.lesser_equal(&self.values[1].get(existing));
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
result
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod calculations {
|
|
|
|
|
use super::*;
|
|
|
|
|
use rand::random;
|
|
|
|
|
|
2025-09-20 21:57:48 -04:00
|
|
|
#[test]
|
|
|
|
|
fn errors_on_different_field_types() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
|
|
|
calc.add_value(Uuid::nil()).unwrap();
|
|
|
|
|
match calc.add_value("other") {
|
|
|
|
|
Ok(_) => unreachable!("should have errored with wrong type"),
|
|
|
|
|
Err(err) => match err {
|
|
|
|
|
MTTError::DocumentFieldWrongDataType(expected, got) => {
|
|
|
|
|
assert_eq!(expected, FieldType::Uuid);
|
|
|
|
|
assert_eq!(got, FieldType::StaticString);
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?}, expected wrong field type", err),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn returns_reference_to_operand() {
|
2025-11-01 02:52:36 -04:00
|
|
|
let calc = Calculation::new(Operand::Add);
|
2025-09-20 21:57:48 -04:00
|
|
|
match calc.operation() {
|
2025-11-01 02:52:36 -04:00
|
|
|
Operand::Add => {}
|
2025-09-20 21:57:48 -04:00
|
|
|
_ => unreachable!("got {:?}, shold have gotten assign", calc.operation()),
|
|
|
|
|
}
|
|
|
|
|
let calc = Calculation::new(Operand::Equal);
|
|
|
|
|
match calc.operation() {
|
|
|
|
|
Operand::Equal => {}
|
|
|
|
|
_ => unreachable!("got {:?}, shold have gotten assign", calc.operation()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-18 13:30:20 -04:00
|
|
|
#[test]
|
|
|
|
|
fn can_equal_true() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
|
|
|
let data: Field = Uuid::new_v4().into();
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(data.clone()).unwrap();
|
|
|
|
|
calc.add_value(data.clone()).unwrap();
|
2025-09-18 13:30:20 -04:00
|
|
|
let expected: Field = true.into();
|
2025-10-31 15:35:17 -04:00
|
|
|
let result = calc.calculate(&Field::None);
|
2025-09-18 13:30:20 -04:00
|
|
|
assert_eq!(result, expected);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_equal_false() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
|
|
|
let value1: Field = "fred".into();
|
|
|
|
|
let value2: Field = "barney".into();
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(value1).unwrap();
|
|
|
|
|
calc.add_value(value2).unwrap();
|
2025-09-18 13:30:20 -04:00
|
|
|
let expected: Field = false.into();
|
2025-10-31 15:35:17 -04:00
|
|
|
let result = calc.calculate(&Field::None);
|
2025-09-18 13:30:20 -04:00
|
|
|
assert_eq!(result, expected);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 01:03:43 -05:00
|
|
|
#[test]
|
|
|
|
|
fn can_greater_than() {
|
|
|
|
|
let data: Vec<(Field, Field)> = vec![
|
|
|
|
|
(0.into(), false.into()),
|
|
|
|
|
(1.into(), false.into()),
|
|
|
|
|
(2.into(), true.into()),
|
|
|
|
|
];
|
|
|
|
|
for (item, expected) in data.iter() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::GreaterThan);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(item.clone()).unwrap();
|
|
|
|
|
calc.add_value(data[1].0.clone()).unwrap();
|
2025-12-06 01:03:43 -05:00
|
|
|
let result = calc.calculate(&Field::None);
|
|
|
|
|
assert_eq!(&result, expected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_greater_than_equal() {
|
|
|
|
|
let data: Vec<(Field, Field)> = vec![
|
|
|
|
|
(0.into(), false.into()),
|
|
|
|
|
(1.into(), true.into()),
|
|
|
|
|
(2.into(), true.into()),
|
|
|
|
|
];
|
|
|
|
|
for (item, expected) in data.iter() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::GreaterThanEqual);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(item.clone()).unwrap();
|
|
|
|
|
calc.add_value(data[1].0.clone()).unwrap();
|
2025-12-06 01:03:43 -05:00
|
|
|
let result = calc.calculate(&Field::None);
|
|
|
|
|
assert_eq!(&result, expected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_lesser_than() {
|
|
|
|
|
let data: Vec<(Field, Field)> = vec![
|
|
|
|
|
(0.into(), true.into()),
|
|
|
|
|
(1.into(), false.into()),
|
|
|
|
|
(2.into(), false.into()),
|
|
|
|
|
];
|
|
|
|
|
for (item, expected) in data.iter() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::LessThan);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(item.clone()).unwrap();
|
|
|
|
|
calc.add_value(data[1].0.clone()).unwrap();
|
2025-12-06 01:03:43 -05:00
|
|
|
let result = calc.calculate(&Field::None);
|
|
|
|
|
assert_eq!(&result, expected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_lesser_than_equal() {
|
|
|
|
|
let data: Vec<(Field, Field)> = vec![
|
|
|
|
|
(0.into(), true.into()),
|
|
|
|
|
(1.into(), true.into()),
|
|
|
|
|
(2.into(), false.into()),
|
|
|
|
|
];
|
|
|
|
|
for (item, expected) in data.iter() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::LessThanEqual);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(item.clone()).unwrap();
|
|
|
|
|
calc.add_value(data[1].0.clone()).unwrap();
|
2025-12-06 01:03:43 -05:00
|
|
|
let result = calc.calculate(&Field::None);
|
|
|
|
|
assert_eq!(&result, expected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-18 13:30:20 -04:00
|
|
|
#[test]
|
|
|
|
|
fn can_add_numbers() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::Add);
|
2025-09-19 08:03:05 -04:00
|
|
|
let value1: i128 = random::<u8>().into();
|
|
|
|
|
let value2: i128 = random::<u8>().into();
|
2025-09-18 13:30:20 -04:00
|
|
|
let expected: Field = { value1 + value2 }.into();
|
|
|
|
|
let value1: Field = value1.into();
|
|
|
|
|
let value2: Field = value2.into();
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(value1.clone()).unwrap();
|
|
|
|
|
calc.add_value(value2.clone()).unwrap();
|
2025-10-31 15:35:17 -04:00
|
|
|
let result = calc.calculate(&Field::None);
|
2025-10-28 15:23:40 -04:00
|
|
|
assert_eq!(
|
|
|
|
|
result, expected,
|
|
|
|
|
"{:?} plus {:?} should equal {:?}",
|
|
|
|
|
value1, value2, expected
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_use_existing_values() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::Add);
|
|
|
|
|
let value1: i128 = random::<u8>().into();
|
|
|
|
|
let value2: i128 = random::<u8>().into();
|
|
|
|
|
let expected: Field = { value1 + value2 }.into();
|
|
|
|
|
let value1: Field = value1.into();
|
|
|
|
|
let value2: Field = value2.into();
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(value1.clone()).unwrap();
|
|
|
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
|
|
|
.unwrap();
|
2025-10-31 15:35:17 -04:00
|
|
|
let result = calc.calculate(&value2);
|
2025-10-28 15:23:40 -04:00
|
|
|
assert_eq!(
|
|
|
|
|
result, expected,
|
|
|
|
|
"{:?} plus {:?} should equal {:?}",
|
|
|
|
|
value1, value2, expected
|
|
|
|
|
);
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn returns_error_on_mismatch() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::Add);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(Uuid::nil()).unwrap();
|
2025-09-18 13:30:20 -04:00
|
|
|
match calc.add_value("mismatch") {
|
|
|
|
|
Ok(_) => unreachable!("should have returned an error"),
|
2025-09-19 08:03:05 -04:00
|
|
|
Err(err) => match err {
|
|
|
|
|
MTTError::DocumentFieldWrongDataType(expected, got) => {
|
|
|
|
|
assert_eq!(got, FieldType::StaticString);
|
|
|
|
|
assert_eq!(expected, FieldType::Uuid);
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?}, expected wrong field type", err),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn datetime_accepts_duration() {
|
|
|
|
|
let mut calc = Calculation::new(Operand::Add);
|
|
|
|
|
let duration = Duration::from_secs(3600);
|
|
|
|
|
let start = Utc::now() + duration;
|
|
|
|
|
calc.add_value(FieldType::DateTime).unwrap();
|
|
|
|
|
match calc.add_value(duration.clone()) {
|
|
|
|
|
Ok(_) => {}
|
|
|
|
|
Err(err) => unreachable!("got {:?}, should have returned normally", err),
|
|
|
|
|
}
|
2025-10-31 15:35:17 -04:00
|
|
|
let result = calc.calculate(&Field::None);
|
2025-09-19 08:03:05 -04:00
|
|
|
let stop = Utc::now() + duration;
|
|
|
|
|
match result {
|
|
|
|
|
Field::DateTime(data) => {
|
|
|
|
|
assert!(data > start);
|
|
|
|
|
assert!(data < stop);
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?}, should have been datetime", result),
|
2025-09-18 13:30:20 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-08-11 12:17:37 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2026-01-09 11:39:14 -05:00
|
|
|
pub struct Operation {
|
2025-08-11 12:17:37 -04:00
|
|
|
field_name: String,
|
|
|
|
|
operation: Operand,
|
|
|
|
|
value: Field,
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-09-16 09:05:42 -04:00
|
|
|
impl Operation {
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn new<F>(name: String, op: Operand, value: F) -> Self
|
2025-08-11 12:17:37 -04:00
|
|
|
where
|
|
|
|
|
F: Into<Field>,
|
|
|
|
|
{
|
|
|
|
|
Self {
|
|
|
|
|
field_name: name,
|
|
|
|
|
operation: op,
|
|
|
|
|
value: value.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-13 12:45:20 -04:00
|
|
|
|
|
|
|
|
fn which_field(&self) -> String {
|
|
|
|
|
self.field_name.clone()
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn validate(&self, field: &Field) -> bool {
|
2025-09-14 10:53:47 -04:00
|
|
|
self.operation.validate(field, &self.value)
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
2025-08-11 12:17:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-11-07 12:08:08 -05:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
enum QueryType {
|
|
|
|
|
Query(Query),
|
|
|
|
|
Oids(HashSet<Oid>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Query> for QueryType {
|
|
|
|
|
fn from(value: Query) -> Self {
|
|
|
|
|
QueryType::Query(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<HashSet<Oid>> for QueryType {
|
|
|
|
|
fn from(value: HashSet<Oid>) -> Self {
|
|
|
|
|
QueryType::Oids(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 12:17:37 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Query {
|
2025-10-08 19:00:02 -04:00
|
|
|
data: HashMap<NameType, Calculation>,
|
2025-08-11 12:17:37 -04:00
|
|
|
}
|
2025-07-28 10:49:34 -04:00
|
|
|
|
2025-08-05 09:56:48 -04:00
|
|
|
impl Query {
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn new() -> Self {
|
2025-08-11 12:17:37 -04:00
|
|
|
Self {
|
2025-09-20 21:57:48 -04:00
|
|
|
data: HashMap::new(),
|
2025-08-11 12:17:37 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn add<NT>(&mut self, name: NT, operation: Calculation)
|
2025-10-14 14:29:11 -04:00
|
|
|
where
|
|
|
|
|
NT: Into<NameType>,
|
|
|
|
|
{
|
2025-12-13 17:33:37 -05:00
|
|
|
self.data.insert(name.into(), operation);
|
2025-09-20 21:57:48 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-10-14 14:29:11 -04:00
|
|
|
fn get<NT>(&self, name: NT) -> Option<Calculation>
|
|
|
|
|
where
|
|
|
|
|
NT: Into<NameType>,
|
|
|
|
|
{
|
2025-10-08 19:00:02 -04:00
|
|
|
match self.data.get(&name.into()) {
|
2025-09-20 21:57:48 -04:00
|
|
|
Some(calc) => Some(calc.clone()),
|
|
|
|
|
None => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-10-08 19:00:02 -04:00
|
|
|
fn field_ids(&self) -> HashSet<&NameType> {
|
|
|
|
|
self.data.keys().collect()
|
2025-09-20 21:57:48 -04:00
|
|
|
}
|
2025-10-08 19:00:02 -04:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item = (&NameType, &Calculation)> {
|
2025-10-08 19:00:02 -04:00
|
|
|
self.data.iter()
|
|
|
|
|
}
|
2025-09-20 21:57:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod queries {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn holds_calculation_to_run_query() {
|
2025-10-21 07:27:48 -04:00
|
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
2025-09-20 21:57:48 -04:00
|
|
|
let data = Uuid::new_v4();
|
|
|
|
|
let mut bad_data = data.clone();
|
|
|
|
|
while bad_data == data {
|
|
|
|
|
bad_data = Uuid::new_v4();
|
|
|
|
|
}
|
|
|
|
|
let mut query = Query::new();
|
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(data.clone()).unwrap();
|
2025-09-20 21:57:48 -04:00
|
|
|
query.add(name.clone(), calc);
|
|
|
|
|
match query.get(&name) {
|
|
|
|
|
Some(op) => {
|
|
|
|
|
let expected: Field = true.into();
|
|
|
|
|
let mut holder = op.clone();
|
2025-12-23 22:15:49 -05:00
|
|
|
holder.add_value(data).unwrap();
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(holder.calculate(&Field::None), expected);
|
2025-09-20 21:57:48 -04:00
|
|
|
}
|
|
|
|
|
None => unreachable!("should have returned a calculation"),
|
|
|
|
|
}
|
|
|
|
|
match query.get(&name) {
|
|
|
|
|
Some(op) => {
|
|
|
|
|
let expected: Field = false.into();
|
|
|
|
|
let mut holder = op.clone();
|
2025-12-23 22:15:49 -05:00
|
|
|
holder.add_value(bad_data).unwrap();
|
2025-10-31 15:35:17 -04:00
|
|
|
assert_eq!(holder.calculate(&Field::None), expected);
|
2025-09-20 21:57:48 -04:00
|
|
|
}
|
|
|
|
|
None => unreachable!("should have returned a calculation"),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-28 10:49:34 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-08-01 10:58:40 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-23 22:15:49 -05:00
|
|
|
pub struct Reply {
|
2025-08-06 13:41:35 -04:00
|
|
|
data: Vec<Document>,
|
|
|
|
|
}
|
2025-07-30 08:37:58 -04:00
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-08-05 09:56:48 -04:00
|
|
|
impl Reply {
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn new() -> Self {
|
2025-08-06 13:41:35 -04:00
|
|
|
Self { data: Vec::new() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn add(&mut self, doc: Document) {
|
|
|
|
|
self.data.push(doc);
|
2025-07-30 08:37:58 -04:00
|
|
|
}
|
2025-08-05 09:56:48 -04:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn len(&self) -> usize {
|
2025-08-06 13:41:35 -04:00
|
|
|
self.data.len()
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 12:17:37 -04:00
|
|
|
fn iter(&self) -> impl Iterator<Item = &Document> {
|
|
|
|
|
self.data.iter()
|
2025-08-05 09:56:48 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod replies {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn is_new_empty() {
|
|
|
|
|
let reply = Reply::new();
|
2025-08-06 13:41:35 -04:00
|
|
|
assert_eq!(reply.len(), 0, "should have no records");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_add_documents() {
|
|
|
|
|
let mut reply = Reply::new();
|
|
|
|
|
let doc = Document::new();
|
|
|
|
|
reply.add(doc.clone());
|
|
|
|
|
assert_eq!(reply.len(), 1);
|
|
|
|
|
reply.add(doc.clone());
|
|
|
|
|
assert_eq!(reply.len(), 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_retrieve_documents() {
|
2025-10-17 10:15:15 -04:00
|
|
|
let fieldname = Name::english("field");
|
2025-08-06 13:41:35 -04:00
|
|
|
let mut doc1 = Document::new();
|
|
|
|
|
doc1.add_field(fieldname.clone(), "one");
|
|
|
|
|
let mut doc2 = Document::new();
|
|
|
|
|
doc2.add_field(fieldname.clone(), "two");
|
|
|
|
|
let mut reply = Reply::new();
|
|
|
|
|
reply.add(doc1);
|
|
|
|
|
reply.add(doc2);
|
2025-08-11 12:17:37 -04:00
|
|
|
let mut reply_iter = reply.iter();
|
2025-12-23 22:15:49 -05:00
|
|
|
let result1 = reply_iter.next().unwrap();
|
2025-11-01 11:56:36 -04:00
|
|
|
match result1.get_field(&fieldname) {
|
|
|
|
|
CalcValue::Value(data) => match data {
|
|
|
|
|
Field::StaticString(output) => assert_eq!(output, "one"),
|
|
|
|
|
_ => unreachable!("got {:?}: should have been static string", result1),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("got {:?}, should have been value", result1),
|
2025-08-06 13:41:35 -04:00
|
|
|
}
|
2025-08-07 13:51:22 -04:00
|
|
|
let result2 = reply_iter.next().unwrap();
|
2025-11-01 11:56:36 -04:00
|
|
|
match result2.get_field(&fieldname) {
|
|
|
|
|
CalcValue::Value(data) => match data {
|
|
|
|
|
Field::StaticString(output) => assert_eq!(output, "two"),
|
|
|
|
|
_ => unreachable!("got {:?}: should have been static string", result2),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("got {:?}, should have been value", result2),
|
2025-08-06 13:41:35 -04:00
|
|
|
}
|
2025-08-07 13:51:22 -04:00
|
|
|
match reply_iter.next() {
|
2025-08-06 13:41:35 -04:00
|
|
|
None => {}
|
|
|
|
|
Some(_) => unreachable!("should be out of data"),
|
|
|
|
|
}
|
2025-08-05 09:56:48 -04:00
|
|
|
}
|
2025-07-30 08:37:58 -04:00
|
|
|
}
|
|
|
|
|
|
2025-10-16 12:02:29 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2026-01-09 11:39:14 -05:00
|
|
|
pub struct InternalRecord {
|
2025-10-16 12:02:29 -04:00
|
|
|
data: HashMap<Uuid, Field>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl InternalRecord {
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn new() -> Self {
|
2025-10-16 12:02:29 -04:00
|
|
|
Self {
|
|
|
|
|
data: HashMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn insert<F>(&mut self, id: Uuid, data: F) -> Field
|
2025-10-18 13:03:36 -04:00
|
|
|
where
|
|
|
|
|
F: Into<Field>,
|
|
|
|
|
{
|
2025-11-01 11:56:36 -04:00
|
|
|
match self.data.insert(id, data.into()) {
|
|
|
|
|
Some(data) => data.clone(),
|
|
|
|
|
None => Field::None,
|
|
|
|
|
}
|
2025-10-16 12:02:29 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn get(&self, id: &Uuid) -> Option<&Field> {
|
2025-10-16 12:02:29 -04:00
|
|
|
self.data.get(id)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn is_empty(&self) -> bool {
|
2025-10-16 12:02:29 -04:00
|
|
|
self.data.is_empty()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-18 17:10:28 -05:00
|
|
|
#[derive(Clone, Debug)]
|
2026-01-09 11:39:14 -05:00
|
|
|
pub struct InternalRecords {
|
2025-11-18 17:10:28 -05:00
|
|
|
data: HashMap<Oid, InternalRecord>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl InternalRecords {
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn new() -> Self {
|
2025-11-18 17:10:28 -05:00
|
|
|
Self {
|
|
|
|
|
data: HashMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn insert(&mut self, oid: Oid, record: InternalRecord) -> Option<InternalRecord> {
|
2025-11-18 17:10:28 -05:00
|
|
|
self.data.insert(oid, record)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn get(&self, oid: &Oid) -> Option<&InternalRecord> {
|
2025-11-18 17:10:28 -05:00
|
|
|
self.data.get(oid)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn remove(&mut self, oid: &Oid) -> Option<InternalRecord> {
|
2025-11-18 17:10:28 -05:00
|
|
|
self.data.remove(oid)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item = (&Oid, &InternalRecord)> {
|
2025-11-18 17:10:28 -05:00
|
|
|
self.data.iter()
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn keys(&self) -> impl Iterator<Item = &Oid> {
|
2025-11-18 17:10:28 -05:00
|
|
|
self.data.keys()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn values(&self) -> impl Iterator<Item = &InternalRecord> {
|
|
|
|
|
self.data.values()
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn contains_key(&self, oid: &Oid) -> bool {
|
2025-11-18 17:10:28 -05:00
|
|
|
self.data.contains_key(oid)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn len(&self) -> usize {
|
|
|
|
|
self.data.len()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Record {
|
2025-11-18 17:10:28 -05:00
|
|
|
names: Names,
|
|
|
|
|
data: InternalRecord,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Record {
|
|
|
|
|
fn with_data(names: Names, rec: InternalRecord) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
names: names,
|
|
|
|
|
data: rec,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn get<NT>(&self, field_id: NT) -> Result<Field, MTTError>
|
2025-11-18 17:10:28 -05:00
|
|
|
where
|
|
|
|
|
NT: Into<NameType>,
|
|
|
|
|
{
|
|
|
|
|
let id = match self.names.get_id(field_id) {
|
|
|
|
|
Ok(data) => data,
|
|
|
|
|
Err(err) => return Err(err),
|
|
|
|
|
};
|
|
|
|
|
match self.data.get(&id) {
|
|
|
|
|
Some(data) => Ok(data.clone()),
|
|
|
|
|
None => Err(MTTError::FieldMissingData),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Records {
|
2025-11-18 17:10:28 -05:00
|
|
|
names: Names,
|
|
|
|
|
data: InternalRecords,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Records {
|
2025-12-28 15:46:50 -05:00
|
|
|
pub fn new(names: Names) -> Self {
|
2025-11-18 17:10:28 -05:00
|
|
|
Self {
|
|
|
|
|
names: names,
|
|
|
|
|
data: InternalRecords::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn with_data(names: Names, records: InternalRecords) -> Self {
|
2025-11-18 17:10:28 -05:00
|
|
|
Self {
|
|
|
|
|
names: names,
|
|
|
|
|
data: records,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn insert(&mut self, oid: Oid, record: InternalRecord) -> Option<InternalRecord> {
|
2025-11-18 17:10:28 -05:00
|
|
|
self.data.insert(oid, record)
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn len(&self) -> usize {
|
2025-11-18 17:10:28 -05:00
|
|
|
self.data.len()
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item = Record> {
|
2025-11-18 17:10:28 -05:00
|
|
|
RecordIter::new(self)
|
|
|
|
|
}
|
2025-12-02 14:48:26 -05:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn get_internal_records(&self) -> &InternalRecords {
|
2025-12-02 14:48:26 -05:00
|
|
|
&self.data
|
|
|
|
|
}
|
2025-11-18 17:10:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct RecordIter {
|
|
|
|
|
names: Names,
|
|
|
|
|
recs: Vec<InternalRecord>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl RecordIter {
|
|
|
|
|
fn new(records: &Records) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
names: records.names.clone(),
|
|
|
|
|
recs: records.data.values().cloned().collect(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Iterator for RecordIter {
|
|
|
|
|
type Item = Record;
|
|
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
|
match self.recs.pop() {
|
|
|
|
|
Some(rec) => Some(Record::with_data(self.names.clone(), rec.clone())),
|
|
|
|
|
None => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-06 13:41:35 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Document {
|
2025-10-08 19:00:02 -04:00
|
|
|
data: HashMap<NameType, CalcValue>,
|
2025-08-05 09:56:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Document {
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
data: HashMap::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-06 13:41:35 -04:00
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn add_field<NT, CV>(&mut self, name: NT, field: CV)
|
2025-08-06 13:41:35 -04:00
|
|
|
where
|
2025-09-21 10:12:07 -04:00
|
|
|
CV: Into<CalcValue>,
|
2025-10-08 19:00:02 -04:00
|
|
|
NT: Into<NameType>,
|
2025-08-06 13:41:35 -04:00
|
|
|
{
|
2025-10-08 19:00:02 -04:00
|
|
|
self.data.insert(name.into(), field.into());
|
2025-08-06 13:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 11:56:36 -04:00
|
|
|
fn get_field<NT>(&self, name: NT) -> &CalcValue
|
2025-10-14 14:29:11 -04:00
|
|
|
where
|
|
|
|
|
NT: Into<NameType>,
|
|
|
|
|
{
|
2025-10-08 19:00:02 -04:00
|
|
|
match self.data.get(&name.into()) {
|
2025-11-01 11:56:36 -04:00
|
|
|
Some(data) => data,
|
|
|
|
|
None => &CalcValue::None,
|
2025-09-21 10:12:07 -04:00
|
|
|
}
|
2025-08-06 13:41:35 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-10-08 19:00:02 -04:00
|
|
|
fn get_all(&self) -> Vec<(NameType, Field)> {
|
2025-09-21 10:12:07 -04:00
|
|
|
let mut output = Vec::new();
|
|
|
|
|
for (key, value) in self.data.iter() {
|
2025-10-31 15:35:17 -04:00
|
|
|
output.push((key.clone(), value.get(&Field::None)));
|
2025-09-21 10:12:07 -04:00
|
|
|
}
|
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn iter(&self) -> impl Iterator<Item = (&NameType, &CalcValue)> {
|
2025-11-06 08:07:43 -05:00
|
|
|
self.data.iter()
|
|
|
|
|
}
|
2025-08-10 11:14:32 -04:00
|
|
|
}
|
|
|
|
|
|
2025-08-06 13:41:35 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod documents {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn can_add_static_string() {
|
|
|
|
|
let mut add = Document::new();
|
2025-10-21 07:27:48 -04:00
|
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
2025-08-06 13:41:35 -04:00
|
|
|
let data = Uuid::new_v4().to_string();
|
|
|
|
|
add.add_field(name.clone(), data.clone());
|
2025-11-01 11:56:36 -04:00
|
|
|
let result = add.get_field(&name);
|
2025-08-06 13:41:35 -04:00
|
|
|
match result {
|
2025-11-01 11:56:36 -04:00
|
|
|
CalcValue::Value(holder) => match holder {
|
|
|
|
|
Field::StaticString(result) => assert_eq!(result, &data),
|
|
|
|
|
_ => unreachable!("got {:?}: should have received static string", holder),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("got {:?}, should have been value", result),
|
2025-08-06 13:41:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[test]
|
2025-08-06 13:41:35 -04:00
|
|
|
fn can_add_uuid() {
|
|
|
|
|
let mut add = Document::new();
|
2025-10-21 07:27:48 -04:00
|
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
2025-08-06 13:41:35 -04:00
|
|
|
let data = Uuid::new_v4();
|
|
|
|
|
add.add_field(name.clone(), data.clone());
|
2025-11-01 11:56:36 -04:00
|
|
|
let result = add.get_field(&name);
|
2025-08-06 13:41:35 -04:00
|
|
|
match result {
|
2025-11-01 11:56:36 -04:00
|
|
|
CalcValue::Value(holder) => match holder {
|
|
|
|
|
Field::Uuid(result) => assert_eq!(result, &data),
|
|
|
|
|
_ => unreachable!("got {:?}: should have received static string", holder),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("got {:?}, should have been value", result),
|
2025-08-06 13:41:35 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-05 09:56:48 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-25 09:41:17 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Delete {
|
2025-11-18 17:10:28 -05:00
|
|
|
query: Query,
|
2025-09-25 09:41:17 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Delete {
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn new(query: Query) -> Self {
|
2025-09-25 09:41:17 -04:00
|
|
|
Self {
|
2025-11-07 12:08:08 -05:00
|
|
|
query: query.into(),
|
2025-09-25 09:41:17 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn get_query(&self) -> &Query {
|
2025-09-25 09:41:17 -04:00
|
|
|
&self.query
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 08:06:22 -04:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub struct Update {
|
2025-11-18 17:10:28 -05:00
|
|
|
query: Query,
|
2025-08-26 08:06:22 -04:00
|
|
|
values: Document,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Update {
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn new(query: Query) -> Self {
|
2025-08-26 08:06:22 -04:00
|
|
|
Self {
|
2025-11-07 12:08:08 -05:00
|
|
|
query: query.into(),
|
2025-08-26 08:06:22 -04:00
|
|
|
values: Document::new(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-26 10:03:46 -04:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn get_query(&self) -> &Query {
|
2025-08-28 10:15:28 -04:00
|
|
|
&self.query
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn get_values(&self) -> &Document {
|
2025-08-28 10:15:28 -04:00
|
|
|
&self.values
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn get_values_mut(&mut self) -> &mut Document {
|
2025-08-26 10:03:46 -04:00
|
|
|
&mut self.values
|
|
|
|
|
}
|
2025-08-26 08:06:22 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-05 23:52:24 -04:00
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
2026-01-09 11:39:14 -05:00
|
|
|
pub struct Oid {
|
2025-08-28 10:15:28 -04:00
|
|
|
oid: Uuid,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Oid {
|
2026-01-09 11:39:14 -05:00
|
|
|
pub fn new() -> Self {
|
2025-08-28 10:15:28 -04:00
|
|
|
Self {
|
|
|
|
|
oid: Uuid::new_v4(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-06 08:07:43 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod oids {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn are_oids_random() {
|
|
|
|
|
let count = 10;
|
|
|
|
|
let mut holder: Vec<Oid> = Vec::new();
|
|
|
|
|
while holder.len() < count {
|
|
|
|
|
let result = Oid::new();
|
|
|
|
|
assert!(!holder.contains(&result));
|
|
|
|
|
holder.push(result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub struct MsgEntry {
|
|
|
|
|
timestamp: DateTime<Utc>,
|
|
|
|
|
message: Message,
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
|
impl MsgEntry {
|
|
|
|
|
fn new(msg: Message) -> Self {
|
2025-09-14 10:53:47 -04:00
|
|
|
Self {
|
2026-01-09 11:39:14 -05:00
|
|
|
timestamp: Utc::now(),
|
|
|
|
|
message: msg,
|
2025-09-14 10:53:47 -04:00
|
|
|
}
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
fn get_timestamp(&self) -> &DateTime<Utc> {
|
|
|
|
|
&self.timestamp
|
2025-10-18 13:03:36 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
fn get_message(&self) -> &Message {
|
|
|
|
|
&self.message
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
fn get_message_id(&self) -> &Uuid {
|
|
|
|
|
self.message.get_message_id()
|
2025-09-22 10:59:29 -04:00
|
|
|
}
|
2026-01-09 11:39:14 -05:00
|
|
|
}
|
2025-09-22 10:59:29 -04:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod msg_entries {
|
|
|
|
|
use super::*;
|
2025-09-14 10:53:47 -04:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
#[test]
|
|
|
|
|
fn creates_message_entry() {
|
|
|
|
|
let msg = Message::new(Name::english("holder"), Query::new());
|
|
|
|
|
let start = Utc::now();
|
|
|
|
|
let entry = MsgEntry::new(msg.clone());
|
|
|
|
|
let end = Utc::now();
|
|
|
|
|
assert!(
|
|
|
|
|
entry.get_timestamp() > &start,
|
|
|
|
|
"timestamp should be between start and end times"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
entry.get_timestamp() < &end,
|
|
|
|
|
"timestamp should be between start and end times"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(entry.get_message_id(), msg.get_message_id());
|
2025-09-14 10:53:47 -04:00
|
|
|
}
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
struct MsgLogs {
|
|
|
|
|
data: HashMap<Uuid, Vec<MsgEntry>>,
|
2025-09-15 09:32:45 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
|
impl MsgLogs {
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
data: HashMap::new(),
|
2025-09-15 09:32:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
fn add(&mut self, msg: Message) {
|
|
|
|
|
let entry = MsgEntry::new(msg);
|
|
|
|
|
let id = entry.get_message_id();
|
|
|
|
|
let entries = match self.data.get_mut(id) {
|
2025-09-15 09:32:45 -04:00
|
|
|
Some(data) => data,
|
2026-01-09 11:39:14 -05:00
|
|
|
None => {
|
|
|
|
|
self.data.insert(id.clone(), Vec::new());
|
|
|
|
|
self.data.get_mut(id).unwrap()
|
|
|
|
|
}
|
2025-09-15 09:32:45 -04:00
|
|
|
};
|
2026-01-09 11:39:14 -05:00
|
|
|
entries.push(entry);
|
2025-09-15 09:32:45 -04:00
|
|
|
}
|
2025-10-16 13:53:04 -04:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
fn get(&self, msg_id: &Uuid) -> Option<&Vec<MsgEntry>> {
|
|
|
|
|
self.data.get(msg_id)
|
2025-10-16 13:53:04 -04:00
|
|
|
}
|
2025-09-15 09:32:45 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-13 12:45:20 -04:00
|
|
|
#[cfg(test)]
|
2026-01-09 11:39:14 -05:00
|
|
|
mod msg_logs {
|
2025-09-13 12:45:20 -04:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-01-09 11:39:14 -05:00
|
|
|
fn can_add_message_to_log() {
|
|
|
|
|
let mut logs = MsgLogs::new();
|
|
|
|
|
let msg = Message::new(Name::english("something"), Query::new());
|
|
|
|
|
logs.add(msg.clone());
|
|
|
|
|
let result = logs.get(msg.get_message_id()).unwrap();
|
|
|
|
|
assert_eq!(result.len(), 1, "should be one entry");
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
2025-09-14 10:53:47 -04:00
|
|
|
|
|
|
|
|
#[test]
|
2026-01-09 11:39:14 -05:00
|
|
|
fn returns_none_when_no_logs_found() {
|
|
|
|
|
let logs = MsgLogs::new();
|
|
|
|
|
match logs.get(&Uuid::nil()) {
|
|
|
|
|
Some(data) => unreachable!("got {:?}, should return none", data),
|
|
|
|
|
None => (),
|
2025-09-14 10:53:47 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-01-09 11:39:14 -05:00
|
|
|
fn stores_messages_with_responses() {
|
|
|
|
|
let mut logs = MsgLogs::new();
|
|
|
|
|
let msg1 = Message::new(Name::english("something"), Query::new());
|
|
|
|
|
let msg2 = msg1.response(Records::new(Names::new()));
|
|
|
|
|
logs.add(msg1.clone());
|
|
|
|
|
logs.add(msg2.clone());
|
|
|
|
|
let result = logs.get(msg1.get_message_id()).unwrap();
|
|
|
|
|
assert_eq!(result.len(), 2, "should be two entry");
|
|
|
|
|
let action1: Action = result[0].get_message().get_action().clone().into();
|
|
|
|
|
let action2: Action = result[1].get_message().get_action().clone().into();
|
|
|
|
|
assert_eq!(action1, Action::Query);
|
|
|
|
|
assert_eq!(action2, Action::Records);
|
2025-09-14 10:53:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-01-09 11:39:14 -05:00
|
|
|
fn messages_are_stored_by_ids() {
|
|
|
|
|
let mut logs = MsgLogs::new();
|
|
|
|
|
let msg1 = Message::new(Name::english("something"), Query::new());
|
|
|
|
|
let msg2 = Message::new(Name::english("something"), Query::new());
|
|
|
|
|
logs.add(msg1.clone());
|
|
|
|
|
logs.add(msg2.clone());
|
|
|
|
|
let result1 = logs.get(msg1.get_message_id()).unwrap();
|
|
|
|
|
let result2 = logs.get(msg2.get_message_id()).unwrap();
|
|
|
|
|
assert_eq!(result1.len(), 1, "should be one entry");
|
|
|
|
|
assert_eq!(result2.len(), 1, "should be one entry");
|
|
|
|
|
assert_eq!(result1[0].get_message_id(), msg1.get_message_id());
|
|
|
|
|
assert_eq!(result2[0].get_message_id(), msg2.get_message_id());
|
2025-09-14 10:53:47 -04:00
|
|
|
}
|
2025-09-13 12:45:20 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
|
struct MessageLog {
|
|
|
|
|
data: MsgLogs,
|
2025-07-25 11:08:47 -04:00
|
|
|
queue: Queue,
|
|
|
|
|
rx: Receiver<Message>,
|
|
|
|
|
}
|
2025-07-04 10:25:37 -04:00
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
#[allow(dead_code)]
|
|
|
|
|
impl MessageLog {
|
|
|
|
|
fn new(queue: Queue, rx: Receiver<Message>) -> Self {
|
2025-07-25 11:08:47 -04:00
|
|
|
Self {
|
2026-01-09 11:39:14 -05:00
|
|
|
data: MsgLogs::new(),
|
2025-07-25 11:08:47 -04:00
|
|
|
queue: queue,
|
|
|
|
|
rx: rx,
|
|
|
|
|
}
|
2025-07-04 10:25:37 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-09 11:39:14 -05:00
|
|
|
fn start(mut queue: Queue) {
|
2025-07-25 11:08:47 -04:00
|
|
|
let (tx, rx) = channel();
|
2026-01-09 11:39:14 -05:00
|
|
|
let mut logs = MessageLog::new(queue.clone(), rx);
|
2025-10-08 19:00:02 -04:00
|
|
|
let id = queue.add_sender(tx);
|
2026-01-09 11:39:14 -05:00
|
|
|
let reg_msg = Register::new(
|
|
|
|
|
id,
|
|
|
|
|
RegMsg::AddRoute(Path::new(Include::All, Include::All, Include::All)),
|
|
|
|
|
);
|
|
|
|
|
let rmsg = Message::new(NameType::None, reg_msg);
|
2025-12-02 14:48:26 -05:00
|
|
|
queue.send(rmsg.clone()).unwrap();
|
|
|
|
|
spawn(move || {
|
|
|
|
|
logs.listen();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn listen(&mut self) {
|
|
|
|
|
loop {
|
|
|
|
|
let msg = self.rx.recv().unwrap();
|
|
|
|
|
match msg.get_action() {
|
|
|
|
|
MsgAction::GetLog(id) => match self.data.get(id) {
|
|
|
|
|
Some(data) => self
|
|
|
|
|
.queue
|
|
|
|
|
.send(msg.response(MsgAction::Log(data.clone())))
|
|
|
|
|
.unwrap(),
|
|
|
|
|
None => self
|
|
|
|
|
.queue
|
|
|
|
|
.send(msg.response(MsgAction::Log(Vec::new())))
|
|
|
|
|
.unwrap(),
|
|
|
|
|
},
|
|
|
|
|
_ => self.data.add(msg),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod message_logs {
|
2025-12-24 17:54:25 -05:00
|
|
|
use super::*;
|
2025-12-26 13:02:15 -05:00
|
|
|
use crate::support_tests::TIMEOUT;
|
2025-12-02 14:48:26 -05:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn does_log_store_messages() {
|
|
|
|
|
let doc_name = Name::english("unimportant");
|
|
|
|
|
let mut queue = Queue::new();
|
|
|
|
|
MessageLog::start(queue.clone());
|
|
|
|
|
let (tx, rx) = channel();
|
|
|
|
|
let id = queue.add_sender(tx);
|
|
|
|
|
let reg_msg = Register::new(id, RegMsg::AddDocName(vec![doc_name.clone()]));
|
|
|
|
|
let rmsg = Message::new(NameType::None, reg_msg);
|
|
|
|
|
queue.send(rmsg.clone()).unwrap();
|
|
|
|
|
let name_result = rx.recv().unwrap();
|
2025-12-23 22:15:49 -05:00
|
|
|
match name_result.get_action() {
|
2025-12-02 14:48:26 -05:00
|
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
|
|
|
RegMsg::DocumentNameID(data) => data,
|
|
|
|
|
RegMsg::Error(err) => unreachable!("got {:?}, should have gotten data", err),
|
|
|
|
|
_ => unreachable!("should only return a name id or an error"),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("should only return a name id or an error"),
|
|
|
|
|
};
|
|
|
|
|
let request = Register::new(
|
|
|
|
|
id.clone(),
|
|
|
|
|
RegMsg::AddRoute(Path::new(
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::All,
|
2025-12-17 12:14:06 -05:00
|
|
|
Include::Just(Action::Log),
|
2025-12-02 14:48:26 -05:00
|
|
|
)),
|
|
|
|
|
);
|
|
|
|
|
queue.send(Message::new(NameType::None, request)).unwrap();
|
|
|
|
|
rx.recv_timeout(TIMEOUT).unwrap();
|
|
|
|
|
let msg = Message::new(doc_name.clone(), Query::new());
|
|
|
|
|
queue.send(msg.clone()).unwrap();
|
|
|
|
|
let log_msg = Message::new(NameType::None, msg.get_message_id());
|
|
|
|
|
queue.send(log_msg.clone()).unwrap();
|
|
|
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
|
|
|
assert_eq!(result.get_message_id(), log_msg.get_message_id());
|
|
|
|
|
match result.get_action() {
|
|
|
|
|
MsgAction::Log(output) => assert_eq!(output.len(), 1),
|
|
|
|
|
_ => unreachable!("got {:?}, should have been log", result.get_action()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-17 12:14:06 -05:00
|
|
|
|
|
|
|
|
pub struct Session {
|
|
|
|
|
doc_name: Name,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Session {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
doc_name: Name::english("session"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 22:15:49 -05:00
|
|
|
#[allow(dead_code)]
|
2025-12-17 12:14:06 -05:00
|
|
|
pub fn get_document_name(&self) -> &Name {
|
|
|
|
|
&self.doc_name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn create(&self, mut queue: Queue) {
|
|
|
|
|
let mut docdef = DocDef::new(self.doc_name.clone());
|
|
|
|
|
|
|
|
|
|
let mut calc = Calculation::new(Operand::Add);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(FieldType::DateTime).unwrap();
|
|
|
|
|
calc.add_value(Duration::from_hours(1)).unwrap();
|
2025-12-17 12:14:06 -05:00
|
|
|
|
|
|
|
|
let name_id = Name::english("id");
|
|
|
|
|
docdef.add_field(name_id.clone(), FieldType::Uuid);
|
2025-12-23 22:15:49 -05:00
|
|
|
docdef.set_default(&name_id, FieldType::Uuid).unwrap();
|
2025-12-17 12:14:06 -05:00
|
|
|
docdef.add_index(&name_id, IndexType::Unique).unwrap();
|
|
|
|
|
|
|
|
|
|
let name_expire = Name::english("expire");
|
|
|
|
|
docdef.add_field(name_expire.clone(), FieldType::DateTime);
|
2025-12-23 22:15:49 -05:00
|
|
|
docdef.set_default(&name_expire, calc.clone()).unwrap();
|
2025-12-17 12:14:06 -05:00
|
|
|
|
|
|
|
|
let mut update = Update::new(Query::new());
|
|
|
|
|
update
|
|
|
|
|
.get_values_mut()
|
|
|
|
|
.add_field(name_expire.clone(), calc.clone());
|
|
|
|
|
let path = Path::new(
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::Just(self.doc_name.clone().into()),
|
|
|
|
|
Include::Just(Action::OnQuery),
|
|
|
|
|
);
|
|
|
|
|
let query_action = DocFuncType::ExistingQuery(update.into());
|
|
|
|
|
docdef.add_route(path, query_action);
|
|
|
|
|
|
|
|
|
|
let mut delete_qry = Query::new();
|
|
|
|
|
let mut delete_calc = Calculation::new(Operand::LessThan);
|
2025-12-23 22:15:49 -05:00
|
|
|
delete_calc.add_value(FieldType::DateTime).unwrap();
|
|
|
|
|
delete_calc
|
|
|
|
|
.add_value(CalcValue::Existing(FieldType::DateTime))
|
|
|
|
|
.unwrap();
|
2025-12-17 12:14:06 -05:00
|
|
|
delete_qry.add(name_expire.clone(), delete_calc);
|
|
|
|
|
let delete = Delete::new(delete_qry);
|
|
|
|
|
let clock_path = Path::new(
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::Just(Name::english("clock").into()),
|
|
|
|
|
Include::Just(Action::OnUpdate),
|
|
|
|
|
);
|
|
|
|
|
let delete_func = DocFuncType::Trigger(delete.into());
|
|
|
|
|
docdef.add_route(clock_path, delete_func);
|
|
|
|
|
|
|
|
|
|
let (tx, rx) = channel();
|
|
|
|
|
let sender_id = queue.add_sender(tx);
|
|
|
|
|
let msg = Message::new(NameType::None, docdef.clone());
|
|
|
|
|
let path = Path::new(
|
|
|
|
|
Include::Just(msg.get_message_id().clone()),
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::All,
|
|
|
|
|
);
|
|
|
|
|
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path));
|
|
|
|
|
queue.send(msg.forward(NameType::None, reg_msg)).unwrap();
|
|
|
|
|
rx.recv().unwrap(); // Wait for completion.
|
|
|
|
|
queue.send(msg).unwrap();
|
|
|
|
|
rx.recv().unwrap(); // Wait for completion.
|
|
|
|
|
rx.recv().unwrap(); // Wait for completion.
|
|
|
|
|
queue.remove_sender(&sender_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod sessions {
|
2025-12-24 17:54:25 -05:00
|
|
|
use super::*;
|
2026-01-12 13:57:59 -05:00
|
|
|
use crate::{document::create::CreateDoc, support_tests::TIMEOUT};
|
2025-12-17 12:14:06 -05:00
|
|
|
use std::{sync::mpsc::RecvTimeoutError, thread::sleep};
|
|
|
|
|
|
|
|
|
|
struct Setup {
|
|
|
|
|
queue: Queue,
|
|
|
|
|
rx: Receiver<Message>,
|
|
|
|
|
sender_id: Uuid,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Setup {
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
let (tx, rx) = channel();
|
|
|
|
|
let mut queue = Queue::new();
|
|
|
|
|
let id = queue.add_sender(tx);
|
|
|
|
|
CreateDoc::start(queue.clone());
|
2026-01-12 13:57:59 -05:00
|
|
|
crate::document::clock::Clock::start(queue.clone());
|
2025-12-17 12:14:06 -05:00
|
|
|
Self {
|
|
|
|
|
queue: queue,
|
|
|
|
|
rx: rx,
|
|
|
|
|
sender_id: id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_sender_id(&self) -> Uuid {
|
|
|
|
|
self.sender_id.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_queue(&self) -> Queue {
|
|
|
|
|
self.queue.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn recv(&self) -> Result<Message, RecvTimeoutError> {
|
|
|
|
|
self.rx.recv_timeout(TIMEOUT)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn register(&self) {
|
|
|
|
|
let session = Session::new();
|
|
|
|
|
let paths = [
|
|
|
|
|
Path::new(
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::Just(session.get_document_name().into()),
|
|
|
|
|
Include::Just(Action::Error),
|
|
|
|
|
),
|
|
|
|
|
Path::new(
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::Just(session.get_document_name().into()),
|
|
|
|
|
Include::Just(Action::Records),
|
|
|
|
|
),
|
|
|
|
|
];
|
|
|
|
|
for path in paths.iter() {
|
|
|
|
|
let reg_msg = Register::new(self.sender_id.clone(), RegMsg::AddRoute(path.clone()));
|
|
|
|
|
self.queue
|
|
|
|
|
.send(Message::new(NameType::None, reg_msg))
|
|
|
|
|
.unwrap();
|
|
|
|
|
self.rx.recv().unwrap(); // Wait for completion.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn creates_the_session_table() {
|
|
|
|
|
let setup = Setup::new();
|
|
|
|
|
let queue = setup.get_queue();
|
2025-12-23 22:15:49 -05:00
|
|
|
let session = Session::new();
|
2025-12-17 12:14:06 -05:00
|
|
|
assert_eq!(session.get_document_name(), &Name::english("session"));
|
|
|
|
|
session.create(queue.clone());
|
|
|
|
|
let path = Path::new(
|
|
|
|
|
Include::All,
|
|
|
|
|
Include::Just(session.get_document_name().into()),
|
|
|
|
|
Include::All,
|
|
|
|
|
);
|
|
|
|
|
let reg_msg = Register::new(setup.get_sender_id(), RegMsg::AddRoute(path));
|
|
|
|
|
queue.send(Message::new(NameType::None, reg_msg)).unwrap();
|
2025-12-23 22:15:49 -05:00
|
|
|
setup.recv().unwrap();
|
2025-12-17 12:14:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn session_ids_are_unique() {
|
|
|
|
|
let setup = Setup::new();
|
|
|
|
|
let queue = setup.get_queue();
|
2025-12-23 22:15:49 -05:00
|
|
|
let session = Session::new();
|
2025-12-17 12:14:06 -05:00
|
|
|
session.create(queue.clone());
|
|
|
|
|
setup.register();
|
|
|
|
|
let count = 10;
|
|
|
|
|
let msg = Message::new(session.get_document_name(), Addition::new());
|
|
|
|
|
let mut ids: Vec<Uuid> = Vec::new();
|
|
|
|
|
for _ in 0..count {
|
|
|
|
|
queue.send(msg.clone()).unwrap();
|
|
|
|
|
let result = setup.recv().unwrap();
|
|
|
|
|
let action = result.get_action();
|
|
|
|
|
match action {
|
|
|
|
|
MsgAction::Records(recs) => {
|
|
|
|
|
assert_eq!(recs.len(), 1);
|
|
|
|
|
let rec = recs.iter().last().unwrap();
|
|
|
|
|
let holder = rec.get(Name::english("id")).unwrap();
|
|
|
|
|
let id = match holder {
|
|
|
|
|
Field::Uuid(data) => data,
|
|
|
|
|
_ => unreachable!("got {:?} should have been uuid", holder),
|
|
|
|
|
};
|
|
|
|
|
assert!(!ids.contains(&id), "{} duplicated in {:?}", id, ids);
|
|
|
|
|
ids.push(id);
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?}, should have gotten records", action),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expire_default_is_an_hour_from_now() {
|
|
|
|
|
let setup = Setup::new();
|
|
|
|
|
let queue = setup.get_queue();
|
2025-12-23 22:15:49 -05:00
|
|
|
let session = Session::new();
|
2025-12-17 12:14:06 -05:00
|
|
|
session.create(queue.clone());
|
|
|
|
|
setup.register();
|
|
|
|
|
let msg = Message::new(session.get_document_name(), Addition::new());
|
|
|
|
|
let start_time = Utc::now() + Duration::from_hours(1);
|
|
|
|
|
queue.send(msg).unwrap();
|
|
|
|
|
let result = setup.recv().unwrap();
|
|
|
|
|
let end_time = Utc::now() + Duration::from_hours(1);
|
|
|
|
|
let action = result.get_action();
|
|
|
|
|
match action {
|
|
|
|
|
MsgAction::Records(recs) => {
|
|
|
|
|
assert_eq!(recs.len(), 1);
|
|
|
|
|
let rec = recs.iter().last().unwrap();
|
|
|
|
|
let holder = rec.get(Name::english("expire")).unwrap();
|
|
|
|
|
match holder {
|
|
|
|
|
Field::DateTime(data) => {
|
|
|
|
|
assert!(data > start_time, "expire should be after {:?}", start_time);
|
|
|
|
|
assert!(data < end_time, "expire should be before {:?}", end_time);
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?} should have been date time", holder),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?}, should have gotten records", action),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn session_ids_error_when_not_unique() {
|
|
|
|
|
let setup = Setup::new();
|
|
|
|
|
let queue = setup.get_queue();
|
2025-12-23 22:15:49 -05:00
|
|
|
let session = Session::new();
|
2025-12-17 12:14:06 -05:00
|
|
|
session.create(queue.clone());
|
|
|
|
|
setup.register();
|
|
|
|
|
let id = Uuid::new_v4();
|
|
|
|
|
let mut addition = Addition::new();
|
|
|
|
|
addition.add_field(Name::english("id"), id);
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(session.get_document_name(), addition.clone()))
|
|
|
|
|
.unwrap();
|
|
|
|
|
setup.recv().unwrap();
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(session.get_document_name(), addition))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let result = setup.recv().unwrap();
|
|
|
|
|
let action = result.get_action();
|
|
|
|
|
match action {
|
|
|
|
|
MsgAction::Error(err) => match err {
|
|
|
|
|
MTTError::FieldDuplicate => {}
|
|
|
|
|
_ => unreachable!("got {:?}, should have been a field duplicate", err),
|
|
|
|
|
},
|
|
|
|
|
_ => unreachable!("got {:?}, should have been an error", action),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expire_should_update_on_successful_query() {
|
|
|
|
|
let setup = Setup::new();
|
|
|
|
|
let queue = setup.get_queue();
|
2025-12-23 22:15:49 -05:00
|
|
|
let session = Session::new();
|
2025-12-17 12:14:06 -05:00
|
|
|
session.create(queue.clone());
|
|
|
|
|
setup.register();
|
|
|
|
|
let id = Uuid::new_v4();
|
|
|
|
|
let timestamp = Utc::now();
|
|
|
|
|
let mut addition = Addition::new();
|
|
|
|
|
addition.add_field(Name::english("id"), id.clone());
|
|
|
|
|
addition.add_field(Name::english("expire"), timestamp);
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(session.get_document_name(), addition.clone()))
|
|
|
|
|
.unwrap();
|
|
|
|
|
setup.recv().unwrap();
|
|
|
|
|
let mut query = Query::new();
|
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
2025-12-23 22:15:49 -05:00
|
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
|
|
|
.unwrap();
|
|
|
|
|
calc.add_value(id).unwrap();
|
2025-12-17 12:14:06 -05:00
|
|
|
query.add(Name::english("id"), calc.clone());
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(session.get_document_name(), query.clone()))
|
|
|
|
|
.unwrap();
|
|
|
|
|
setup.recv().unwrap();
|
|
|
|
|
let start_time = Utc::now() + Duration::from_secs(3600);
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(session.get_document_name(), query.clone()))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let result = setup.recv().unwrap();
|
|
|
|
|
let end_time = Utc::now() + Duration::from_hours(1);
|
|
|
|
|
sleep(TIMEOUT);
|
|
|
|
|
let action = result.get_action();
|
|
|
|
|
match action {
|
|
|
|
|
MsgAction::Records(recs) => {
|
|
|
|
|
assert_eq!(recs.len(), 1);
|
|
|
|
|
let rec = recs.iter().last().unwrap();
|
|
|
|
|
let holder = rec.get(Name::english("expire")).unwrap();
|
|
|
|
|
match holder {
|
|
|
|
|
Field::DateTime(data) => {
|
|
|
|
|
assert!(data > start_time, "expire should be after {:?}", start_time);
|
|
|
|
|
assert!(data < end_time, "expire should be before {:?}", end_time);
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?} should have been date time", holder),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?}, should have gotten records", action),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn clock_removes_expired_sessions() {
|
|
|
|
|
let setup = Setup::new();
|
|
|
|
|
let queue = setup.get_queue();
|
2025-12-23 22:15:49 -05:00
|
|
|
let session = Session::new();
|
2025-12-17 12:14:06 -05:00
|
|
|
session.create(queue.clone());
|
|
|
|
|
setup.register();
|
|
|
|
|
let id1 = Uuid::new_v4();
|
|
|
|
|
let id2 = Uuid::new_v4();
|
|
|
|
|
let duration = Duration::from_secs(240);
|
|
|
|
|
let expire1 = Utc::now() + duration;
|
|
|
|
|
println!("{:?}", expire1);
|
|
|
|
|
println!("{:?}", Utc::now());
|
|
|
|
|
let expire2 = Utc::now() - duration;
|
|
|
|
|
println!("{:?}", expire2);
|
|
|
|
|
let mut addition1 = Addition::new();
|
|
|
|
|
addition1.add_field(Name::english("id"), id1.clone());
|
|
|
|
|
addition1.add_field(Name::english("expire"), expire1);
|
|
|
|
|
let mut addition2 = Addition::new();
|
|
|
|
|
addition2.add_field(Name::english("id"), id2);
|
|
|
|
|
addition2.add_field(Name::english("expire"), expire2);
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(session.get_document_name(), addition1))
|
|
|
|
|
.unwrap();
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(session.get_document_name(), addition2))
|
|
|
|
|
.unwrap();
|
|
|
|
|
setup.recv().unwrap();
|
|
|
|
|
setup.recv().unwrap();
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(
|
|
|
|
|
Name::english("clock"),
|
|
|
|
|
MsgAction::OnUpdate(Records::new(Names::new())),
|
|
|
|
|
))
|
|
|
|
|
.unwrap();
|
|
|
|
|
sleep(TIMEOUT);
|
|
|
|
|
queue
|
|
|
|
|
.send(Message::new(session.get_document_name(), Query::new()))
|
|
|
|
|
.unwrap();
|
|
|
|
|
let result = setup.recv().unwrap();
|
|
|
|
|
let action = result.get_action();
|
|
|
|
|
match action {
|
|
|
|
|
MsgAction::Records(recs) => {
|
|
|
|
|
assert_eq!(recs.len(), 1, "nothing was deleted");
|
|
|
|
|
let rec = recs.iter().last().unwrap();
|
|
|
|
|
let id = rec.get(Name::english("id")).unwrap();
|
|
|
|
|
let expire = rec.get(Name::english("expire")).unwrap();
|
|
|
|
|
assert_eq!(id, id1.into(), "\n\n{:?}\n{:?}", Utc::now(), recs);
|
|
|
|
|
assert_eq!(expire, expire1.into(), "\n\n{:?}\n{:?}", Utc::now(), recs);
|
|
|
|
|
}
|
|
|
|
|
_ => unreachable!("got {:?}, should have gotten records", action),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|