Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 1s
7135 lines
233 KiB
Rust
7135 lines
233 KiB
Rust
use super::MTTError;
|
|
use crate::{
|
|
name::{Name, NameType, Names},
|
|
router::{Include, Path, Queue},
|
|
};
|
|
use chrono::prelude::*;
|
|
use std::{
|
|
collections::{HashMap, HashSet},
|
|
ops::{Add, AddAssign},
|
|
sync::{
|
|
mpsc::{channel, Receiver},
|
|
},
|
|
thread::{sleep, spawn},
|
|
time::Duration,
|
|
};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
pub enum Action {
|
|
Addition,
|
|
Create,
|
|
Delete,
|
|
Error,
|
|
GetLog,
|
|
Log,
|
|
OnAddition,
|
|
OnDelete,
|
|
OnQuery,
|
|
OnUpdate,
|
|
Query,
|
|
Records,
|
|
Register,
|
|
Reply,
|
|
Show,
|
|
Update,
|
|
}
|
|
|
|
impl From<MsgAction> for Action {
|
|
fn from(value: MsgAction) -> Self {
|
|
match value {
|
|
MsgAction::Addition(_) => Action::Addition,
|
|
MsgAction::Create(_) => Action::Create,
|
|
MsgAction::Delete(_) => Action::Delete,
|
|
MsgAction::Error(_) => Action::Error,
|
|
MsgAction::GetLog(_) => Action::GetLog,
|
|
MsgAction::Log(_) => Action::Log,
|
|
MsgAction::OnAddition(_) => Action::OnAddition,
|
|
MsgAction::OnDelete(_) => Action::OnDelete,
|
|
MsgAction::OnQuery(_) => Action::OnQuery,
|
|
MsgAction::OnUpdate(_) => Action::OnUpdate,
|
|
MsgAction::Query(_) => Action::Query,
|
|
MsgAction::Records(_) => Action::Records,
|
|
MsgAction::Register(_) => Action::Register,
|
|
MsgAction::Reply(_) => Action::Reply,
|
|
MsgAction::Show => Action::Show,
|
|
MsgAction::Update(_) => Action::Update,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&MsgAction> for Action {
|
|
fn from(value: &MsgAction) -> Self {
|
|
let action = value.clone();
|
|
Self::from(action)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum MsgAction {
|
|
Addition(Addition),
|
|
Create(DocDef),
|
|
// Alter
|
|
// Remove
|
|
Error(MTTError),
|
|
GetLog(Uuid),
|
|
Log(Vec<MsgEntry>),
|
|
OnAddition(Records),
|
|
OnDelete(Records),
|
|
OnQuery(Records),
|
|
OnUpdate(Records),
|
|
Query(Query),
|
|
Records(Records),
|
|
Register(Register),
|
|
Reply(Reply),
|
|
Show,
|
|
Delete(Delete),
|
|
Update(Update),
|
|
}
|
|
|
|
impl From<Addition> for MsgAction {
|
|
fn from(value: Addition) -> Self {
|
|
MsgAction::Addition(value)
|
|
}
|
|
}
|
|
|
|
impl From<Delete> for MsgAction {
|
|
fn from(value: Delete) -> Self {
|
|
MsgAction::Delete(value)
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
impl From<Query> for MsgAction {
|
|
fn from(value: Query) -> Self {
|
|
MsgAction::Query(value)
|
|
}
|
|
}
|
|
|
|
impl From<Records> for MsgAction {
|
|
fn from(value: Records) -> Self {
|
|
MsgAction::Records(value)
|
|
}
|
|
}
|
|
|
|
impl From<Register> for MsgAction {
|
|
fn from(value: Register) -> Self {
|
|
MsgAction::Register(value)
|
|
}
|
|
}
|
|
|
|
impl From<Reply> for MsgAction {
|
|
fn from(value: Reply) -> Self {
|
|
MsgAction::Reply(value)
|
|
}
|
|
}
|
|
|
|
impl From<Update> for MsgAction {
|
|
fn from(value: Update) -> Self {
|
|
MsgAction::Update(value)
|
|
}
|
|
}
|
|
|
|
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())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod msgactions {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn turn_document_definition_into_action() {
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let value = DocDef::new(name.clone());
|
|
let result: MsgAction = value.into();
|
|
match result {
|
|
MsgAction::Create(def) => assert_eq!(def.get_document_names(), &[name].to_vec()),
|
|
_ => 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() {
|
|
let value = Query::new();
|
|
let result: MsgAction = value.into();
|
|
match result {
|
|
MsgAction::Query(_) => {}
|
|
_ => unreachable!("Got {:?}: dhould have been query", result),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn turn_reply_into_action() {
|
|
let value = Reply::new();
|
|
let result: MsgAction = value.into();
|
|
match result {
|
|
MsgAction::Reply(_) => {}
|
|
_ => unreachable!("Got {:?}: dhould have been reply", result),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Message {
|
|
msg_id: Uuid,
|
|
document_id: NameType,
|
|
action: MsgAction,
|
|
// session: Option<?>
|
|
}
|
|
|
|
impl Message {
|
|
pub fn new<D, A>(doc_id: D, action: A) -> Self
|
|
where
|
|
D: Into<NameType>,
|
|
A: Into<MsgAction>,
|
|
{
|
|
Self {
|
|
msg_id: Uuid::new_v4(),
|
|
document_id: doc_id.into(),
|
|
action: action.into(),
|
|
}
|
|
}
|
|
|
|
pub fn get_message_id(&self) -> &Uuid {
|
|
&self.msg_id
|
|
}
|
|
|
|
fn get_document_id(&self) -> &NameType {
|
|
&self.document_id
|
|
}
|
|
|
|
pub fn get_action(&self) -> &MsgAction {
|
|
&self.action
|
|
}
|
|
|
|
fn get_path(&self) -> Path {
|
|
Path::new(
|
|
Include::Just(self.msg_id.clone()),
|
|
Include::Just(self.document_id.clone()),
|
|
Include::Just(self.action.clone().into()),
|
|
)
|
|
}
|
|
|
|
fn reset_name_id<NT>(&mut self, name: NT)
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
self.document_id = name.into();
|
|
}
|
|
|
|
pub fn response<A>(&self, action: A) -> Self
|
|
where
|
|
A: Into<MsgAction>,
|
|
{
|
|
Self {
|
|
msg_id: self.msg_id.clone(),
|
|
document_id: self.document_id.clone(),
|
|
action: action.into(),
|
|
}
|
|
}
|
|
|
|
pub fn forward<D, A>(&self, doc_id: D, action: A) -> Self
|
|
where
|
|
D: Into<NameType>,
|
|
A: Into<MsgAction>,
|
|
{
|
|
Self {
|
|
msg_id: self.msg_id.clone(),
|
|
document_id: doc_id.into(),
|
|
action: action.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod messages {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_the_document_be_a_stringi_reference() {
|
|
let dts = [Name::english("one"), Name::english("two")];
|
|
for document in dts.into_iter() {
|
|
let msg = Message::new(
|
|
document.clone(),
|
|
MsgAction::Create(DocDef::new(document.clone())),
|
|
);
|
|
match msg.get_document_id() {
|
|
NameType::Name(data) => assert_eq!(data, &document),
|
|
_ => unreachable!("should have been a string id"),
|
|
}
|
|
match msg.get_action() {
|
|
MsgAction::Create(_) => {}
|
|
_ => unreachable!("should have been a create document"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_the_document_be_an_id() {
|
|
let document = Uuid::new_v4();
|
|
let msg = Message::new(document.clone(), Query::new());
|
|
match msg.get_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"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
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);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
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.get_document_id() {
|
|
NameType::Name(data) => assert_eq!(data, &name),
|
|
_ => unreachable!("should have been a name"),
|
|
}
|
|
match reply.get_action() {
|
|
MsgAction::Reply(_) => {}
|
|
_ => unreachable!("should have been a reply"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
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.get_document_id() {
|
|
NameType::Name(data) => assert_eq!(data, &name),
|
|
_ => unreachable!("should have been a name"),
|
|
}
|
|
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"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
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.get_document_id(), msg.get_document_id());
|
|
assert_eq!(result2.get_document_id(), msg.get_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),
|
|
},
|
|
_ => 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),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_reset_document_id() {
|
|
let mut msg = Message::new(Name::english("something"), Query::new());
|
|
let id = Uuid::new_v4();
|
|
msg.reset_name_id(&id);
|
|
let name_id = msg.get_document_id();
|
|
match name_id {
|
|
NameType::ID(data) => assert_eq!(data, &id),
|
|
_ => unreachable!("got {:?}, should have been an id", name_id),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
pub struct RouteID {
|
|
action: Option<Action>,
|
|
doc_type: Option<Uuid>,
|
|
msg_id: Option<Uuid>,
|
|
}
|
|
|
|
impl From<Route> for RouteID {
|
|
fn from(value: Route) -> Self {
|
|
Self {
|
|
action: match value.action {
|
|
Include::All => None,
|
|
Include::Just(action) => Some(action.clone()),
|
|
},
|
|
doc_type: match value.doc_type {
|
|
Include::All => None,
|
|
Include::Just(doc) => Some(doc.clone()),
|
|
},
|
|
msg_id: match value.msg_id {
|
|
Include::All => None,
|
|
Include::Just(id) => Some(id.clone()),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Message> for RouteID {
|
|
type Error = MTTError;
|
|
|
|
fn try_from(value: Message) -> Result<Self, Self::Error> {
|
|
let doc_id = match value.get_document_id() {
|
|
NameType::ID(data) => data,
|
|
_ => return Err(MTTError::CannotConvertMessageToRouteID),
|
|
};
|
|
Ok(RouteID {
|
|
action: Some(value.get_action().into()),
|
|
doc_type: Some(doc_id.clone()),
|
|
msg_id: Some(value.get_message_id().clone()),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod route_ids {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_get_message_route_id() {
|
|
let actions: Vec<MsgAction> = vec![Query::new().into(), MsgAction::Show];
|
|
let doc_id = Uuid::new_v4();
|
|
for action in actions.iter() {
|
|
let msg = Message::new(doc_id.clone(), action.clone());
|
|
let route_id: RouteID = msg.clone().try_into().unwrap();
|
|
match route_id.msg_id {
|
|
Some(data) => assert_eq!(&data, msg.get_message_id()),
|
|
None => unreachable!("should have had a message id"),
|
|
}
|
|
match route_id.doc_type {
|
|
Some(data) => assert_eq!(data, doc_id),
|
|
None => unreachable!("should have had doc type"),
|
|
}
|
|
match route_id.action {
|
|
Some(data) => assert_eq!(data, action.into()),
|
|
None => unreachable!("should have had doc type"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn errors_when_doc_id_is_not_id() {
|
|
let msg = Message::new(Name::english("nope"), Query::new());
|
|
match TryInto::<RouteID>::try_into(msg) {
|
|
Ok(_) => unreachable!("should be an error"),
|
|
Err(err) => match err {
|
|
MTTError::CannotConvertMessageToRouteID => {}
|
|
_ => unreachable!("got {:?}, should have been covert error", err),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum RegMsg {
|
|
AddRoute(Path),
|
|
AddDocName(Vec<Name>),
|
|
DocumentNameID(Uuid),
|
|
Error(MTTError),
|
|
GetNameID(Name),
|
|
Ok,
|
|
RemoveSender(Uuid),
|
|
RouteID(RouteID),
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Register {
|
|
msg: RegMsg,
|
|
sender_id: Uuid,
|
|
}
|
|
|
|
impl Register {
|
|
pub fn new(sender_id: Uuid, reg_msg: RegMsg) -> Self {
|
|
Self {
|
|
msg: reg_msg,
|
|
sender_id: sender_id,
|
|
}
|
|
}
|
|
|
|
pub fn get_msg(&self) -> &RegMsg {
|
|
&self.msg
|
|
}
|
|
|
|
fn get_sender_id(&self) -> &Uuid {
|
|
&self.sender_id
|
|
}
|
|
|
|
fn response(&self, reg_msg: RegMsg) -> Self {
|
|
Self {
|
|
msg: reg_msg,
|
|
sender_id: self.sender_id.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct Route {
|
|
action: Include<Action>,
|
|
doc_type: Include<Uuid>,
|
|
msg_id: Include<Uuid>,
|
|
}
|
|
|
|
impl Route {
|
|
pub fn new(msg_id: Include<Uuid>, doc: Include<Uuid>, action: Include<Action>) -> Self {
|
|
Self {
|
|
action: action,
|
|
doc_type: doc,
|
|
msg_id: msg_id,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<RouteID> for Route {
|
|
fn from(value: RouteID) -> Self {
|
|
Self {
|
|
action: match value.action {
|
|
Some(data) => Include::Just(data.clone()),
|
|
None => Include::All,
|
|
},
|
|
doc_type: match value.doc_type {
|
|
Some(doc) => Include::Just(doc.clone()),
|
|
None => Include::All,
|
|
},
|
|
msg_id: match value.msg_id {
|
|
Some(msg) => Include::Just(msg.clone()),
|
|
None => Include::All,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&RouteID> for Route {
|
|
fn from(value: &RouteID) -> Self {
|
|
Self {
|
|
action: match &value.action {
|
|
Some(data) => Include::Just(data.clone()),
|
|
None => Include::All,
|
|
},
|
|
doc_type: match &value.doc_type {
|
|
Some(doc) => Include::Just(doc.clone()),
|
|
None => Include::All,
|
|
},
|
|
msg_id: match &value.msg_id {
|
|
Some(msg) => Include::Just(msg.clone()),
|
|
None => Include::All,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<Path> for Route {
|
|
type Error = MTTError;
|
|
|
|
fn try_from(value: Path) -> Result<Self, Self::Error> {
|
|
let doc = match value.doc {
|
|
Include::Just(data) => match data {
|
|
NameType::ID(id) => Include::Just(id.clone()),
|
|
_ => return Err(MTTError::RouteRequiresDocumentID),
|
|
},
|
|
Include::All => Include::All,
|
|
};
|
|
Ok(Self {
|
|
action: value.action,
|
|
doc_type: doc,
|
|
msg_id: value.msg_id,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod routes {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_a_route_set_action() {
|
|
let actions = [Action::Query, Action::Reply];
|
|
for action in actions.into_iter() {
|
|
let route = Route::new(Include::All, Include::All, Include::Just(action.clone()));
|
|
match route.msg_id {
|
|
Include::All => {}
|
|
Include::Just(_) => unreachable!("should have been all"),
|
|
}
|
|
match route.doc_type {
|
|
Include::All => {}
|
|
Include::Just(_) => unreachable!("should have been all"),
|
|
}
|
|
match route.action {
|
|
Include::All => unreachable!("should be a specific value"),
|
|
Include::Just(result) => assert_eq!(result, action),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_route_set_document_by_name() {
|
|
let doc_id = Uuid::new_v4();
|
|
let route = Route::new(Include::All, Include::Just(doc_id.clone()), Include::All);
|
|
match route.msg_id {
|
|
Include::All => {}
|
|
Include::Just(_) => unreachable!("should have been all"),
|
|
}
|
|
match route.doc_type {
|
|
Include::All => unreachable!("should be a specific value"),
|
|
Include::Just(result) => assert_eq!(result, doc_id),
|
|
}
|
|
match route.action {
|
|
Include::All => {}
|
|
Include::Just(_) => unreachable!("should have been all"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_route_set_document_by_id() {
|
|
let id = Uuid::new_v4();
|
|
let route = Route::new(Include::All, Include::Just(id.clone()), Include::All);
|
|
match route.msg_id {
|
|
Include::All => {}
|
|
Include::Just(_) => unreachable!("should have been all"),
|
|
}
|
|
match route.doc_type {
|
|
Include::All => unreachable!("should be a specific value"),
|
|
Include::Just(result) => assert_eq!(result, id),
|
|
}
|
|
match route.action {
|
|
Include::All => {}
|
|
Include::Just(_) => unreachable!("should have been all"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_route_be_set_by_message_id() {
|
|
let id = Uuid::new_v4();
|
|
let route = Route::new(Include::Just(id.clone()), Include::All, Include::All);
|
|
match route.msg_id {
|
|
Include::All => unreachable!("should be a specific value"),
|
|
Include::Just(result) => assert_eq!(result, id),
|
|
}
|
|
match route.doc_type {
|
|
Include::All => {}
|
|
Include::Just(_) => unreachable!("should have been all"),
|
|
}
|
|
match route.action {
|
|
Include::All => {}
|
|
Include::Just(_) => unreachable!("should have been all"),
|
|
}
|
|
}
|
|
}
|
|
|
|
struct RouteStorage {
|
|
data: HashMap<RouteID, HashSet<Uuid>>,
|
|
}
|
|
|
|
impl RouteStorage {
|
|
fn new() -> Self {
|
|
Self {
|
|
data: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn add(&mut self, route: Route, sender_id: Uuid) -> RouteID {
|
|
let route_id: RouteID = route.into();
|
|
let set = match self.data.get_mut(&route_id) {
|
|
Some(result) => result,
|
|
None => {
|
|
let holder = HashSet::new();
|
|
self.data.insert(route_id.clone(), holder);
|
|
self.data.get_mut(&route_id).unwrap()
|
|
}
|
|
};
|
|
set.insert(sender_id);
|
|
route_id
|
|
}
|
|
|
|
fn remove_sender_id(&mut self, sender_id: &Uuid) {
|
|
let mut removal: Vec<RouteID> = Vec::new();
|
|
for (route_id, set) in self.data.iter_mut() {
|
|
set.remove(sender_id);
|
|
if set.is_empty() {
|
|
removal.push(route_id.clone());
|
|
}
|
|
}
|
|
for route_id in removal.iter() {
|
|
self.data.remove(route_id);
|
|
}
|
|
}
|
|
|
|
fn get(&self, route: Route) -> HashSet<Uuid> {
|
|
let mut output = HashSet::new();
|
|
for (route_id, set) in self.data.iter() {
|
|
if route == route_id.into() {
|
|
output = output.union(set).cloned().collect();
|
|
}
|
|
}
|
|
output
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod route_storeage {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_add_routes() {
|
|
let mut routes = RouteStorage::new();
|
|
let id1 = Uuid::new_v4();
|
|
let id2 = Uuid::new_v4();
|
|
let route1 = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
let route2 = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
let route_id1 = routes.add(route1.clone(), id1.clone());
|
|
let route_id2 = routes.add(route2.clone(), id2.clone());
|
|
let result1 = routes.get(route1.clone());
|
|
assert_eq!(result1.len(), 1);
|
|
assert!(
|
|
result1.contains(&id1),
|
|
"{:?} not found in {:?}",
|
|
id1,
|
|
result1
|
|
);
|
|
assert_eq!(route_id1, route1.into());
|
|
let result2 = routes.get(route2.clone());
|
|
assert_eq!(result2.len(), 1);
|
|
assert!(
|
|
result2.contains(&id2),
|
|
"{:?} not found in {:?}",
|
|
id2,
|
|
result2
|
|
);
|
|
assert_eq!(route_id2, route2.into());
|
|
}
|
|
|
|
#[test]
|
|
fn returns_empty_set_when_nothing_is_available() {
|
|
let routes = RouteStorage::new();
|
|
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
let result = routes.get(route);
|
|
assert_eq!(result.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn returns_all_entries_using_the_same_route() {
|
|
let count = 5;
|
|
let mut routes = RouteStorage::new();
|
|
let mut ids: HashSet<Uuid> = HashSet::new();
|
|
while ids.len() < count {
|
|
ids.insert(Uuid::new_v4());
|
|
}
|
|
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
for id in ids.iter() {
|
|
routes.add(route.clone(), id.clone());
|
|
}
|
|
let result = routes.get(route);
|
|
assert_eq!(result, ids);
|
|
}
|
|
|
|
#[test]
|
|
fn routes_are_not_duplicated() {
|
|
let count = 5;
|
|
let mut routes = RouteStorage::new();
|
|
let id = Uuid::new_v4();
|
|
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
for _ in 0..count {
|
|
routes.add(route.clone(), id.clone());
|
|
}
|
|
let result = routes.get(route);
|
|
assert_eq!(result.len(), 1);
|
|
assert!(result.contains(&id), "{:?} not found in {:?}", id, result);
|
|
}
|
|
|
|
#[test]
|
|
fn overlapping_routes_are_combined() {
|
|
let mut routes = RouteStorage::new();
|
|
let id1 = Uuid::new_v4();
|
|
let id2 = Uuid::new_v4();
|
|
let route1 = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
let route2 = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
routes.add(route1.clone(), id1.clone());
|
|
routes.add(route2.clone(), id2.clone());
|
|
let retrieve = Route::new(Include::All, Include::All, Include::All);
|
|
let result = routes.get(retrieve);
|
|
assert_eq!(result.len(), 2);
|
|
assert!(result.contains(&id1), "{:?} not found in {:?}", id1, result);
|
|
assert!(result.contains(&id2), "{:?} not found in {:?}", id2, result);
|
|
}
|
|
|
|
#[test]
|
|
fn can_remove_sender_id() {
|
|
let mut routes = RouteStorage::new();
|
|
let count = 5;
|
|
let mut ids: HashSet<Uuid> = HashSet::new();
|
|
while ids.len() < count {
|
|
ids.insert(Uuid::new_v4());
|
|
}
|
|
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
for id in ids.iter() {
|
|
routes.add(route.clone(), id.clone());
|
|
}
|
|
let removed = ids.iter().last().unwrap().clone();
|
|
ids.remove(&removed);
|
|
routes.remove_sender_id(&removed);
|
|
let result = routes.get(route);
|
|
assert_eq!(result, ids);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_routes_are_release_memory() {
|
|
let mut routes = RouteStorage::new();
|
|
let id = Uuid::new_v4();
|
|
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
|
routes.add(route.clone(), id.clone());
|
|
routes.remove_sender_id(&id);
|
|
assert_eq!(routes.data.len(), 0);
|
|
}
|
|
}
|
|
|
|
pub struct DocRegistry {
|
|
doc_names: Names,
|
|
queue: Queue,
|
|
receiver: Receiver<Message>,
|
|
routes: RouteStorage,
|
|
}
|
|
|
|
impl DocRegistry {
|
|
fn new(queue: Queue, rx: Receiver<Message>) -> Self {
|
|
Self {
|
|
doc_names: Names::new(),
|
|
queue: queue,
|
|
receiver: rx,
|
|
routes: RouteStorage::new(),
|
|
}
|
|
}
|
|
|
|
pub fn start(queue: Queue, rx: Receiver<Message>) {
|
|
let mut doc_names = DocRegistry::new(queue, rx);
|
|
spawn(move || {
|
|
doc_names.listen();
|
|
});
|
|
}
|
|
|
|
fn listen(&mut self) {
|
|
loop {
|
|
let mut msg = self.receiver.recv().unwrap();
|
|
match msg.get_action() {
|
|
MsgAction::Register(data) => {
|
|
let id = data.get_sender_id();
|
|
let reply = msg.response(self.register_action(data));
|
|
self.queue.forward(id, reply);
|
|
}
|
|
_ => match self.doc_names.get_id(msg.get_document_id()) {
|
|
Ok(doc_id) => {
|
|
msg.reset_name_id(doc_id);
|
|
let route: Route = msg.get_path().try_into().unwrap();
|
|
for sender_id in self.routes.get(route).iter() {
|
|
self.queue.forward(sender_id, msg.clone());
|
|
}
|
|
}
|
|
Err(err) => self
|
|
.queue
|
|
.send(msg.response(MsgAction::Error(err)))
|
|
.unwrap(),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn register_action(&mut self, reg: &Register) -> Register {
|
|
match reg.get_msg() {
|
|
RegMsg::AddDocName(names) => match self.doc_names.add_names(names.clone()) {
|
|
Ok(id) => reg.response(RegMsg::DocumentNameID(id.clone())),
|
|
Err(err) => reg.response(RegMsg::Error(err)),
|
|
},
|
|
RegMsg::AddRoute(path) => {
|
|
let route = self.doc_names.path_to_route(path).unwrap();
|
|
reg.response(RegMsg::RouteID(
|
|
self.routes.add(route, reg.get_sender_id().clone()),
|
|
))
|
|
}
|
|
RegMsg::GetNameID(name) => match self.doc_names.get_id(name) {
|
|
Ok(id) => reg.response(RegMsg::DocumentNameID(id.clone())),
|
|
Err(err) => reg.response(RegMsg::Error(err)),
|
|
},
|
|
RegMsg::RemoveSender(sender_id) => {
|
|
self.routes.remove_sender_id(sender_id);
|
|
reg.response(RegMsg::Ok)
|
|
}
|
|
_ => reg.response(RegMsg::Ok),
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
struct Router {
|
|
doc_registry: Sender<Message>,
|
|
senders: HashMap<Uuid, Sender<Message>>,
|
|
}
|
|
|
|
impl Router {
|
|
fn new(tx: Sender<Message>) -> Self {
|
|
Self {
|
|
doc_registry: tx,
|
|
senders: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn add_sender(&mut self, sender: Sender<Message>) -> Uuid {
|
|
let mut id = Uuid::new_v4();
|
|
while self.senders.contains_key(&id) {
|
|
id = Uuid::new_v4();
|
|
}
|
|
self.senders.insert(id.clone(), sender);
|
|
id
|
|
}
|
|
|
|
fn remove_sender(&mut self, id: &Uuid) {
|
|
let action = Register::new(Uuid::nil(), RegMsg::RemoveSender(id.clone()));
|
|
self.doc_registry
|
|
.send(Message::new(NameType::None, action))
|
|
.unwrap();
|
|
self.senders.remove(id);
|
|
}
|
|
|
|
fn forward(&self, id: &Uuid, msg: Message) {
|
|
if id == &Uuid::nil() {
|
|
return;
|
|
}
|
|
self.senders.get(id).unwrap().send(msg).unwrap();
|
|
}
|
|
|
|
fn send(&self, msg: Message) {
|
|
self.doc_registry.send(msg).unwrap();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod routers {
|
|
use super::*;
|
|
use crate::support_tests::TIMEOUT;
|
|
|
|
#[test]
|
|
fn can_pass_message() {
|
|
let (tx, rx) = channel();
|
|
let router = Router::new(tx);
|
|
let msg = Message::new(Name::english("task"), Query::new());
|
|
router.send(msg.clone());
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
}
|
|
|
|
#[test]
|
|
fn can_forward_message() {
|
|
let (tx, _) = channel();
|
|
let mut router = Router::new(tx);
|
|
let (sender, receiver) = channel();
|
|
let id = router.add_sender(sender);
|
|
let msg = Message::new(Name::english("wiki"), Query::new());
|
|
router.forward(&id, msg.clone());
|
|
let result = receiver.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
}
|
|
|
|
#[test]
|
|
fn sender_ids_are_unique() {
|
|
let (tx, _) = channel();
|
|
let mut router = Router::new(tx);
|
|
let count = 10;
|
|
let mut holder: HashSet<Uuid> = HashSet::new();
|
|
for _ in 0..count {
|
|
let (tx, _) = channel();
|
|
holder.insert(router.add_sender(tx));
|
|
}
|
|
assert_eq!(holder.len(), count, "had duplicate keys");
|
|
}
|
|
|
|
#[test]
|
|
fn can_remove_sender() {
|
|
let (tx, rx) = channel();
|
|
let mut router = Router::new(tx);
|
|
let (data, _) = channel();
|
|
let id = router.add_sender(data);
|
|
assert_eq!(router.senders.len(), 1, "should have only one sender");
|
|
router.remove_sender(&id);
|
|
assert_eq!(router.senders.len(), 0, "should have no senders.");
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Register(reg_msg) => {
|
|
let reg_action = reg_msg.get_msg();
|
|
match reg_action {
|
|
RegMsg::RemoveSender(result) => assert_eq!(result, &id),
|
|
_ => unreachable!("got {:?}, should have been remove sender", reg_action),
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}, should have been registry message", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ignores_bad_id_removals() {
|
|
let (tx, rx) = channel();
|
|
let mut router = Router::new(tx);
|
|
router.remove_sender(&Uuid::new_v4());
|
|
assert_eq!(router.senders.len(), 0, "should have no senders.");
|
|
rx.recv_timeout(TIMEOUT).unwrap();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct Queue {
|
|
router: Arc<RwLock<Router>>,
|
|
}
|
|
|
|
impl Queue {
|
|
pub fn new() -> Self {
|
|
let (tx, rx) = channel();
|
|
let output = Self {
|
|
router: Arc::new(RwLock::new(Router::new(tx))),
|
|
};
|
|
DocRegistry::start(output.clone(), rx);
|
|
output
|
|
}
|
|
|
|
pub fn add_sender(&mut self, sender: Sender<Message>) -> Uuid {
|
|
let mut router = self.router.write().unwrap();
|
|
router.add_sender(sender)
|
|
}
|
|
|
|
pub fn remove_sender(&mut self, id: &Uuid) {
|
|
let mut router = self.router.write().unwrap();
|
|
router.remove_sender(id);
|
|
}
|
|
|
|
fn forward(&self, id: &Uuid, msg: Message) {
|
|
let router = self.router.read().unwrap();
|
|
router.forward(id, msg);
|
|
}
|
|
|
|
pub fn send(&self, msg: Message) -> Result<(), MTTError> {
|
|
let router = self.router.read().unwrap();
|
|
router.send(msg.clone());
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod queues {
|
|
use super::*;
|
|
use crate::support_tests::TIMEOUT;
|
|
use std::sync::mpsc::RecvTimeoutError;
|
|
|
|
struct TestQueue {
|
|
sender_id: Uuid,
|
|
queue: Queue,
|
|
receiver: Receiver<Message>,
|
|
doc_names: HashMap<Name, Uuid>,
|
|
doc_tx_id: HashMap<Name, Uuid>,
|
|
doc_rx: HashMap<Name, Receiver<Message>>,
|
|
}
|
|
|
|
impl TestQueue {
|
|
fn new() -> Self {
|
|
let mut queue = Queue::new();
|
|
let (tx, rx) = channel();
|
|
let id = queue.add_sender(tx);
|
|
Self {
|
|
sender_id: id,
|
|
queue: queue,
|
|
receiver: rx,
|
|
doc_names: HashMap::new(),
|
|
doc_tx_id: HashMap::new(),
|
|
doc_rx: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn add_document(&mut self, name: Name) {
|
|
let (tx, rx) = channel();
|
|
let id = self.queue.add_sender(tx);
|
|
let reg_msg = Register::new(id.clone(), RegMsg::AddDocName([name.clone()].to_vec()));
|
|
let msg = Message::new(NameType::None, reg_msg);
|
|
self.queue.send(msg.clone()).unwrap();
|
|
match rx.recv_timeout(TIMEOUT).unwrap().get_action() {
|
|
MsgAction::Register(doc_data) => match doc_data.get_msg() {
|
|
RegMsg::DocumentNameID(data) => {
|
|
self.doc_names.insert(name.clone(), data.clone())
|
|
}
|
|
_ => panic!("should not get here"),
|
|
},
|
|
_ => panic!("should not get here"),
|
|
};
|
|
self.doc_tx_id.insert(name.clone(), id);
|
|
self.doc_rx.insert(name.clone(), rx);
|
|
}
|
|
|
|
fn get_preset_id(&self) -> &Uuid {
|
|
&self.sender_id
|
|
}
|
|
|
|
fn get_preset_rx(&self) -> &Receiver<Message> {
|
|
&self.receiver
|
|
}
|
|
|
|
fn get_doc_id(&self, name: &Name) -> &Uuid {
|
|
self.doc_names.get(name).unwrap()
|
|
}
|
|
|
|
fn get_doc_rx_id(&self, name: &Name) -> &Uuid {
|
|
self.doc_tx_id.get(name).unwrap()
|
|
}
|
|
|
|
fn get_doc_rx(&self, name: &Name) -> &Receiver<Message> {
|
|
self.doc_rx.get(name).unwrap()
|
|
}
|
|
|
|
fn get_queue(&self) -> Queue {
|
|
self.queue.clone()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_forward_message() {
|
|
let tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let msg = Message::new(Name::english("wiki"), Query::new());
|
|
queue.forward(tester.get_preset_id(), msg.clone());
|
|
let result = tester.get_preset_rx().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
}
|
|
|
|
#[test]
|
|
fn sender_ids_are_unique() {
|
|
let mut queue = Queue::new();
|
|
let count = 10;
|
|
let mut holder: HashSet<Uuid> = HashSet::new();
|
|
for _ in 0..count {
|
|
let (tx, _) = channel();
|
|
holder.insert(queue.add_sender(tx));
|
|
}
|
|
assert_eq!(holder.len(), count, "had duplicate keys");
|
|
let router = queue.router.read().unwrap();
|
|
assert_eq!(
|
|
router.senders.len(),
|
|
count,
|
|
"should contain all of the senders"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn senders_can_be_removed() {
|
|
let mut queue = Queue::new();
|
|
let (tx, _) = channel();
|
|
let id = queue.add_sender(tx);
|
|
queue.remove_sender(&id);
|
|
let router = queue.router.read().unwrap();
|
|
assert_eq!(router.senders.len(), 0, "should contain no senders");
|
|
}
|
|
|
|
#[test]
|
|
fn document_names_have_unique_id() {
|
|
let tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let names = [
|
|
Name::english("one"),
|
|
Name::english("two"),
|
|
Name::english("three"),
|
|
Name::english("four"),
|
|
Name::english("five"),
|
|
];
|
|
let mut ids: Vec<Uuid> = Vec::new();
|
|
for name in names.iter() {
|
|
let reg_msg = Register::new(
|
|
tester.get_preset_id().clone(),
|
|
RegMsg::AddDocName([name.clone()].to_vec()),
|
|
);
|
|
let msg = Message::new(NameType::None, reg_msg);
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = tester.get_preset_rx().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::DocumentNameID(data) => {
|
|
assert!(!ids.contains(data), "{} already in {:?}", data, ids);
|
|
ids.push(data.clone());
|
|
}
|
|
_ => unreachable!("got {:?}, should have been register ok", action),
|
|
},
|
|
_ => unreachable!("got {:?}, should have been register ok", action),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_name_id_get_updated() {
|
|
let tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let doc_name = Name::english("test");
|
|
let reg_msg = Register::new(
|
|
tester.get_preset_id().clone(),
|
|
RegMsg::AddDocName([doc_name.clone()].to_vec()),
|
|
);
|
|
let msg = Message::new(NameType::None, reg_msg);
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = tester.get_preset_rx().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
let id = match action {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::DocumentNameID(data) => data.clone(),
|
|
_ => unreachable!("got {:?}, should have been register ok", action),
|
|
},
|
|
_ => unreachable!("got {:?}, should have been register ok", action),
|
|
};
|
|
let reg_msg = Register::new(
|
|
tester.get_preset_id().clone(),
|
|
RegMsg::AddRoute(Path::new(Include::All, Include::All, Include::All)),
|
|
);
|
|
let msg = Message::new(NameType::None, reg_msg);
|
|
queue.send(msg.clone()).unwrap();
|
|
tester.get_preset_rx().recv_timeout(TIMEOUT).unwrap();
|
|
let msg = Message::new(doc_name.clone(), Query::new());
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = tester.get_preset_rx().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
let name_id: NameType = id.into();
|
|
assert_eq!(result.get_document_id(), &name_id);
|
|
}
|
|
|
|
#[test]
|
|
fn can_register_multiple_names_at_once() {
|
|
let tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let names = [Name::english("one"), Name::japanese("一")].to_vec();
|
|
let reg_msg = Register::new(
|
|
tester.get_preset_id().clone(),
|
|
RegMsg::AddDocName(names.clone()),
|
|
);
|
|
let msg = Message::new(NameType::None, reg_msg);
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = tester.get_preset_rx().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
let id = match action {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::DocumentNameID(data) => data,
|
|
_ => unreachable!("got {:?}, should have returned id", data),
|
|
},
|
|
_ => unreachable!("got {:?}, should have returned id", action),
|
|
};
|
|
for name in names.iter() {
|
|
let reg_msg = Register::new(
|
|
tester.get_preset_id().clone(),
|
|
RegMsg::GetNameID(name.clone()),
|
|
);
|
|
let msg = Message::new(NameType::None, reg_msg);
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = tester.get_preset_rx().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
let result = match action {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::DocumentNameID(data) => data,
|
|
_ => unreachable!("got {:?}, should have returned id", data),
|
|
},
|
|
_ => unreachable!("got {:?}, should have returned id", action),
|
|
};
|
|
assert_eq!(result, id);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn errors_on_duplicate_names() {
|
|
let tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let receiver = tester.get_preset_rx();
|
|
let doc_name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let reg_msg = Register::new(
|
|
tester.get_preset_id().clone(),
|
|
RegMsg::AddDocName([doc_name.clone()].to_vec()),
|
|
);
|
|
let msg = Message::new(NameType::None, reg_msg.clone());
|
|
queue.send(msg.clone()).unwrap();
|
|
receiver.recv_timeout(TIMEOUT).unwrap();
|
|
let msg2 = Message::new(NameType::None, reg_msg.clone());
|
|
queue.send(msg2.clone()).unwrap();
|
|
let result = receiver.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg2.get_message_id());
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::Error(err) => match err {
|
|
MTTError::NameDuplicate(name) => {
|
|
assert_eq!(name.to_string(), doc_name.to_string())
|
|
}
|
|
_ => unreachable!("got {:?}, should have been duplicate error", err),
|
|
},
|
|
_ => unreachable!("got {:?}, should have been error", data),
|
|
},
|
|
_ => unreachable!("got {:?}, should have been register ok", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_register_routes() {
|
|
let mut tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let names = [Name::english("task"), Name::english("recipe")];
|
|
for name in names.iter() {
|
|
tester.add_document(name.clone());
|
|
}
|
|
let route_req = Path::new(Include::All, Include::All, Include::All);
|
|
let reg_msg = RegMsg::AddRoute(route_req);
|
|
let reg = Register::new(tester.get_doc_rx_id(&names[0]).clone(), reg_msg);
|
|
let msg = Message::new(NameType::None, reg);
|
|
queue.send(msg).unwrap();
|
|
tester.get_doc_rx(&names[0]).recv_timeout(TIMEOUT).unwrap();
|
|
let msg = Message::new(NameType::None, Query::new());
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = tester.get_doc_rx(&names[0]).recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
match tester.get_doc_rx(&names[1]).recv_timeout(TIMEOUT) {
|
|
Ok(msg) => unreachable!("should not receive: {:?}", msg),
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Timeout => {}
|
|
_ => unreachable!("should have timed out"),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_multiple_register_for_the_same_route() {
|
|
let mut tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let names = [Name::english("task"), Name::english("recipe")];
|
|
let route_req = Path::new(Include::All, Include::All, Include::All);
|
|
let reg_msg = RegMsg::AddRoute(route_req);
|
|
for name in names.iter() {
|
|
tester.add_document(name.clone());
|
|
let reg = Register::new(tester.get_doc_rx_id(name).clone(), reg_msg.clone());
|
|
let msg = Message::new(NameType::None, reg);
|
|
queue.send(msg).unwrap();
|
|
tester.get_doc_rx(name).recv_timeout(TIMEOUT).unwrap();
|
|
}
|
|
let msg = Message::new(NameType::None, Query::new());
|
|
queue.send(msg.clone()).unwrap();
|
|
for name in names.iter() {
|
|
let result = tester.get_doc_rx(name).recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_receiver_only_receives_the_message_once() {
|
|
let mut tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let name = Name::english("something");
|
|
tester.add_document(name.clone());
|
|
let paths = [
|
|
Path::new(Include::All, Include::All, Include::All),
|
|
Path::new(
|
|
Include::All,
|
|
Include::Just(name.clone().into()),
|
|
Include::All,
|
|
),
|
|
];
|
|
for path in paths.iter() {
|
|
let reg_msg = RegMsg::AddRoute(path.clone());
|
|
let reg = Register::new(tester.get_doc_rx_id(&name).clone(), reg_msg);
|
|
let msg = Message::new(NameType::None, reg);
|
|
queue.send(msg).unwrap();
|
|
tester.get_doc_rx(&name).recv_timeout(TIMEOUT).unwrap();
|
|
}
|
|
let msg = Message::new(name.clone(), Query::new());
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = tester.get_doc_rx(&name).recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
match tester.get_doc_rx(&name).recv_timeout(TIMEOUT) {
|
|
Ok(msg) => unreachable!("should not receive: {:?}", msg),
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Timeout => {}
|
|
_ => unreachable!("should have timed out"),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_routing_be_based_on_message_id() {
|
|
let mut tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let names = [Name::english("one"), Name::english("two")];
|
|
let mut inputs: HashMap<Name, Message> = HashMap::new();
|
|
for name in names.iter() {
|
|
tester.add_document(name.clone());
|
|
let input = Message::new(name.clone(), Query::new());
|
|
let path = Path::new(
|
|
Include::Just(input.get_message_id().clone()),
|
|
Include::All,
|
|
Include::All,
|
|
);
|
|
let reg_msg = RegMsg::AddRoute(path);
|
|
let reg = Register::new(tester.get_doc_rx_id(&name).clone(), reg_msg);
|
|
let msg = Message::new(NameType::None, reg);
|
|
queue.send(msg).unwrap();
|
|
let result = tester.get_doc_rx(&name).recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::RouteID(output) => {
|
|
let expected = RouteID {
|
|
action: None,
|
|
doc_type: None,
|
|
msg_id: Some(input.get_message_id().clone()),
|
|
};
|
|
assert_eq!(output, &expected);
|
|
}
|
|
_ => unreachable!("got {:?}, should have been route id", data),
|
|
},
|
|
_ => unreachable!("got {:?}, should have been route id", action),
|
|
}
|
|
inputs.insert(name.clone(), input);
|
|
}
|
|
for msg in inputs.values() {
|
|
queue.send(msg.clone()).unwrap();
|
|
}
|
|
for (name, msg) in inputs.iter() {
|
|
let rx = tester.get_doc_rx(&name);
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
match rx.recv_timeout(TIMEOUT) {
|
|
Ok(msg) => unreachable!("should not receive: {:?}", msg),
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Timeout => {}
|
|
_ => unreachable!("should have timed out"),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_routing_be_based_on_document_name() {
|
|
let mut tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let names = [Name::english("one"), Name::english("two")];
|
|
let mut inputs: HashMap<Name, Message> = HashMap::new();
|
|
for name in names.iter() {
|
|
tester.add_document(name.clone());
|
|
let input = Message::new(name.clone(), Query::new());
|
|
let path = Path::new(
|
|
Include::All,
|
|
Include::Just(name.clone().into()),
|
|
Include::All,
|
|
);
|
|
let reg_msg = RegMsg::AddRoute(path);
|
|
let reg = Register::new(tester.get_doc_rx_id(&name).clone(), reg_msg);
|
|
let msg = Message::new(NameType::None, reg);
|
|
queue.send(msg).unwrap();
|
|
let result = tester.get_doc_rx(&name).recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::RouteID(output) => {
|
|
let expected = RouteID {
|
|
action: None,
|
|
doc_type: Some(tester.get_doc_id(name).clone()),
|
|
msg_id: None,
|
|
};
|
|
assert_eq!(output, &expected);
|
|
}
|
|
_ => unreachable!("got {:?}, should have been route id", data),
|
|
},
|
|
_ => unreachable!("got {:?}, should have been route id", action),
|
|
}
|
|
inputs.insert(name.clone(), input);
|
|
}
|
|
for msg in inputs.values() {
|
|
queue.send(msg.clone()).unwrap();
|
|
}
|
|
for (name, msg) in inputs.iter() {
|
|
let rx = tester.get_doc_rx(&name);
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
match rx.recv_timeout(TIMEOUT) {
|
|
Ok(msg) => unreachable!("should not receive: {:?}", msg),
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Timeout => {}
|
|
_ => unreachable!("should have timed out"),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_routing_be_based_on_action() {
|
|
let mut tester = TestQueue::new();
|
|
let queue = tester.get_queue();
|
|
let names = [Name::english("one"), Name::english("two")];
|
|
let paths = [
|
|
Path::new(Include::All, Include::All, Include::Just(Action::Reply)),
|
|
Path::new(Include::All, Include::All, Include::Just(Action::Error)),
|
|
];
|
|
let actions = [
|
|
MsgAction::Reply(Reply::new()),
|
|
MsgAction::Error(MTTError::NameDuplicate(names[0].clone())),
|
|
];
|
|
let mut inputs: HashMap<Name, Message> = HashMap::new();
|
|
let mut count = 0;
|
|
for name in names.iter() {
|
|
tester.add_document(name.clone());
|
|
let input = Message::new(NameType::None, actions[count].clone());
|
|
let path = paths[count].clone();
|
|
let reg_msg = RegMsg::AddRoute(path);
|
|
let reg = Register::new(tester.get_doc_rx_id(&name).clone(), reg_msg);
|
|
let msg = Message::new(NameType::None, reg);
|
|
queue.send(msg).unwrap();
|
|
let result = tester.get_doc_rx(&name).recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::RouteID(output) => {
|
|
let expected = RouteID {
|
|
action: Some(actions[count].clone().into()),
|
|
doc_type: None,
|
|
msg_id: None,
|
|
};
|
|
assert_eq!(output, &expected);
|
|
}
|
|
_ => unreachable!("got {:?}, should have been route id", data),
|
|
},
|
|
_ => unreachable!("got {:?}, should have been route id", action),
|
|
}
|
|
inputs.insert(name.clone(), input);
|
|
count += 1;
|
|
}
|
|
for msg in inputs.values() {
|
|
queue.send(msg.clone()).unwrap();
|
|
}
|
|
for (name, msg) in inputs.iter() {
|
|
let rx = tester.get_doc_rx(&name);
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
match rx.recv_timeout(TIMEOUT) {
|
|
Ok(msg) => unreachable!("should not receive: {:?}", msg),
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Timeout => {}
|
|
_ => unreachable!("should have timed out"),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_removing_sender_id_remove_from_document_registry() {
|
|
let mut tester = TestQueue::new();
|
|
let mut queue = tester.get_queue();
|
|
let (tx, rx) = channel();
|
|
let sender_id = queue.add_sender(tx);
|
|
let name = Name::english("testing");
|
|
tester.add_document(name.clone());
|
|
let path = Path::new(
|
|
Include::All,
|
|
Include::Just(name.clone().into()),
|
|
Include::All,
|
|
);
|
|
let reg_msg = RegMsg::AddRoute(path);
|
|
let reg = Register::new(sender_id.clone(), reg_msg);
|
|
let msg = Message::new(NameType::None, reg);
|
|
queue.send(msg).unwrap();
|
|
rx.recv_timeout(TIMEOUT).unwrap();
|
|
queue.remove_sender(&sender_id);
|
|
let msg = Message::new(name, Query::new());
|
|
queue.send(msg).unwrap();
|
|
match rx.recv_timeout(TIMEOUT) {
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Disconnected => {}
|
|
_ => unreachable!("got {:?}, should have been disconnected", err),
|
|
},
|
|
Ok(data) => unreachable!("got {:?}, should have been an error", data),
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
pub struct CreateDoc {
|
|
queue: Queue,
|
|
rx: Receiver<Message>,
|
|
}
|
|
|
|
impl CreateDoc {
|
|
fn new(queue: Queue, rx: Receiver<Message>) -> Self {
|
|
Self {
|
|
queue: queue,
|
|
rx: rx,
|
|
}
|
|
}
|
|
|
|
pub fn start(mut queue: Queue) {
|
|
let (tx, rx) = channel();
|
|
let routes = [Path::new(
|
|
Include::All,
|
|
Include::All,
|
|
Include::Just(Action::Create),
|
|
)]
|
|
.to_vec();
|
|
let id = queue.add_sender(tx);
|
|
for route in routes.iter() {
|
|
let regmsg = Register::new(id.clone(), RegMsg::AddRoute(route.clone()));
|
|
queue.send(Message::new(NameType::None, regmsg)).unwrap();
|
|
rx.recv().unwrap();
|
|
}
|
|
let doc = CreateDoc::new(queue, rx);
|
|
spawn(move || {
|
|
doc.listen();
|
|
});
|
|
}
|
|
|
|
fn listen(&self) {
|
|
loop {
|
|
let msg = self.rx.recv().unwrap();
|
|
DocumentFile::start(self.queue.clone(), msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum FieldType {
|
|
Boolean,
|
|
DateTime,
|
|
Duration,
|
|
Integer,
|
|
None,
|
|
StaticString,
|
|
Uuid,
|
|
}
|
|
|
|
impl FieldType {
|
|
fn get_default(&self) -> Field {
|
|
match self {
|
|
FieldType::Boolean => false.into(),
|
|
FieldType::DateTime => Utc::now().into(),
|
|
FieldType::Duration => Duration::from_secs(0).into(),
|
|
FieldType::Integer => 0.into(),
|
|
FieldType::None => Field::None,
|
|
FieldType::StaticString => "".into(),
|
|
FieldType::Uuid => Uuid::new_v4().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&Field> for FieldType {
|
|
fn from(value: &Field) -> Self {
|
|
match value {
|
|
Field::Boolean(_) => Self::Boolean,
|
|
Field::DateTime(_) => Self::DateTime,
|
|
Field::Duration(_) => Self::Duration,
|
|
Field::Integer(_) => Self::Integer,
|
|
Field::None => Self::None,
|
|
Field::StaticString(_) => Self::StaticString,
|
|
Field::Uuid(_) => Self::Uuid,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod fieldtypes {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_get_defaults_for_uuid() {
|
|
let ftype = FieldType::Uuid;
|
|
let mut ids: Vec<Uuid> = Vec::new();
|
|
for _ in 0..5 {
|
|
let result = ftype.get_default();
|
|
match result {
|
|
Field::Uuid(data) => {
|
|
assert!(
|
|
!ids.contains(&data),
|
|
"found duplicate id {:?} in {:?}",
|
|
data,
|
|
ids
|
|
);
|
|
ids.push(data.clone());
|
|
}
|
|
_ => unreachable!("got {:?}: should have been uuid", result),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_get_defaults_for_static_string() {
|
|
let ftype = FieldType::StaticString;
|
|
let result = ftype.get_default();
|
|
match result {
|
|
Field::StaticString(data) => assert_eq!(data, ""),
|
|
_ => unreachable!("got {:?}: should have been static string", result),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
pub enum Field {
|
|
Boolean(bool),
|
|
DateTime(DateTime<Utc>),
|
|
Duration(Duration),
|
|
Integer(i128),
|
|
None,
|
|
StaticString(String),
|
|
Uuid(Uuid),
|
|
}
|
|
|
|
impl Field {
|
|
fn get_type(&self) -> FieldType {
|
|
self.into()
|
|
}
|
|
|
|
fn equal(&self, other: &Field) -> Field {
|
|
if self.get_type() == other.get_type() {
|
|
{ self == other }.into()
|
|
} else {
|
|
Field::None
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn not_equal(&self, other: &Field) -> Field {
|
|
if self.get_type() == other.get_type() {
|
|
{ self != other }.into()
|
|
} else {
|
|
Field::None
|
|
}
|
|
}
|
|
|
|
fn greater(&self, other: &Self) -> Field {
|
|
if self.get_type() == other.get_type() {
|
|
{ self > other }.into()
|
|
} else {
|
|
Field::None
|
|
}
|
|
}
|
|
|
|
fn greater_equal(&self, other: &Self) -> Field {
|
|
if self.get_type() == other.get_type() {
|
|
{ self >= other }.into()
|
|
} else {
|
|
Field::None
|
|
}
|
|
}
|
|
|
|
fn lesser(&self, other: &Self) -> Field {
|
|
if self.get_type() == other.get_type() {
|
|
{ self < other }.into()
|
|
} else {
|
|
Field::None
|
|
}
|
|
}
|
|
|
|
fn lesser_equal(&self, other: &Self) -> Field {
|
|
if self.get_type() == other.get_type() {
|
|
{ self <= other }.into()
|
|
} else {
|
|
Field::None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Add for Field {
|
|
type Output = Self;
|
|
|
|
fn add(self, other: Self) -> Self {
|
|
match self {
|
|
Field::DateTime(value1) => match other {
|
|
Field::Duration(value2) => { value1 + value2 }.into(),
|
|
_ => Field::None,
|
|
},
|
|
Field::Duration(value1) => match other {
|
|
Field::Duration(value2) => { value1 + value2 }.into(),
|
|
_ => Field::None,
|
|
},
|
|
Field::Integer(value1) => match other {
|
|
Field::Integer(value2) => { value1 + value2 }.into(),
|
|
_ => Field::None,
|
|
},
|
|
_ => Self::None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AddAssign for Field {
|
|
fn add_assign(&mut self, other: Self) {
|
|
*self = self.clone().add(other);
|
|
}
|
|
}
|
|
|
|
impl From<bool> for Field {
|
|
fn from(value: bool) -> Self {
|
|
Self::Boolean(value)
|
|
}
|
|
}
|
|
|
|
impl From<DateTime<Utc>> for Field {
|
|
fn from(value: DateTime<Utc>) -> Self {
|
|
Self::DateTime(value)
|
|
}
|
|
}
|
|
|
|
impl From<Duration> for Field {
|
|
fn from(value: Duration) -> Self {
|
|
Self::Duration(value)
|
|
}
|
|
}
|
|
|
|
impl From<String> for Field {
|
|
fn from(value: String) -> Self {
|
|
Self::StaticString(value)
|
|
}
|
|
}
|
|
|
|
impl From<&str> for Field {
|
|
fn from(value: &str) -> Self {
|
|
Self::from(value.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<Uuid> for Field {
|
|
fn from(value: Uuid) -> Self {
|
|
Self::Uuid(value)
|
|
}
|
|
}
|
|
|
|
impl From<i128> for Field {
|
|
fn from(value: i128) -> Self {
|
|
Self::Integer(value)
|
|
}
|
|
}
|
|
|
|
impl From<isize> for Field {
|
|
fn from(value: isize) -> Self {
|
|
let data: i128 = value.try_into().unwrap();
|
|
Self::from(data)
|
|
}
|
|
}
|
|
|
|
impl From<i32> for Field {
|
|
fn from(value: i32) -> Self {
|
|
let data: i128 = value.into();
|
|
Self::from(data)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod fields {
|
|
use super::*;
|
|
use rand::random;
|
|
|
|
#[test]
|
|
fn can_create_static_string() {
|
|
let data = Uuid::new_v4().to_string();
|
|
let result: Field = data.clone().into();
|
|
match result.clone() {
|
|
Field::StaticString(output) => assert_eq!(output, data),
|
|
_ => unreachable!("got {:?}: should have been static string", result),
|
|
}
|
|
assert_eq!(result.get_type(), FieldType::StaticString);
|
|
}
|
|
|
|
#[test]
|
|
fn can_create_from_str() {
|
|
let holder = ["one", "two"];
|
|
for data in holder.into_iter() {
|
|
let result: Field = data.into();
|
|
match result.clone() {
|
|
Field::StaticString(output) => assert_eq!(output, data),
|
|
_ => unreachable!("got {:?}: should have been static string", result),
|
|
}
|
|
assert_eq!(result.get_type(), FieldType::StaticString);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn create_from_uuid() {
|
|
let data = Uuid::new_v4();
|
|
let result: Field = data.clone().into();
|
|
match result.clone() {
|
|
Field::Uuid(output) => assert_eq!(output, data),
|
|
_ => unreachable!("got {:?}: should have been uuid", result),
|
|
}
|
|
assert_eq!(result.get_type(), FieldType::Uuid);
|
|
}
|
|
|
|
#[test]
|
|
fn create_from_datatime() {
|
|
let data = Utc::now();
|
|
let result: Field = data.into();
|
|
match result.clone() {
|
|
Field::DateTime(output) => assert_eq!(output, data),
|
|
_ => unreachable!("got {:?}: should have been uuid", result),
|
|
}
|
|
assert_eq!(result.get_type(), FieldType::DateTime);
|
|
}
|
|
|
|
#[test]
|
|
fn does_adding_return_none_for_things_that_cannot_add() {
|
|
let value1: Field = Uuid::new_v4().into();
|
|
let value2: Field = Uuid::new_v4().into();
|
|
assert_eq!(value1 + value2, Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn can_integers_be_added() {
|
|
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();
|
|
assert_eq!(value1 + value2, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn can_integer_add_mismatch_returns_none() {
|
|
let value1: Field = 5.into();
|
|
let value2: Field = "nope".into();
|
|
assert_eq!(value1 + value2, Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn can_durations_be_added() {
|
|
let data1: u64 = random::<u8>().into();
|
|
let data2: u64 = random::<u8>().into();
|
|
let value1: Field = Duration::from_secs(data1).into();
|
|
let value2: Field = Duration::from_secs(data2).into();
|
|
let expected: Field = Duration::from_secs(data1 + data2).into();
|
|
assert_eq!(value1 + value2, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn does_duration_mismatch_return_none() {
|
|
let value1: Field = Duration::from_secs(20).into();
|
|
let value2: Field = "nope".into();
|
|
assert_eq!(value1 + value2, Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn can_durations_be_added_to_datetimes() {
|
|
let timestamp = Utc::now();
|
|
let data: u64 = random::<u8>().into();
|
|
let duration = Duration::from_secs(data);
|
|
let expected: Field = { timestamp + duration }.into();
|
|
let value1: Field = timestamp.into();
|
|
let value2: Field = duration.into();
|
|
assert_eq!(value1 + value2, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn does_datetime_mismatch_return_none() {
|
|
let value1: Field = Utc::now().into();
|
|
let value2: Field = "nope".into();
|
|
assert_eq!(value1 + value2, Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn does_field_equal_return_properly() {
|
|
let mut values: Vec<Field> = Vec::new();
|
|
let count = 2;
|
|
while values.len() < count {
|
|
let value: Field = Uuid::new_v4().into();
|
|
if !values.contains(&value) {
|
|
values.push(value);
|
|
}
|
|
}
|
|
assert_eq!(values[0].equal(&values[0]), true.into());
|
|
assert_eq!(values[0].equal(&values[1]), false.into());
|
|
assert_eq!(values[0].equal(&"nope".into()), Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn does_field_not_equal_return_properly() {
|
|
let mut values: Vec<Field> = Vec::new();
|
|
let count = 2;
|
|
while values.len() < count {
|
|
let value: Field = Uuid::new_v4().into();
|
|
if !values.contains(&value) {
|
|
values.push(value);
|
|
}
|
|
}
|
|
assert_eq!(values[0].not_equal(&values[0]), false.into());
|
|
assert_eq!(values[0].not_equal(&values[1]), true.into());
|
|
assert_eq!(values[0].not_equal(&"nope".into()), Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn can_field_have_greater_value() {
|
|
let value1: Field = 1.into();
|
|
let value2: Field = 2.into();
|
|
let value3: Field = 3.into();
|
|
let mismatch: Field = "bad".into();
|
|
assert_eq!(value2.greater(&value1), true.into());
|
|
assert_eq!(value2.greater(&value2), false.into());
|
|
assert_eq!(value2.greater(&value3), false.into());
|
|
assert_eq!(value2.greater(&mismatch), Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn can_field_have_greater_or_equal_value() {
|
|
let value1: Field = 1.into();
|
|
let value2: Field = 2.into();
|
|
let value3: Field = 3.into();
|
|
let mismatch: Field = "bad".into();
|
|
assert_eq!(value2.greater_equal(&value1), true.into());
|
|
assert_eq!(value2.greater_equal(&value2), true.into());
|
|
assert_eq!(value2.greater_equal(&value3), false.into());
|
|
assert_eq!(value2.greater_equal(&mismatch), Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn can_field_have_lesser_value() {
|
|
let value1: Field = 1.into();
|
|
let value2: Field = 2.into();
|
|
let value3: Field = 3.into();
|
|
let mismatch: Field = "bad".into();
|
|
assert_eq!(value2.lesser(&value1), false.into());
|
|
assert_eq!(value2.lesser(&value2), false.into());
|
|
assert_eq!(value2.lesser(&value3), true.into());
|
|
assert_eq!(value2.lesser(&mismatch), Field::None);
|
|
}
|
|
|
|
#[test]
|
|
fn can_field_have_lesser_or_equal_value() {
|
|
let value1: Field = 1.into();
|
|
let value2: Field = 2.into();
|
|
let value3: Field = 3.into();
|
|
let mismatch: Field = "bad".into();
|
|
assert_eq!(value2.lesser_equal(&value1), false.into());
|
|
assert_eq!(value2.lesser_equal(&value2), true.into());
|
|
assert_eq!(value2.lesser_equal(&value3), true.into());
|
|
assert_eq!(value2.lesser_equal(&mismatch), Field::None);
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct FieldSetting {
|
|
fieldtype: FieldType,
|
|
default_value: CalcValue,
|
|
}
|
|
|
|
impl FieldSetting {
|
|
fn new(ftype: FieldType) -> Self {
|
|
Self {
|
|
fieldtype: ftype,
|
|
default_value: CalcValue::None,
|
|
}
|
|
}
|
|
|
|
fn set_default<CV>(&mut self, holder: CV) -> Result<(), MTTError>
|
|
where
|
|
CV: Into<CalcValue>,
|
|
{
|
|
let value = holder.into();
|
|
match &value {
|
|
CalcValue::Calculate(calc) => {
|
|
if self.fieldtype != calc.get_type() {
|
|
return Err(MTTError::FieldInvalidType);
|
|
}
|
|
}
|
|
CalcValue::Existing(ftype) | CalcValue::FType(ftype) => {
|
|
if &self.fieldtype != ftype {
|
|
return Err(MTTError::FieldInvalidType);
|
|
}
|
|
}
|
|
CalcValue::Value(ref item) => {
|
|
if item.get_type() != self.fieldtype {
|
|
return Err(MTTError::FieldInvalidType);
|
|
}
|
|
}
|
|
CalcValue::None => {}
|
|
}
|
|
self.default_value = value;
|
|
Ok(())
|
|
}
|
|
|
|
fn validate(&self, value: &Field) -> Result<Field, MTTError> {
|
|
match value {
|
|
Field::None => match &self.default_value {
|
|
CalcValue::None => Err(MTTError::InvalidNone),
|
|
_ => Ok(self.default_value.get(&Field::None)),
|
|
},
|
|
_ => {
|
|
let vft: FieldType = value.into();
|
|
if vft != self.fieldtype {
|
|
return Err(MTTError::DocumentFieldWrongDataType(
|
|
self.fieldtype.clone(),
|
|
vft,
|
|
));
|
|
}
|
|
Ok(value.clone())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod fieldsettings {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn validates_field_type() {
|
|
let fset = FieldSetting::new(FieldType::Uuid);
|
|
let value: Field = Uuid::new_v4().into();
|
|
match fset.validate(&value) {
|
|
Ok(data) => assert_eq!(data, value),
|
|
Err(err) => unreachable!("got {:?}: should have gotten a value", err),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn validates_for_bad_field_type() {
|
|
let fset = FieldSetting::new(FieldType::Uuid);
|
|
let value: Field = "text".into();
|
|
match fset.validate(&value) {
|
|
Ok(data) => unreachable!("got {:?}: should have gotten an error", data),
|
|
Err(err) => match err {
|
|
MTTError::DocumentFieldWrongDataType(expected, got) => {
|
|
assert_eq!(expected, FieldType::Uuid);
|
|
assert_eq!(got, FieldType::StaticString);
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a value", err),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn no_default_returns_error() {
|
|
let fset = FieldSetting::new(FieldType::Uuid);
|
|
match fset.validate(&Field::None) {
|
|
Ok(data) => unreachable!("got {:?}: should have gotten an error", data),
|
|
Err(err) => match err {
|
|
MTTError::InvalidNone => {}
|
|
_ => unreachable!("got {:?}: should have gotten a invalid none", err),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn returns_value_if_default_is_set() {
|
|
let mut fset = FieldSetting::new(FieldType::StaticString);
|
|
fset.set_default(FieldType::StaticString).unwrap();
|
|
match fset.validate(&Field::None) {
|
|
Ok(data) => assert_eq!(data, "".into()),
|
|
Err(err) => unreachable!("got {:?}: should have gotten a value", err),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn returns_default_value() {
|
|
let mut fset = FieldSetting::new(FieldType::StaticString);
|
|
let input = "fred";
|
|
fset.set_default(input).unwrap();
|
|
match fset.validate(&Field::None) {
|
|
Ok(data) => assert_eq!(data, input.into()),
|
|
Err(err) => unreachable!("got {:?}: should have gotten a value", err),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_default_be_calculated() {
|
|
let mut fset = FieldSetting::new(FieldType::DateTime);
|
|
let duration = Duration::from_secs(3600);
|
|
let mut calc = Calculation::new(Operand::Add);
|
|
calc.add_value(FieldType::DateTime).unwrap();
|
|
calc.add_value(duration).unwrap();
|
|
fset.set_default(calc).unwrap();
|
|
let start = Utc::now() + duration;
|
|
let result = match fset.validate(&Field::None).unwrap() {
|
|
Field::DateTime(data) => data,
|
|
_ => unreachable!("should return datetime"),
|
|
};
|
|
let stop = Utc::now() + duration;
|
|
assert!(
|
|
result > start,
|
|
"{:?} should have been greater than {:?}",
|
|
result,
|
|
start
|
|
);
|
|
assert!(
|
|
result < stop,
|
|
"{:?} should have been less than {:?}",
|
|
result,
|
|
stop
|
|
);
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Addition {
|
|
data: Document,
|
|
}
|
|
|
|
impl Addition {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
data: Document::new(),
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn add_field<NT, CV>(&mut self, name: NT, field: CV)
|
|
where
|
|
CV: Into<CalcValue>,
|
|
NT: Into<NameType>,
|
|
{
|
|
self.data.add_field(name, field);
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_field<NT>(&self, name: NT) -> &CalcValue
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
self.data.get_field(name)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_document(&self) -> Document {
|
|
self.data.clone()
|
|
}
|
|
|
|
fn iter(&self) -> impl Iterator<Item = (&NameType, &CalcValue)> {
|
|
self.data.iter()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod additions {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_add_static_string() {
|
|
let mut add = Addition::new();
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let data = Uuid::new_v4().to_string();
|
|
add.add_field(name.clone(), data.clone());
|
|
let result = add.get_field(&name);
|
|
match result {
|
|
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),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_add_uuid() {
|
|
let mut add = Addition::new();
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let data = Uuid::new_v4();
|
|
add.add_field(name.clone(), data.clone());
|
|
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),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_get_document() {
|
|
let mut add = Addition::new();
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let data = Uuid::new_v4();
|
|
add.add_field(name.clone(), data.clone());
|
|
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),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum IndexType {
|
|
Index,
|
|
Unique,
|
|
}
|
|
|
|
impl IndexType {
|
|
fn create_index(&self) -> Index {
|
|
match self {
|
|
Self::Index => Index::new(),
|
|
Self::Unique => Index::new_unique(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum DocFuncType {
|
|
Add,
|
|
Delete,
|
|
ExistingQuery(MsgAction),
|
|
Query,
|
|
Show,
|
|
Trigger(MsgAction),
|
|
Update,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct PathAction {
|
|
path: Path,
|
|
func_type: DocFuncType,
|
|
}
|
|
|
|
impl PathAction {
|
|
fn new(path: Path, func_type: DocFuncType) -> Self {
|
|
Self {
|
|
path: path,
|
|
func_type: func_type,
|
|
}
|
|
}
|
|
|
|
fn path(&self) -> Path {
|
|
self.path.clone()
|
|
}
|
|
|
|
fn doc_function(&self) -> DocFuncType {
|
|
self.func_type.clone()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct DocDef {
|
|
doc_names: Vec<Name>,
|
|
field_names: Names,
|
|
fields: HashMap<Uuid, FieldSetting>,
|
|
indexes: HashMap<Uuid, IndexType>,
|
|
routes: Vec<PathAction>,
|
|
}
|
|
|
|
impl DocDef {
|
|
pub fn new(name: Name) -> Self {
|
|
let names = vec![name];
|
|
Self::with_names(names)
|
|
}
|
|
|
|
fn with_names(names: Vec<Name>) -> Self {
|
|
let routes = vec![
|
|
PathAction::new(
|
|
Path::new(
|
|
Include::All,
|
|
Include::Just(names[0].clone().into()),
|
|
Include::Just(Action::Addition),
|
|
),
|
|
DocFuncType::Add,
|
|
),
|
|
PathAction::new(
|
|
Path::new(
|
|
Include::All,
|
|
Include::Just(names[0].clone().into()),
|
|
Include::Just(Action::Delete),
|
|
),
|
|
DocFuncType::Delete,
|
|
),
|
|
PathAction::new(
|
|
Path::new(
|
|
Include::All,
|
|
Include::Just(names[0].clone().into()),
|
|
Include::Just(Action::Query),
|
|
),
|
|
DocFuncType::Query,
|
|
),
|
|
PathAction::new(
|
|
Path::new(
|
|
Include::All,
|
|
Include::Just(names[0].clone().into()),
|
|
Include::Just(Action::Show),
|
|
),
|
|
DocFuncType::Show,
|
|
),
|
|
PathAction::new(
|
|
Path::new(
|
|
Include::All,
|
|
Include::Just(names[0].clone().into()),
|
|
Include::Just(Action::Update),
|
|
),
|
|
DocFuncType::Update,
|
|
),
|
|
];
|
|
Self {
|
|
doc_names: names,
|
|
field_names: Names::new(),
|
|
fields: HashMap::new(),
|
|
indexes: HashMap::new(),
|
|
routes: routes,
|
|
}
|
|
}
|
|
|
|
fn get_document_names(&self) -> &Vec<Name> {
|
|
&self.doc_names
|
|
}
|
|
|
|
fn get_field_names(&self) -> &Names {
|
|
&self.field_names
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_field_names_mut(&mut self) -> &mut Names {
|
|
&mut self.field_names
|
|
}
|
|
|
|
pub fn add_field(&mut self, name: Name, ftype: FieldType) {
|
|
let id = self.field_names.add_names([name].to_vec()).unwrap();
|
|
self.fields.insert(id, FieldSetting::new(ftype));
|
|
}
|
|
|
|
fn get_field_id<NT>(&self, field_name: NT) -> Result<Uuid, MTTError>
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
match self.field_names.get_id(field_name) {
|
|
Ok(data) => Ok(data),
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_field<NT>(&self, field_name: NT) -> Result<&FieldSetting, MTTError>
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
let id = match self.field_names.get_id(field_name) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
Ok(self.fields.get(&id).unwrap())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_field_mut<NT>(&mut self, field_name: NT) -> Result<&mut FieldSetting, MTTError>
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
let id = match self.field_names.get_id(field_name) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
Ok(self.fields.get_mut(&id).unwrap())
|
|
}
|
|
|
|
fn field_ids(&self) -> HashSet<Uuid> {
|
|
self.fields.keys().cloned().collect()
|
|
}
|
|
|
|
fn validate<NT>(&self, field_name: NT, value: &Field) -> Result<Field, MTTError>
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
let id = match self.field_names.get_id(field_name) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
self.fields.get(&id).unwrap().validate(value)
|
|
}
|
|
|
|
pub fn set_default<CV>(&mut self, field_name: &Name, value: CV) -> Result<(), MTTError>
|
|
where
|
|
CV: Into<CalcValue>,
|
|
{
|
|
let id = match self.field_names.get_id(field_name) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
match self.fields.get_mut(&id).unwrap().set_default(value) {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => Err(err),
|
|
}
|
|
}
|
|
|
|
pub fn add_index(&mut self, field_name: &Name, index_type: IndexType) -> Result<(), MTTError> {
|
|
let id = match self.field_names.get_id(field_name) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
self.indexes.insert(id.clone(), index_type);
|
|
Ok(())
|
|
}
|
|
|
|
fn create_indexes(&self) -> Indexes {
|
|
Indexes::new(&self.indexes)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn iter(&self) -> impl Iterator<Item = (&Uuid, &FieldSetting)> {
|
|
self.fields.iter()
|
|
}
|
|
|
|
fn iter_routes(&self) -> impl Iterator<Item = &PathAction> {
|
|
self.routes.iter()
|
|
}
|
|
|
|
pub fn add_route(&mut self, path: Path, action: DocFuncType) {
|
|
self.routes.push(PathAction::new(path, action));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod docdefs {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_field_be_added() {
|
|
let docname = Name::english("tester");
|
|
let mut docdef = DocDef::new(docname);
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let field_type = FieldType::Uuid;
|
|
docdef.add_field(name.clone(), field_type.clone());
|
|
let result = docdef.get_field(name).unwrap();
|
|
match result.validate(&Uuid::new_v4().into()) {
|
|
Ok(_) => {}
|
|
Err(err) => unreachable!("got {:?}: should have been a value", err),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn produces_error_for_bad_fields() {
|
|
let docname = Name::english("tester");
|
|
let docdef = DocDef::new(docname);
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
match docdef.get_field(&name) {
|
|
Ok(_) => unreachable!("should return non existant field error"),
|
|
Err(err) => match err {
|
|
MTTError::NameNotFound(data) => assert_eq!(data, name),
|
|
_ => unreachable!("got {:?}: should have been document field not found", err),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_multiple_fields_be_added() {
|
|
let docname = Name::english("testing");
|
|
let mut docdef = DocDef::new(docname);
|
|
let names = ["one", "two", "three"];
|
|
let field_type = FieldType::StaticString;
|
|
for name in names.iter() {
|
|
docdef.add_field(Name::english(name), field_type.clone());
|
|
}
|
|
for name in names.iter() {
|
|
let result = docdef.get_field(Name::english(name)).unwrap();
|
|
match result.validate(&"".into()) {
|
|
Ok(_) => {}
|
|
Err(err) => unreachable!("got {:?}: should have been a value", err),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_change_field_default_to_function() {
|
|
let docname = Name::english("something");
|
|
let mut docdef = DocDef::new(docname);
|
|
let name = Name::english("defaultfunction");
|
|
docdef.add_field(name.clone(), FieldType::StaticString);
|
|
docdef.set_default(&name, FieldType::StaticString).unwrap();
|
|
match docdef.get_field(name).unwrap().validate(&Field::None) {
|
|
Ok(data) => match data {
|
|
Field::StaticString(result) => assert_eq!(result, ""),
|
|
_ => unreachable!("got {:?}: should return a static string", data),
|
|
},
|
|
Err(err) => unreachable!("got {:?}: should return a value", err),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_set_default_value_error_on_bad_field_name() {
|
|
let docname = Name::english("something");
|
|
let mut docdef = DocDef::new(docname);
|
|
let field_name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
match docdef.set_default(&field_name, FieldType::Uuid) {
|
|
Ok(_) => unreachable!("should be an error"),
|
|
Err(err) => match err {
|
|
MTTError::NameNotFound(data) => assert_eq!(data, field_name),
|
|
_ => unreachable!("got {:?}: should have been field not found", err),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_set_default_value_error_on_bad_field_type() {
|
|
let docname = Name::english("something");
|
|
let mut docdef = DocDef::new(docname);
|
|
let name = Name::english("defaultvalue");
|
|
docdef.add_field(name.clone(), FieldType::Uuid);
|
|
match docdef.set_default(&name, "fred") {
|
|
Ok(data) => unreachable!("got {:?}, should be an error", data),
|
|
Err(err) => match err {
|
|
MTTError::FieldInvalidType => {}
|
|
_ => unreachable!("got {:?}: should have been field not found", err),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_default_routes_get_set() {
|
|
let default_num = 5;
|
|
let docname = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let docdef = DocDef::new(docname.clone());
|
|
assert_eq!(
|
|
docdef.iter_routes().count(),
|
|
default_num,
|
|
"routes contained the following:\n{:?}",
|
|
docdef.routes
|
|
);
|
|
let mut actions: HashSet<Action> = HashSet::new();
|
|
let mut doc_funcs: HashSet<String> = HashSet::new();
|
|
for path_action in docdef.iter_routes() {
|
|
let path = path_action.path();
|
|
match &path.msg_id {
|
|
Include::All => {}
|
|
_ => unreachable!("got {:?}, message id should include all", path.msg_id),
|
|
}
|
|
match &path.doc {
|
|
Include::Just(output) => match output {
|
|
NameType::Name(data) => assert_eq!(data, &docname),
|
|
_ => unreachable!("got {:?}, name type should be {:?}", path.doc, docname),
|
|
},
|
|
_ => unreachable!("got {:?}, name type should be {:?}", path.doc, docname),
|
|
};
|
|
match &path.action {
|
|
Include::Just(output) => match output {
|
|
Action::Addition => actions.insert(output.clone()),
|
|
Action::Delete => actions.insert(output.clone()),
|
|
Action::Query => actions.insert(output.clone()),
|
|
Action::Show => actions.insert(output.clone()),
|
|
Action::Update => actions.insert(output.clone()),
|
|
_ => unreachable!("got {:?} which is not a default action", output),
|
|
},
|
|
_ => unreachable!("got {:?}, which is not a default action", path.action),
|
|
};
|
|
let file_func = path_action.doc_function();
|
|
match file_func {
|
|
DocFuncType::Add => doc_funcs.insert("Add".to_string()),
|
|
DocFuncType::Delete => doc_funcs.insert("Delete".to_string()),
|
|
DocFuncType::Query => doc_funcs.insert("Query".to_string()),
|
|
DocFuncType::Show => doc_funcs.insert("Show".to_string()),
|
|
DocFuncType::Update => doc_funcs.insert("Update".to_string()),
|
|
_ => unreachable!("got {:?}, which is not a default function", file_func),
|
|
};
|
|
}
|
|
assert_eq!(
|
|
actions.len(),
|
|
default_num,
|
|
"got {:?}, missing some actions",
|
|
actions
|
|
);
|
|
assert_eq!(
|
|
doc_funcs.len(),
|
|
default_num,
|
|
"got {:?}, missing some actions",
|
|
doc_funcs
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn add_route_function() {
|
|
let docname = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let mut docdef = DocDef::new(docname.clone());
|
|
docdef.add_route(
|
|
Path::new(
|
|
Include::All,
|
|
Include::Just(docname.clone().into()),
|
|
Include::Just(Action::OnQuery),
|
|
),
|
|
DocFuncType::Trigger(Update::new(Query::new()).into()),
|
|
);
|
|
let path_action = docdef.iter_routes().last().unwrap();
|
|
let path = path_action.path();
|
|
match &path.msg_id {
|
|
Include::All => {}
|
|
_ => unreachable!("got {:?}, message id should include all", path.msg_id),
|
|
};
|
|
match &path.doc {
|
|
Include::Just(output) => match output {
|
|
NameType::Name(data) => assert_eq!(data, &docname),
|
|
_ => unreachable!("got {:?}, name type should be {:?}", path.doc, docname),
|
|
},
|
|
_ => unreachable!("got {:?}, name type should be {:?}", path.doc, docname),
|
|
};
|
|
match &path.action {
|
|
Include::Just(output) => match output {
|
|
Action::OnQuery => {}
|
|
_ => unreachable!("got {:?} which is not a additional action", output),
|
|
},
|
|
_ => unreachable!("got {:?}, which is not on query action", path.action),
|
|
}
|
|
let file_func = path_action.doc_function();
|
|
match file_func {
|
|
DocFuncType::Trigger(_) => {}
|
|
_ => unreachable!("got {:?}, which is not a default function", file_func),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum Operand {
|
|
Add,
|
|
Equal,
|
|
GreaterThan,
|
|
GreaterThanEqual,
|
|
LessThan,
|
|
LessThanEqual,
|
|
}
|
|
|
|
impl Operand {
|
|
#[allow(dead_code)]
|
|
fn validate(&self, x: &Field, y: &Field) -> bool {
|
|
match self {
|
|
Self::Equal => x == y,
|
|
Self::GreaterThan => x > y,
|
|
Self::GreaterThanEqual => x >= y,
|
|
Self::LessThan => x < y,
|
|
Self::LessThanEqual => x <= y,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod operands {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn equals_true() {
|
|
let data: Field = Uuid::new_v4().into();
|
|
assert!(Operand::Equal.validate(&data, &data));
|
|
}
|
|
|
|
#[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();
|
|
}
|
|
assert!(!Operand::Equal.validate(&x, &y));
|
|
}
|
|
|
|
#[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]));
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum CalcValue {
|
|
Calculate(Calculation),
|
|
Existing(FieldType),
|
|
FType(FieldType),
|
|
None,
|
|
Value(Field),
|
|
}
|
|
|
|
impl CalcValue {
|
|
fn get(&self, existing: &Field) -> Field {
|
|
match self {
|
|
Self::Calculate(calc) => calc.calculate(existing),
|
|
Self::Existing(_) => existing.clone(),
|
|
Self::FType(ftype) => ftype.get_default(),
|
|
Self::None => Field::None,
|
|
Self::Value(field) => field.clone(),
|
|
}
|
|
}
|
|
|
|
fn get_type(&self) -> FieldType {
|
|
match self {
|
|
Self::Calculate(calc) => calc.get_type(),
|
|
Self::Existing(ftype) => ftype.clone(),
|
|
Self::FType(ftype) => ftype.clone(),
|
|
Self::None => FieldType::None,
|
|
Self::Value(field) => field.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Calculation> for CalcValue {
|
|
fn from(value: Calculation) -> Self {
|
|
Self::Calculate(value)
|
|
}
|
|
}
|
|
|
|
impl From<Field> for CalcValue {
|
|
fn from(value: Field) -> Self {
|
|
Self::Value(value)
|
|
}
|
|
}
|
|
|
|
impl From<&Field> for CalcValue {
|
|
fn from(value: &Field) -> Self {
|
|
Self::from(value.clone())
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
impl From<i128> for CalcValue {
|
|
fn from(value: i128) -> Self {
|
|
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();
|
|
match result.clone() {
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
|
}
|
|
assert_eq!(result.get(&Field::None), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn from_str() {
|
|
let value = "something";
|
|
let expected: Field = value.into();
|
|
let result: CalcValue = value.into();
|
|
match result.clone() {
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
|
}
|
|
assert_eq!(result.get(&Field::None), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn from_string() {
|
|
let value = "data".to_string();
|
|
let expected: Field = value.clone().into();
|
|
let result: CalcValue = value.into();
|
|
match result.clone() {
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
|
}
|
|
assert_eq!(result.get(&Field::None), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn from_boolean() {
|
|
let value = true;
|
|
let expected: Field = value.clone().into();
|
|
let result: CalcValue = value.into();
|
|
match result.clone() {
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
|
}
|
|
assert_eq!(result.get(&Field::None), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn from_datetime() {
|
|
let value = Utc::now();
|
|
let expected: Field = value.clone().into();
|
|
let result: CalcValue = value.into();
|
|
match result.clone() {
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
|
}
|
|
assert_eq!(result.get(&Field::None), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn from_duration() {
|
|
let value = Duration::from_secs(5);
|
|
let expected: Field = value.clone().into();
|
|
let result: CalcValue = value.into();
|
|
match result.clone() {
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
|
}
|
|
assert_eq!(result.get(&Field::None), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn from_integer() {
|
|
let value: i128 = 5;
|
|
let expected: Field = value.clone().into();
|
|
let result: CalcValue = value.into();
|
|
match result.clone() {
|
|
CalcValue::Value(data) => assert_eq!(data, expected),
|
|
_ => unreachable!("got {:?}, should have gotten a field", result),
|
|
}
|
|
assert_eq!(result.get(&Field::None), expected);
|
|
}
|
|
|
|
#[test]
|
|
fn from_calculation() {
|
|
let duration = Duration::from_secs(300);
|
|
let start = Utc::now() + duration;
|
|
let mut calc = Calculation::new(Operand::Add);
|
|
calc.add_value(FieldType::DateTime).unwrap();
|
|
calc.add_value(duration.clone()).unwrap();
|
|
let result: CalcValue = calc.into();
|
|
let data = match result.get(&Field::None) {
|
|
Field::DateTime(data) => data,
|
|
_ => unreachable!(),
|
|
};
|
|
let stop = Utc::now() + duration;
|
|
assert!(
|
|
data > start && data < stop,
|
|
"should be about 5 minutes ahead"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Calculation {
|
|
operation: Operand,
|
|
values: Vec<CalcValue>,
|
|
}
|
|
|
|
impl Calculation {
|
|
pub fn new(operand: Operand) -> Self {
|
|
Self {
|
|
operation: operand,
|
|
values: Vec::new(),
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn operation(&self) -> &Operand {
|
|
&self.operation
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_fields(&self, existing: Field) -> Vec<Field> {
|
|
let mut output = Vec::new();
|
|
for item in self.values.iter() {
|
|
output.push(item.get(&existing));
|
|
}
|
|
output
|
|
}
|
|
|
|
fn get_type(&self) -> FieldType {
|
|
if self.values.is_empty() {
|
|
FieldType::None
|
|
} else {
|
|
self.values[0].get_type()
|
|
}
|
|
}
|
|
|
|
pub fn add_value<CV>(&mut self, data: CV) -> Result<(), MTTError>
|
|
where
|
|
CV: Into<CalcValue>,
|
|
{
|
|
let holder: CalcValue = data.into();
|
|
if self.values.is_empty() {
|
|
self.values.push(holder);
|
|
return Ok(());
|
|
}
|
|
let mut base = self.get_type();
|
|
match self.operation {
|
|
Operand::Add => match base {
|
|
FieldType::DateTime => base = FieldType::Duration,
|
|
_ => {}
|
|
},
|
|
_ => {}
|
|
}
|
|
let ftype = holder.get_type();
|
|
if base == ftype {
|
|
self.values.push(holder);
|
|
} else {
|
|
return Err(MTTError::DocumentFieldWrongDataType(base, ftype));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
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();
|
|
match self.operation {
|
|
Operand::Add => {
|
|
if base == FieldType::DateTime {
|
|
base = FieldType::Duration;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
let ftype = holder.get_type();
|
|
if base == ftype {
|
|
Ok(())
|
|
} else {
|
|
Err(MTTError::DocumentFieldWrongDataType(base, ftype))
|
|
}
|
|
}
|
|
|
|
fn calculate(&self, existing: &Field) -> Field {
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
Operand::Equal => {
|
|
if self.values.len() >= 2 {
|
|
result = self.values[0]
|
|
.get(existing)
|
|
.equal(&self.values[1].get(existing));
|
|
}
|
|
}
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
result
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod calculations {
|
|
use super::*;
|
|
use rand::random;
|
|
|
|
#[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() {
|
|
let calc = Calculation::new(Operand::Add);
|
|
match calc.operation() {
|
|
Operand::Add => {}
|
|
_ => 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()),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_equal_true() {
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
let data: Field = Uuid::new_v4().into();
|
|
calc.add_value(data.clone()).unwrap();
|
|
calc.add_value(data.clone()).unwrap();
|
|
let expected: Field = true.into();
|
|
let result = calc.calculate(&Field::None);
|
|
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();
|
|
calc.add_value(value1).unwrap();
|
|
calc.add_value(value2).unwrap();
|
|
let expected: Field = false.into();
|
|
let result = calc.calculate(&Field::None);
|
|
assert_eq!(result, expected);
|
|
}
|
|
|
|
#[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);
|
|
calc.add_value(item.clone()).unwrap();
|
|
calc.add_value(data[1].0.clone()).unwrap();
|
|
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);
|
|
calc.add_value(item.clone()).unwrap();
|
|
calc.add_value(data[1].0.clone()).unwrap();
|
|
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);
|
|
calc.add_value(item.clone()).unwrap();
|
|
calc.add_value(data[1].0.clone()).unwrap();
|
|
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);
|
|
calc.add_value(item.clone()).unwrap();
|
|
calc.add_value(data[1].0.clone()).unwrap();
|
|
let result = calc.calculate(&Field::None);
|
|
assert_eq!(&result, expected);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_add_numbers() {
|
|
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();
|
|
calc.add_value(value1.clone()).unwrap();
|
|
calc.add_value(value2.clone()).unwrap();
|
|
let result = calc.calculate(&Field::None);
|
|
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();
|
|
calc.add_value(value1.clone()).unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
let result = calc.calculate(&value2);
|
|
assert_eq!(
|
|
result, expected,
|
|
"{:?} plus {:?} should equal {:?}",
|
|
value1, value2, expected
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn returns_error_on_mismatch() {
|
|
let mut calc = Calculation::new(Operand::Add);
|
|
calc.add_value(Uuid::nil()).unwrap();
|
|
match calc.add_value("mismatch") {
|
|
Ok(_) => unreachable!("should have returned an error"),
|
|
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),
|
|
}
|
|
let result = calc.calculate(&Field::None);
|
|
let stop = Utc::now() + duration;
|
|
match result {
|
|
Field::DateTime(data) => {
|
|
assert!(data > start);
|
|
assert!(data < stop);
|
|
}
|
|
_ => unreachable!("got {:?}, should have been datetime", result),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Clone, Debug)]
|
|
struct Operation {
|
|
field_name: String,
|
|
operation: Operand,
|
|
value: Field,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl Operation {
|
|
fn new<F>(name: String, op: Operand, value: F) -> Self
|
|
where
|
|
F: Into<Field>,
|
|
{
|
|
Self {
|
|
field_name: name,
|
|
operation: op,
|
|
value: value.into(),
|
|
}
|
|
}
|
|
|
|
fn which_field(&self) -> String {
|
|
self.field_name.clone()
|
|
}
|
|
|
|
fn validate(&self, field: &Field) -> bool {
|
|
self.operation.validate(field, &self.value)
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[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)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Query {
|
|
data: HashMap<NameType, Calculation>,
|
|
}
|
|
|
|
impl Query {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
data: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn add<NT>(&mut self, name: NT, operation: Calculation)
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
self.data.insert(name.into(), operation);
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get<NT>(&self, name: NT) -> Option<Calculation>
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
match self.data.get(&name.into()) {
|
|
Some(calc) => Some(calc.clone()),
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn field_ids(&self) -> HashSet<&NameType> {
|
|
self.data.keys().collect()
|
|
}
|
|
|
|
fn iter(&self) -> impl Iterator<Item = (&NameType, &Calculation)> {
|
|
self.data.iter()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod queries {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn holds_calculation_to_run_query() {
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
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);
|
|
calc.add_value(data.clone()).unwrap();
|
|
query.add(name.clone(), calc);
|
|
match query.get(&name) {
|
|
Some(op) => {
|
|
let expected: Field = true.into();
|
|
let mut holder = op.clone();
|
|
holder.add_value(data).unwrap();
|
|
assert_eq!(holder.calculate(&Field::None), expected);
|
|
}
|
|
None => unreachable!("should have returned a calculation"),
|
|
}
|
|
match query.get(&name) {
|
|
Some(op) => {
|
|
let expected: Field = false.into();
|
|
let mut holder = op.clone();
|
|
holder.add_value(bad_data).unwrap();
|
|
assert_eq!(holder.calculate(&Field::None), expected);
|
|
}
|
|
None => unreachable!("should have returned a calculation"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Clone, Debug)]
|
|
pub struct Reply {
|
|
data: Vec<Document>,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl Reply {
|
|
fn new() -> Self {
|
|
Self { data: Vec::new() }
|
|
}
|
|
|
|
fn add(&mut self, doc: Document) {
|
|
self.data.push(doc);
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
self.data.len()
|
|
}
|
|
|
|
fn iter(&self) -> impl Iterator<Item = &Document> {
|
|
self.data.iter()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod replies {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn is_new_empty() {
|
|
let reply = Reply::new();
|
|
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() {
|
|
let fieldname = Name::english("field");
|
|
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);
|
|
let mut reply_iter = reply.iter();
|
|
let result1 = reply_iter.next().unwrap();
|
|
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),
|
|
}
|
|
let result2 = reply_iter.next().unwrap();
|
|
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),
|
|
}
|
|
match reply_iter.next() {
|
|
None => {}
|
|
Some(_) => unreachable!("should be out of data"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct InternalRecord {
|
|
data: HashMap<Uuid, Field>,
|
|
}
|
|
|
|
impl InternalRecord {
|
|
fn new() -> Self {
|
|
Self {
|
|
data: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn insert<F>(&mut self, id: Uuid, data: F) -> Field
|
|
where
|
|
F: Into<Field>,
|
|
{
|
|
match self.data.insert(id, data.into()) {
|
|
Some(data) => data.clone(),
|
|
None => Field::None,
|
|
}
|
|
}
|
|
|
|
fn get(&self, id: &Uuid) -> Option<&Field> {
|
|
self.data.get(id)
|
|
}
|
|
|
|
/*
|
|
fn keys(&self) -> impl Iterator<Item = &Uuid> {
|
|
self.data.keys()
|
|
}
|
|
*/
|
|
|
|
fn is_empty(&self) -> bool {
|
|
self.data.is_empty()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct InternalRecords {
|
|
data: HashMap<Oid, InternalRecord>,
|
|
}
|
|
|
|
impl InternalRecords {
|
|
fn new() -> Self {
|
|
Self {
|
|
data: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn insert(&mut self, oid: Oid, record: InternalRecord) -> Option<InternalRecord> {
|
|
self.data.insert(oid, record)
|
|
}
|
|
|
|
fn get(&self, oid: &Oid) -> Option<&InternalRecord> {
|
|
self.data.get(oid)
|
|
}
|
|
|
|
fn remove(&mut self, oid: &Oid) -> Option<InternalRecord> {
|
|
self.data.remove(oid)
|
|
}
|
|
|
|
fn iter(&self) -> impl Iterator<Item = (&Oid, &InternalRecord)> {
|
|
self.data.iter()
|
|
}
|
|
|
|
fn keys(&self) -> impl Iterator<Item = &Oid> {
|
|
self.data.keys()
|
|
}
|
|
|
|
fn values(&self) -> impl Iterator<Item = &InternalRecord> {
|
|
self.data.values()
|
|
}
|
|
|
|
fn contains_key(&self, oid: &Oid) -> bool {
|
|
self.data.contains_key(oid)
|
|
}
|
|
|
|
/*
|
|
fn is_empty(&self) -> bool {
|
|
self.data.is_empty()
|
|
}
|
|
*/
|
|
|
|
fn len(&self) -> usize {
|
|
self.data.len()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Record {
|
|
names: Names,
|
|
data: InternalRecord,
|
|
}
|
|
|
|
impl Record {
|
|
fn with_data(names: Names, rec: InternalRecord) -> Self {
|
|
Self {
|
|
names: names,
|
|
data: rec,
|
|
}
|
|
}
|
|
|
|
pub fn get<NT>(&self, field_id: NT) -> Result<Field, MTTError>
|
|
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)]
|
|
pub struct Records {
|
|
names: Names,
|
|
data: InternalRecords,
|
|
}
|
|
|
|
impl Records {
|
|
fn new(names: Names) -> Self {
|
|
Self {
|
|
names: names,
|
|
data: InternalRecords::new(),
|
|
}
|
|
}
|
|
|
|
fn with_data(names: Names, records: InternalRecords) -> Self {
|
|
Self {
|
|
names: names,
|
|
data: records,
|
|
}
|
|
}
|
|
|
|
fn insert(&mut self, oid: Oid, record: InternalRecord) -> Option<InternalRecord> {
|
|
self.data.insert(oid, record)
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.data.len()
|
|
}
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = Record> {
|
|
RecordIter::new(self)
|
|
}
|
|
|
|
fn get_internal_records(&self) -> &InternalRecords {
|
|
&self.data
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Document {
|
|
data: HashMap<NameType, CalcValue>,
|
|
}
|
|
|
|
impl Document {
|
|
fn new() -> Self {
|
|
Self {
|
|
data: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn add_field<NT, CV>(&mut self, name: NT, field: CV)
|
|
where
|
|
CV: Into<CalcValue>,
|
|
NT: Into<NameType>,
|
|
{
|
|
self.data.insert(name.into(), field.into());
|
|
}
|
|
|
|
fn get_field<NT>(&self, name: NT) -> &CalcValue
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
match self.data.get(&name.into()) {
|
|
Some(data) => data,
|
|
None => &CalcValue::None,
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_all(&self) -> Vec<(NameType, Field)> {
|
|
let mut output = Vec::new();
|
|
for (key, value) in self.data.iter() {
|
|
output.push((key.clone(), value.get(&Field::None)));
|
|
}
|
|
output
|
|
}
|
|
|
|
fn iter(&self) -> impl Iterator<Item = (&NameType, &CalcValue)> {
|
|
self.data.iter()
|
|
}
|
|
|
|
/*
|
|
fn is_empty(&self) -> bool {
|
|
self.data.is_empty()
|
|
}
|
|
*/
|
|
}
|
|
|
|
/*
|
|
struct DocIter {
|
|
storage: Vec<(NameType, Field)>,
|
|
}
|
|
|
|
impl DocIter {
|
|
fn new(doc: &Document) -> Self {
|
|
Self {
|
|
storage: doc.get_all(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Iterator for DocIter {
|
|
type Item = (NameType, Field);
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.storage.pop()
|
|
}
|
|
}
|
|
*/
|
|
|
|
#[cfg(test)]
|
|
mod documents {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn can_add_static_string() {
|
|
let mut add = Document::new();
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let data = Uuid::new_v4().to_string();
|
|
add.add_field(name.clone(), data.clone());
|
|
let result = add.get_field(&name);
|
|
match result {
|
|
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),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_add_uuid() {
|
|
let mut add = Document::new();
|
|
let name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let data = Uuid::new_v4();
|
|
add.add_field(name.clone(), data.clone());
|
|
let result = add.get_field(&name);
|
|
match result {
|
|
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),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Delete {
|
|
query: Query,
|
|
}
|
|
|
|
impl Delete {
|
|
pub fn new(query: Query) -> Self {
|
|
Self {
|
|
query: query.into(),
|
|
}
|
|
}
|
|
|
|
fn get_query(&self) -> &Query {
|
|
&self.query
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Update {
|
|
query: Query,
|
|
values: Document,
|
|
}
|
|
|
|
impl Update {
|
|
pub fn new(query: Query) -> Self {
|
|
Self {
|
|
query: query.into(),
|
|
values: Document::new(),
|
|
}
|
|
}
|
|
|
|
fn get_query(&self) -> &Query {
|
|
&self.query
|
|
}
|
|
|
|
fn get_values(&self) -> &Document {
|
|
&self.values
|
|
}
|
|
|
|
pub fn get_values_mut(&mut self) -> &mut Document {
|
|
&mut self.values
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
struct Oid {
|
|
oid: Uuid,
|
|
}
|
|
|
|
impl Oid {
|
|
fn new() -> Self {
|
|
Self {
|
|
oid: Uuid::new_v4(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Index {
|
|
data: HashMap<Field, HashSet<Oid>>,
|
|
unique: bool,
|
|
}
|
|
|
|
impl Index {
|
|
fn new() -> Self {
|
|
Self {
|
|
data: HashMap::new(),
|
|
unique: false,
|
|
}
|
|
}
|
|
|
|
fn new_unique() -> Self {
|
|
Self {
|
|
data: HashMap::new(),
|
|
unique: true,
|
|
}
|
|
}
|
|
|
|
fn internal_add(&mut self, field: &Field, oid: Oid) {
|
|
let storage = match self.data.get_mut(field) {
|
|
Some(data) => data,
|
|
None => {
|
|
let data = HashSet::new();
|
|
self.data.insert(field.clone(), data);
|
|
self.data.get_mut(field).unwrap()
|
|
}
|
|
};
|
|
storage.insert(oid);
|
|
}
|
|
|
|
fn add(&mut self, field: Field, oid: Oid) -> Result<(), MTTError> {
|
|
let oids = match self.data.get_mut(&field) {
|
|
Some(data) => data,
|
|
None => {
|
|
self.data.insert(field.clone(), HashSet::new());
|
|
self.data.get_mut(&field).unwrap()
|
|
}
|
|
};
|
|
if self.unique && oids.len() > 0 {
|
|
return Err(MTTError::FieldDuplicate);
|
|
} else {
|
|
oids.insert(oid);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn append(&mut self, index: &Self) {
|
|
for (value, oids) in index.data.iter() {
|
|
match self.data.get(value) {
|
|
Some(data) => {
|
|
let holder = data.union(oids).cloned().collect();
|
|
self.data.insert(value.clone(), holder);
|
|
}
|
|
None => {
|
|
self.data.insert(value.clone(), oids.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get(&self, spec: &Operation) -> Vec<Oid> {
|
|
let mut output = Vec::new();
|
|
for (field, oids) in self.data.iter() {
|
|
if spec.validate(field) {
|
|
for oid in oids.iter() {
|
|
output.push(oid.clone());
|
|
}
|
|
}
|
|
}
|
|
output
|
|
}
|
|
|
|
fn pull(&self, calc: &Calculation) -> Result<HashSet<Oid>, MTTError> {
|
|
let mut output = HashSet::new();
|
|
for (key, value) in self.data.iter() {
|
|
match calc.calculate(key) {
|
|
Field::Boolean(data) => {
|
|
if data {
|
|
output = output.union(&value).cloned().collect();
|
|
}
|
|
}
|
|
_ => return Err(MTTError::FieldInvalidType),
|
|
}
|
|
}
|
|
Ok(output)
|
|
}
|
|
|
|
fn remove(&mut self, field: &Field, oid: &Oid) {
|
|
match self.data.get_mut(field) {
|
|
Some(oids) => {
|
|
oids.remove(oid);
|
|
if oids.len() == 0 {
|
|
self.data.remove(field);
|
|
}
|
|
}
|
|
None => {}
|
|
};
|
|
}
|
|
|
|
fn validate(&self, field: &Field) -> Result<(), MTTError> {
|
|
if self.unique {
|
|
match self.data.get(field) {
|
|
Some(_) => return Err(MTTError::FieldDuplicate),
|
|
None => {}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
struct Indexes {
|
|
data: HashMap<Uuid, Index>,
|
|
}
|
|
|
|
impl Indexes {
|
|
fn new(settings: &HashMap<Uuid, IndexType>) -> Self {
|
|
let mut output = HashMap::new();
|
|
for (key, value) in settings.iter() {
|
|
output.insert(key.clone(), value.create_index());
|
|
}
|
|
Self { data: output }
|
|
}
|
|
|
|
fn index_ids(&self) -> HashSet<&Uuid> {
|
|
self.data.keys().collect::<HashSet<&Uuid>>()
|
|
}
|
|
|
|
/*
|
|
fn get_index(&self, field_id: &Uuid) -> &Index {
|
|
self.data.get(field_id).unwrap()
|
|
}
|
|
|
|
fn get_index_mut(&mut self, field_id: &Uuid) -> &mut Index {
|
|
self.data.get_mut(field_id).unwrap()
|
|
}
|
|
|
|
fn append(&mut self, indexes: &Self) {
|
|
for (field_id, index) in indexes.iter() {
|
|
self.data.get_mut(field_id).unwrap().append(index);
|
|
}
|
|
}
|
|
*/
|
|
|
|
fn pull(&self, field_id: &Uuid, calc: &Calculation) -> Result<HashSet<Oid>, MTTError> {
|
|
self.data.get(field_id).unwrap().pull(calc)
|
|
}
|
|
|
|
fn add_to_index(&mut self, field_name: &Uuid, field: Field, oid: Oid) -> Result<(), MTTError> {
|
|
let index = match self.data.get_mut(field_name) {
|
|
Some(data) => data,
|
|
None => return Ok(()),
|
|
};
|
|
index.add(field, oid)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn remove_from_index(&mut self, field_name: &Uuid, field: &Field, oid: &Oid) {
|
|
let index = match self.data.get_mut(field_name) {
|
|
Some(data) => data,
|
|
None => return,
|
|
};
|
|
index.remove(field, oid);
|
|
}
|
|
|
|
fn validate(&self, field_name: &Uuid, value: &Field) -> Result<(), MTTError> {
|
|
match self.data.get(field_name) {
|
|
Some(index) => match index.validate(value) {
|
|
Ok(_) => {}
|
|
Err(err) => return Err(err),
|
|
},
|
|
None => {}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/*
|
|
fn iter(&self) -> impl Iterator<Item = (&Uuid, &Index)> {
|
|
self.data.iter()
|
|
}
|
|
*/
|
|
|
|
fn iter_mut(&mut self) -> impl Iterator<Item = (&Uuid, &mut Index)> {
|
|
self.data.iter_mut()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod indexes {
|
|
use super::*;
|
|
|
|
fn get_fields(count: usize) -> Vec<Field> {
|
|
let mut output = Vec::new();
|
|
while output.len() < count {
|
|
let field: Field = Uuid::new_v4().into();
|
|
if !output.contains(&field) {
|
|
output.push(field);
|
|
}
|
|
}
|
|
output
|
|
}
|
|
|
|
fn get_oids(count: usize) -> Vec<Oid> {
|
|
let mut output = Vec::new();
|
|
while output.len() < count {
|
|
let oid = Oid::new();
|
|
if !output.contains(&oid) {
|
|
output.push(oid);
|
|
}
|
|
}
|
|
output
|
|
}
|
|
|
|
#[test]
|
|
fn add_to_index() {
|
|
let mut index = Index::new();
|
|
let count = 3;
|
|
let fields = get_fields(count);
|
|
let oids = get_oids(count);
|
|
for i in 0..count {
|
|
index.add(fields[i].clone(), oids[i].clone()).unwrap();
|
|
}
|
|
for i in 0..count {
|
|
let spec = Operation::new("stuff".to_string(), Operand::Equal, fields[i].clone());
|
|
let result = index.get(&spec);
|
|
assert_eq!(result.len(), 1);
|
|
assert_eq!(result[0], oids[i]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn index_can_handle_multiple_entries() {
|
|
let mut index = Index::new();
|
|
let count = 3;
|
|
let fields = get_fields(1);
|
|
let oids = get_oids(count);
|
|
for i in 0..count {
|
|
index.add(fields[0].clone(), oids[i].clone()).unwrap();
|
|
}
|
|
let spec = Operation::new("unimportant".to_string(), Operand::Equal, fields[0].clone());
|
|
let result = index.get(&spec);
|
|
assert_eq!(result.len(), 3);
|
|
for oid in oids {
|
|
assert!(result.contains(&oid));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_remove_oid() {
|
|
let mut index = Index::new();
|
|
let count = 3;
|
|
let pos = 1;
|
|
let fields = get_fields(1);
|
|
let oids = get_oids(count);
|
|
for i in 0..count {
|
|
index.add(fields[0].clone(), oids[i].clone()).unwrap();
|
|
}
|
|
index.remove(&fields[0], &oids[pos]);
|
|
let spec = Operation::new("x".to_string(), Operand::Equal, fields[0].clone());
|
|
let result = index.get(&spec);
|
|
assert!(!result.contains(&oids[pos]), "should have removed oid");
|
|
}
|
|
|
|
#[test]
|
|
fn are_empty_indexes_removed() {
|
|
let mut index = Index::new();
|
|
let field: Field = Uuid::new_v4().into();
|
|
let oid = Oid::new();
|
|
index.add(field.clone(), oid.clone()).unwrap();
|
|
index.remove(&field, &oid);
|
|
assert_eq!(index.data.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn do_unique_indexes_error_on_duplicates() {
|
|
let mut index = Index::new_unique();
|
|
let field: Field = "fred".into();
|
|
let oids = get_oids(2);
|
|
index.add(field.clone(), oids[0].clone()).unwrap();
|
|
match index.add(field.clone(), oids[0].clone()) {
|
|
Ok(_) => unreachable!("should have been an error"),
|
|
Err(err) => match err {
|
|
MTTError::FieldDuplicate => {}
|
|
_ => unreachable!("got {:?}: should have been duplicate field", err),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn index_returns_validate() {
|
|
let mut index = Index::new();
|
|
let field: Field = "stuff".into();
|
|
let oid = Oid::new();
|
|
index.add(field.clone(), oid).unwrap();
|
|
match index.validate(&field) {
|
|
Ok(_) => {}
|
|
Err(err) => unreachable!("got {:?}: should have returned without issue", err),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unique_return_duplicate_error() {
|
|
let mut index = Index::new_unique();
|
|
let field: Field = "fred".into();
|
|
let oid = Oid::new();
|
|
index.add(field.clone(), oid).unwrap();
|
|
match index.validate(&field) {
|
|
Ok(_) => unreachable!("should have gotten a duplication error"),
|
|
Err(err) => match err {
|
|
MTTError::FieldDuplicate => {}
|
|
_ => unreachable!("got {:?}: should have been duplicate field", err),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
struct DocumentFile {
|
|
docdef: DocDef,
|
|
docs: InternalRecords,
|
|
indexes: Indexes,
|
|
name_id: Uuid,
|
|
queue: Queue,
|
|
routes: HashMap<RouteID, DocFuncType>,
|
|
rx: Receiver<Message>,
|
|
}
|
|
|
|
impl DocumentFile {
|
|
fn new(
|
|
queue: Queue,
|
|
rx: Receiver<Message>,
|
|
docdef: DocDef,
|
|
routes: HashMap<RouteID, DocFuncType>,
|
|
name_id: Uuid,
|
|
) -> Self {
|
|
Self {
|
|
docdef: docdef.clone(),
|
|
docs: InternalRecords::new(),
|
|
indexes: docdef.create_indexes(),
|
|
name_id: name_id,
|
|
queue: queue,
|
|
routes: routes,
|
|
rx: rx,
|
|
}
|
|
}
|
|
|
|
fn start(mut queue: Queue, msg: Message) {
|
|
let (tx, rx) = channel();
|
|
let action = msg.get_action();
|
|
let docdef = match action {
|
|
MsgAction::Create(data) => data.clone(),
|
|
_ => unreachable!("got {:?}: should have been a create message", action),
|
|
};
|
|
let names = docdef.get_document_names();
|
|
let id = queue.add_sender(tx);
|
|
let reg_msg = Register::new(id, RegMsg::AddDocName(names.clone()));
|
|
let rmsg = msg.response(reg_msg.clone());
|
|
queue.send(rmsg.clone()).unwrap();
|
|
let name_result = rx.recv().unwrap();
|
|
let name_id = match name_result.get_action() {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::DocumentNameID(data) => data,
|
|
RegMsg::Error(err) => {
|
|
queue.remove_sender(&id);
|
|
queue.send(msg.response(err.clone())).unwrap();
|
|
return;
|
|
}
|
|
_ => unreachable!("should only return a name id or an error"),
|
|
},
|
|
_ => unreachable!("should only return a name id or an error"),
|
|
};
|
|
let mut route_action: HashMap<RouteID, DocFuncType> = HashMap::new();
|
|
for path_action in docdef.iter_routes() {
|
|
let request = reg_msg.response(RegMsg::AddRoute(path_action.path()));
|
|
let add_route = rmsg.response(request);
|
|
queue.send(add_route).unwrap();
|
|
let result = rx.recv().unwrap();
|
|
let route_id = match result.get_action() {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::RouteID(data) => data,
|
|
RegMsg::Error(err) => {
|
|
queue.remove_sender(&id);
|
|
queue.send(msg.response(err.clone())).unwrap();
|
|
return;
|
|
}
|
|
_ => unreachable!("should only return a route id or an error"),
|
|
},
|
|
_ => unreachable!("should only return a route id or an error"),
|
|
};
|
|
route_action.insert(route_id.clone(), path_action.doc_function());
|
|
}
|
|
let mut doc = DocumentFile::new(queue.clone(), rx, docdef, route_action, name_id.clone());
|
|
spawn(move || {
|
|
doc.listen();
|
|
});
|
|
let reply = msg.response(Reply::new());
|
|
queue.send(reply.clone()).unwrap();
|
|
}
|
|
|
|
fn listen(&mut self) {
|
|
loop {
|
|
let msg = self.rx.recv().unwrap();
|
|
let route: Route = msg.get_path().try_into().unwrap();
|
|
for (route_id, doc_func) in self.routes.clone().iter() {
|
|
if route == route_id.into() {
|
|
match doc_func {
|
|
DocFuncType::Add => self.add_document(&msg),
|
|
DocFuncType::Delete => self.delete(&msg),
|
|
DocFuncType::Query => self.query(&msg),
|
|
DocFuncType::Show => self.queue.send(msg.response(Reply::new())).unwrap(),
|
|
DocFuncType::Update => self.update(&msg),
|
|
DocFuncType::ExistingQuery(action) => self.existing_query(&msg, action),
|
|
DocFuncType::Trigger(action) => self.trigger(&msg, action),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
fn get_docdef(&self) -> &DocDef {
|
|
&self.docdef
|
|
}
|
|
*/
|
|
|
|
fn validate<NT>(&self, field_name: NT, value: &Field) -> Result<Field, MTTError>
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
let field_id = match self.docdef.get_field_id(field_name) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
let output = match self.docdef.validate(field_id.clone(), value) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
match self.indexes.validate(&field_id, &output) {
|
|
Ok(_) => {}
|
|
Err(err) => return Err(err),
|
|
}
|
|
Ok(output)
|
|
}
|
|
|
|
/*
|
|
fn add_field_to_error(key: String, err: MTTError) -> MTTError {
|
|
match err {
|
|
MTTError::DocumentFieldMissing(_) => MTTError::DocumentFieldMissing(key),
|
|
_ => err.clone(),
|
|
}
|
|
}
|
|
|
|
fn add_to_index<NT>(&mut self, field_name: NT, field: Field, oid: Oid)
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
let field_id = self.docdef.get_field_id(field_name).unwrap();
|
|
self.indexes.add_to_index(&field_id, field, oid).unwrap();
|
|
}
|
|
|
|
fn remove_from_index<NT>(&mut self, field_name: NT, field: &Field, oid: &Oid)
|
|
where
|
|
NT: Into<NameType>,
|
|
{
|
|
let field_id = self.docdef.get_field_id(field_name).unwrap();
|
|
self.indexes.remove_from_index(&field_id, field, oid);
|
|
}
|
|
*/
|
|
|
|
fn add_document(&mut self, msg: &Message) {
|
|
let addition = match msg.get_action() {
|
|
MsgAction::Addition(data) => data,
|
|
_ => return,
|
|
};
|
|
let mut holder = InternalRecord::new();
|
|
for (name_id, value) in addition.iter() {
|
|
let field_id = match self.docdef.get_field_id(name_id) {
|
|
Ok(id) => id,
|
|
Err(err) => {
|
|
let reply = msg.response(err);
|
|
self.queue.send(reply).unwrap();
|
|
return;
|
|
}
|
|
};
|
|
holder.insert(field_id.clone(), value.get(&Field::None));
|
|
}
|
|
for field_id in self.docdef.field_ids().iter() {
|
|
let value = match holder.get(field_id) {
|
|
Some(data) => data,
|
|
None => &Field::None,
|
|
};
|
|
let corrected = match self.validate(field_id, value) {
|
|
Ok(data) => data,
|
|
Err(err) => {
|
|
let reply = msg.response(err);
|
|
self.queue.send(reply).unwrap();
|
|
return;
|
|
}
|
|
};
|
|
holder.insert(field_id.clone(), corrected.clone());
|
|
}
|
|
let mut records = Records::new(self.docdef.get_field_names().clone());
|
|
if !holder.is_empty() {
|
|
let mut oid = Oid::new();
|
|
while self.docs.contains_key(&oid) {
|
|
oid = Oid::new();
|
|
}
|
|
for (field_id, oids) in self.indexes.iter_mut() {
|
|
let value = holder.get(field_id).unwrap();
|
|
oids.internal_add(value, oid.clone());
|
|
}
|
|
self.docs.insert(oid.clone(), holder.clone());
|
|
records.insert(oid, holder);
|
|
}
|
|
self.queue.send(msg.response(records.clone())).unwrap();
|
|
self.queue
|
|
.send(msg.response(MsgAction::OnAddition(records)))
|
|
.unwrap();
|
|
}
|
|
|
|
fn delete(&mut self, msg: &Message) {
|
|
let delete = match msg.get_action() {
|
|
MsgAction::Delete(data) => data,
|
|
_ => unreachable!("should always be delete action"),
|
|
};
|
|
let records = match self.run_query(delete.get_query()) {
|
|
Ok(data) => data,
|
|
Err(err) => {
|
|
let reply = msg.response(err);
|
|
self.queue.send(reply).unwrap();
|
|
return;
|
|
}
|
|
};
|
|
for (oid, record) in records.iter() {
|
|
for (field_id, index) in self.indexes.iter_mut() {
|
|
index.remove(record.get(field_id).unwrap(), oid);
|
|
}
|
|
self.docs.remove(oid);
|
|
}
|
|
let rec = Records::with_data(self.docdef.get_field_names().clone(), records);
|
|
self.queue.send(msg.response(rec.clone())).unwrap();
|
|
self.queue
|
|
.send(msg.response(MsgAction::OnDelete(rec)))
|
|
.unwrap();
|
|
}
|
|
|
|
fn run_query(&self, query: &Query) -> Result<InternalRecords, MTTError> {
|
|
let indexed_ids = self.indexes.index_ids();
|
|
let mut indexed: HashMap<Uuid, Calculation> = HashMap::new();
|
|
let mut unindexed: HashMap<Uuid, Calculation> = HashMap::new();
|
|
for (field, data) in query.iter() {
|
|
let id = match self.docdef.get_field_id(field) {
|
|
Ok(fid) => fid,
|
|
Err(err) => return Err(err),
|
|
};
|
|
if indexed_ids.contains(&id) {
|
|
indexed.insert(id, data.clone());
|
|
} else {
|
|
unindexed.insert(id, data.clone());
|
|
}
|
|
}
|
|
let mut oids: HashSet<Oid> = self.docs.keys().cloned().collect();
|
|
for (field_id, calculation) in indexed.iter() {
|
|
let holder = match self.indexes.pull(field_id, calculation) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
oids = oids.intersection(&holder).cloned().collect();
|
|
}
|
|
let mut records = InternalRecords::new();
|
|
for oid in oids.iter() {
|
|
records.insert(oid.clone(), self.docs.get(oid).unwrap().clone());
|
|
}
|
|
let holder = oids.clone();
|
|
for (oid, record) in records.iter() {
|
|
for (field_id, calc) in unindexed.iter() {
|
|
match calc.calculate(record.get(field_id).unwrap()) {
|
|
Field::Boolean(data) => {
|
|
if !data {
|
|
oids.remove(oid);
|
|
break;
|
|
}
|
|
}
|
|
_ => {
|
|
oids.remove(oid);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let removals = holder.difference(&oids);
|
|
for oid in removals {
|
|
records.remove(oid);
|
|
}
|
|
Ok(records)
|
|
}
|
|
|
|
fn query(&self, msg: &Message) {
|
|
let query = match msg.get_action() {
|
|
MsgAction::Query(data) => data,
|
|
_ => unreachable!("should receive a query"),
|
|
};
|
|
let records = match self.run_query(query) {
|
|
Ok(data) => data,
|
|
Err(err) => {
|
|
let reply = msg.response(err);
|
|
self.queue.send(reply).unwrap();
|
|
return;
|
|
}
|
|
};
|
|
let recs = Records::with_data(self.docdef.get_field_names().clone(), records);
|
|
self.queue.send(msg.response(recs.clone())).unwrap();
|
|
self.queue
|
|
.send(msg.response(MsgAction::OnQuery(recs)))
|
|
.unwrap();
|
|
}
|
|
|
|
fn run_update(
|
|
&mut self,
|
|
original: &InternalRecords,
|
|
update: &Update,
|
|
msg: &Message,
|
|
) -> Result<Records, MTTError> {
|
|
let mut changes: HashMap<Uuid, &CalcValue> = HashMap::new();
|
|
for (key, value) in update.get_values().iter() {
|
|
let field_id = match self.docdef.get_field_id(key) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
changes.insert(field_id, value);
|
|
}
|
|
let mut indexes = self.docdef.create_indexes();
|
|
let mut updates = InternalRecords::new();
|
|
for (oid, record) in original.iter() {
|
|
let mut holder = record.clone();
|
|
for (field_id, value) in changes.iter() {
|
|
let field = value.get(holder.get(field_id).unwrap());
|
|
let correction = match self.validate(field_id, &field) {
|
|
Ok(data) => data,
|
|
Err(err) => return Err(err),
|
|
};
|
|
holder.insert(field_id.clone(), correction.clone());
|
|
match indexes.add_to_index(&field_id, correction, oid.clone()) {
|
|
Ok(_) => {}
|
|
Err(err) => return Err(err),
|
|
}
|
|
}
|
|
updates.insert(oid.clone(), holder);
|
|
}
|
|
for (oid, new_rec) in updates.iter() {
|
|
let old_rec = original.get(oid).unwrap();
|
|
for (field_id, index) in self.indexes.iter_mut() {
|
|
index.remove(old_rec.get(field_id).unwrap(), oid);
|
|
index
|
|
.add(new_rec.get(field_id).unwrap().clone(), oid.clone())
|
|
.unwrap();
|
|
}
|
|
self.docs.insert(oid.clone(), new_rec.clone());
|
|
}
|
|
let recs = Records::with_data(self.docdef.get_field_names().clone(), updates);
|
|
self.queue
|
|
.send(msg.response(MsgAction::OnUpdate(recs.clone())))
|
|
.unwrap();
|
|
Ok(recs)
|
|
}
|
|
|
|
fn update(&mut self, msg: &Message) {
|
|
let update = match msg.get_action() {
|
|
MsgAction::Update(data) => data,
|
|
_ => unreachable!("should receive a update"),
|
|
};
|
|
let original = match self.run_query(update.get_query()) {
|
|
Ok(result) => result,
|
|
Err(err) => {
|
|
let reply = msg.response(err);
|
|
self.queue.send(reply).unwrap();
|
|
return;
|
|
}
|
|
};
|
|
let data = match self.run_update(&original, update, msg) {
|
|
Ok(output) => output,
|
|
Err(err) => {
|
|
let reply = msg.response(err);
|
|
self.queue.send(reply).unwrap();
|
|
return;
|
|
}
|
|
};
|
|
self.queue.send(msg.response(data)).unwrap();
|
|
}
|
|
|
|
fn existing_query(&mut self, msg: &Message, action: &MsgAction) {
|
|
let recs = match msg.get_action() {
|
|
MsgAction::OnQuery(data) => data,
|
|
_ => unreachable!("should only receive on messages"),
|
|
};
|
|
match action {
|
|
MsgAction::Update(change) => self
|
|
.run_update(recs.get_internal_records(), change, msg)
|
|
.unwrap(),
|
|
_ => panic!("should not get here"),
|
|
};
|
|
}
|
|
|
|
fn trigger(&self, msg: &Message, action: &MsgAction) {
|
|
self.queue
|
|
.send(msg.forward(self.name_id.clone(), action.clone()))
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod document_files {
|
|
use super::*;
|
|
use crate::support_tests::TIMEOUT;
|
|
use std::{sync::mpsc::RecvTimeoutError, thread::sleep};
|
|
|
|
fn standard_paths() -> Vec<Path> {
|
|
[
|
|
Path::new(Include::All, Include::All, Include::Just(Action::Records)),
|
|
Path::new(Include::All, Include::All, Include::Just(Action::Reply)),
|
|
Path::new(Include::All, Include::All, Include::Just(Action::Error)),
|
|
]
|
|
.to_vec()
|
|
}
|
|
|
|
struct TestDocument {
|
|
docdef: DocDef,
|
|
queue: Queue,
|
|
sender_id: Uuid,
|
|
rx: Receiver<Message>,
|
|
}
|
|
|
|
impl TestDocument {
|
|
fn new(field_types: Vec<FieldType>) -> Self {
|
|
let doc_name = Name::english(Uuid::new_v4().to_string().as_str());
|
|
let mut docdef = DocDef::new(doc_name.clone());
|
|
let mut count = 0;
|
|
for field_type in field_types.iter() {
|
|
docdef.add_field(
|
|
Name::english(format!("field{}", count).as_str()),
|
|
field_type.clone(),
|
|
);
|
|
count += 1;
|
|
}
|
|
let (tx, rx) = channel();
|
|
let mut queue = Queue::new();
|
|
let id = queue.add_sender(tx);
|
|
Self {
|
|
docdef: docdef,
|
|
queue: queue,
|
|
sender_id: id,
|
|
rx: rx,
|
|
}
|
|
}
|
|
|
|
fn get_docdef(&self) -> &DocDef {
|
|
&self.docdef
|
|
}
|
|
|
|
fn get_docdef_mut(&mut self) -> &mut DocDef {
|
|
&mut self.docdef
|
|
}
|
|
|
|
fn get_queue(&mut self) -> Queue {
|
|
self.queue.clone()
|
|
}
|
|
|
|
fn get_receiver(&self) -> &Receiver<Message> {
|
|
&self.rx
|
|
}
|
|
|
|
fn get_name_id(&self) -> Uuid {
|
|
let reg_request = RegMsg::GetNameID(self.docdef.get_document_names()[0].clone());
|
|
let reg_msg = Register::new(self.get_sender_id(), reg_request);
|
|
let msg = Message::new(NameType::None, reg_msg);
|
|
self.queue.send(msg).unwrap();
|
|
let result = self.rx.recv().unwrap();
|
|
match result.get_action() {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::DocumentNameID(output) => output.clone(),
|
|
_ => unreachable!("should return a name id"),
|
|
},
|
|
_ => unreachable!("should return a name id"),
|
|
}
|
|
}
|
|
|
|
fn get_sender_id(&self) -> Uuid {
|
|
self.sender_id.clone()
|
|
}
|
|
|
|
fn send<A>(&self, action: A) -> Result<(), MTTError>
|
|
where
|
|
A: Into<MsgAction>,
|
|
{
|
|
let msg = Message::new(self.docdef.get_document_names()[0].clone(), action);
|
|
self.queue.send(msg)
|
|
}
|
|
|
|
fn start(&mut self, routes: Vec<Path>) {
|
|
let msg = Message::new(
|
|
self.docdef.get_document_names()[0].clone(),
|
|
self.docdef.clone(),
|
|
);
|
|
DocumentFile::start(self.queue.clone(), msg);
|
|
for route in routes.iter() {
|
|
let request =
|
|
Register::new(self.sender_id.clone(), RegMsg::AddRoute(route.clone()));
|
|
let add_route = Message::new(NameType::None, request);
|
|
self.queue.send(add_route).unwrap();
|
|
self.rx.recv().unwrap();
|
|
}
|
|
}
|
|
|
|
fn populate(&self, data: Vec<Field>) {
|
|
let mut add = Addition::new();
|
|
let mut count = 0;
|
|
for item in data.iter() {
|
|
add.add_field(
|
|
Name::english(format!("field{}", count).as_str()),
|
|
item.clone(),
|
|
);
|
|
count += 1;
|
|
}
|
|
self.send(add).unwrap();
|
|
match self.rx.recv_timeout(TIMEOUT) {
|
|
Ok(_) => {} // eats the addition response.
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Timeout => {}
|
|
_ => unreachable!("got {}, should have been ok or time out", err),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<DocDef> for TestDocument {
|
|
fn from(value: DocDef) -> Self {
|
|
let (tx, rx) = channel();
|
|
let mut queue = Queue::new();
|
|
let id = queue.add_sender(tx);
|
|
Self {
|
|
docdef: value,
|
|
queue: queue,
|
|
sender_id: id,
|
|
rx: rx,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_not_respond_to_create() {
|
|
let name = Name::english("quiet");
|
|
let docdef = DocDef::new(name.clone());
|
|
let mut test_doc: TestDocument = docdef.into();
|
|
let alt = Name::english("alternate");
|
|
test_doc.start(standard_paths());
|
|
let docdef = DocDef::new(alt);
|
|
let msg = Message::new(name.clone(), docdef);
|
|
test_doc.get_queue().send(msg).unwrap();
|
|
match test_doc.get_receiver().recv_timeout(TIMEOUT) {
|
|
Ok(msg) => unreachable!("should not receive: {:?}", msg),
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Timeout => {}
|
|
_ => unreachable!("should have timed out"),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_document_respond_to_requests() {
|
|
let name = Name::english("listen");
|
|
let docdef = DocDef::new(name.clone());
|
|
let mut test_doc: TestDocument = docdef.into();
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let msg_actions = [
|
|
MsgAction::Addition(Addition::new()),
|
|
MsgAction::Delete(Delete::new(Query::new())),
|
|
MsgAction::Query(Query::new().into()),
|
|
MsgAction::Show,
|
|
MsgAction::Update(Update::new(Query::new())),
|
|
];
|
|
for msg_action in msg_actions.iter() {
|
|
let msg = Message::new(name.clone(), msg_action.clone());
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = match test_doc.get_receiver().recv_timeout(TIMEOUT) {
|
|
Ok(data) => data.clone(),
|
|
Err(err) => unreachable!("for {:?} got {:?}", msg_action, err),
|
|
};
|
|
assert_eq!(
|
|
result.get_message_id(),
|
|
msg.get_message_id(),
|
|
"for {:?} response and reply ids should equal",
|
|
msg_action
|
|
);
|
|
match result.get_action() {
|
|
MsgAction::Reply(data) => {
|
|
assert_eq!(data.len(), 0, "for {:?} got {:?}", msg_action, result)
|
|
}
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(data.len(), 0, "for {:?} got {:?}", msg_action, result)
|
|
}
|
|
_ => unreachable!(
|
|
"for {:?} got {:?}: should have received a reply",
|
|
msg_action,
|
|
result.get_action()
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_not_respond_to_other_document_requests() {
|
|
let name = Name::english("quiet");
|
|
let alt = Name::english("alternate");
|
|
let docdef = DocDef::new(name.clone());
|
|
let mut test_doc: TestDocument = docdef.into();
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let reg_msg = Register::new(
|
|
test_doc.get_sender_id(),
|
|
RegMsg::AddDocName([alt.clone()].to_vec()),
|
|
);
|
|
let setup = Message::new(NameType::None, reg_msg.clone());
|
|
queue.send(setup).unwrap();
|
|
test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let msg_actions = [
|
|
MsgAction::Addition(Addition::new()),
|
|
MsgAction::Create(DocDef::new(name.clone())),
|
|
MsgAction::Delete(Delete::new(Query::new())),
|
|
MsgAction::Query(Query::new().into()),
|
|
MsgAction::Show,
|
|
MsgAction::Update(Update::new(Query::new())),
|
|
];
|
|
let mut msgs: HashMap<Uuid, MsgAction> = HashMap::new();
|
|
for msg_action in msg_actions.iter() {
|
|
let msg = Message::new(alt.clone(), msg_action.clone());
|
|
msgs.insert(msg.get_message_id().clone(), msg_action.clone());
|
|
queue.send(msg).unwrap();
|
|
}
|
|
match test_doc.get_receiver().recv_timeout(TIMEOUT) {
|
|
Ok(msg) => unreachable!(
|
|
"for {:?} should not receive: {:?}",
|
|
msgs.get(msg.get_message_id()).unwrap(),
|
|
msg
|
|
),
|
|
Err(err) => match err {
|
|
RecvTimeoutError::Timeout => {}
|
|
_ => unreachable!("got {:?}, should have timed out", err),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn query_sends_on_query_message() {
|
|
let count = 5;
|
|
let mut data: HashSet<Field> = HashSet::new();
|
|
while data.len() < count {
|
|
let field: Field = Uuid::new_v4().into();
|
|
data.insert(field);
|
|
}
|
|
let mut test_doc = TestDocument::new([FieldType::Uuid].to_vec());
|
|
let doc_name = test_doc.get_docdef().get_document_names()[0].clone();
|
|
let queue = test_doc.get_queue();
|
|
let routes = [Path::new(
|
|
Include::All,
|
|
Include::All,
|
|
Include::Just(Action::OnQuery),
|
|
)]
|
|
.to_vec();
|
|
test_doc.start(routes);
|
|
let name_id: NameType = test_doc.get_name_id().into();
|
|
for item in data.iter() {
|
|
test_doc.populate([item.clone()].to_vec());
|
|
}
|
|
let msg = Message::new(doc_name.clone(), Query::new());
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(
|
|
result.get_message_id(),
|
|
msg.get_message_id(),
|
|
"message ids should match"
|
|
);
|
|
assert_eq!(
|
|
result.get_document_id(),
|
|
&name_id,
|
|
"document name ids should match"
|
|
);
|
|
match result.get_action() {
|
|
MsgAction::OnQuery(output) => {
|
|
assert_eq!(
|
|
output.len(),
|
|
count,
|
|
"wrong number of entries: got {:?}",
|
|
output
|
|
);
|
|
for rec in output.iter() {
|
|
assert!(data.contains(&rec.get(Name::english("field0")).unwrap()));
|
|
}
|
|
}
|
|
_ => unreachable!("should never get here"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn send_on_addition_message() {
|
|
let data: Field = Uuid::new_v4().into();
|
|
let field_name = Name::english("field0");
|
|
let mut test_doc = TestDocument::new([FieldType::Uuid].to_vec());
|
|
let doc_name = test_doc.get_docdef().get_document_names()[0].clone();
|
|
let queue = test_doc.get_queue();
|
|
let routes = vec![Path::new(
|
|
Include::All,
|
|
Include::All,
|
|
Include::Just(Action::OnAddition),
|
|
)];
|
|
test_doc.start(routes);
|
|
let name_id: NameType = test_doc.get_name_id().into();
|
|
let mut add = Addition::new();
|
|
add.add_field(field_name.clone(), data.clone());
|
|
let msg = Message::new(doc_name.clone(), add);
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(
|
|
result.get_message_id(),
|
|
msg.get_message_id(),
|
|
"message ids should match"
|
|
);
|
|
assert_eq!(
|
|
result.get_document_id(),
|
|
&name_id,
|
|
"document name ids should match"
|
|
);
|
|
match result.get_action() {
|
|
MsgAction::OnAddition(output) => {
|
|
assert_eq!(output.len(), 1, "wrong number of entries: got {:?}", output);
|
|
for rec in output.iter() {
|
|
assert_eq!(rec.get(Name::english("field0")).unwrap(), data);
|
|
}
|
|
}
|
|
_ => unreachable!("should never get here"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn sends_on_delete_message() {
|
|
let count = 5;
|
|
let mut data: HashSet<Field> = HashSet::new();
|
|
while data.len() < count {
|
|
let field: Field = Uuid::new_v4().into();
|
|
data.insert(field);
|
|
}
|
|
let mut test_doc = TestDocument::new([FieldType::Uuid].to_vec());
|
|
let doc_name = test_doc.get_docdef().get_document_names()[0].clone();
|
|
let queue = test_doc.get_queue();
|
|
let routes = [Path::new(
|
|
Include::All,
|
|
Include::All,
|
|
Include::Just(Action::OnDelete),
|
|
)]
|
|
.to_vec();
|
|
test_doc.start(routes);
|
|
let name_id: NameType = test_doc.get_name_id().into();
|
|
for item in data.iter() {
|
|
test_doc.populate([item.clone()].to_vec());
|
|
}
|
|
let msg = Message::new(doc_name.clone(), Delete::new(Query::new()));
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(
|
|
result.get_message_id(),
|
|
msg.get_message_id(),
|
|
"message ids should match"
|
|
);
|
|
assert_eq!(
|
|
result.get_document_id(),
|
|
&name_id,
|
|
"document name ids should match"
|
|
);
|
|
match result.get_action() {
|
|
MsgAction::OnDelete(output) => {
|
|
assert_eq!(
|
|
output.len(),
|
|
count,
|
|
"wrong number of entries: got {:?}",
|
|
output
|
|
);
|
|
for rec in output.iter() {
|
|
assert!(data.contains(&rec.get(Name::english("field0")).unwrap()));
|
|
}
|
|
}
|
|
_ => unreachable!("should never get here"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn sends_on_update_message() {
|
|
let count = 5;
|
|
let field_name = Name::english("field0");
|
|
let mut data: HashSet<Field> = HashSet::new();
|
|
while data.len() < count {
|
|
let field: Field = Uuid::new_v4().into();
|
|
data.insert(field);
|
|
}
|
|
let mut test_doc = TestDocument::new([FieldType::Uuid].to_vec());
|
|
let doc_name = test_doc.get_docdef().get_document_names()[0].clone();
|
|
let queue = test_doc.get_queue();
|
|
let routes = [Path::new(
|
|
Include::All,
|
|
Include::All,
|
|
Include::Just(Action::OnUpdate),
|
|
)]
|
|
.to_vec();
|
|
test_doc.start(routes);
|
|
let name_id: NameType = test_doc.get_name_id().into();
|
|
for item in data.iter() {
|
|
test_doc.populate([item.clone()].to_vec());
|
|
}
|
|
let mut update = Update::new(Query::new());
|
|
update
|
|
.get_values_mut()
|
|
.add_field(field_name.clone(), Uuid::nil());
|
|
let msg = Message::new(doc_name.clone(), update);
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(
|
|
result.get_message_id(),
|
|
msg.get_message_id(),
|
|
"message ids should match"
|
|
);
|
|
assert_eq!(
|
|
result.get_document_id(),
|
|
&name_id,
|
|
"document name ids should match"
|
|
);
|
|
match result.get_action() {
|
|
MsgAction::OnUpdate(output) => {
|
|
assert_eq!(
|
|
output.len(),
|
|
count,
|
|
"wrong number of entries: got {:?}",
|
|
output
|
|
);
|
|
for rec in output.iter() {
|
|
assert_eq!(rec.get(&field_name).unwrap(), Uuid::nil().into());
|
|
}
|
|
}
|
|
_ => unreachable!("should never get here"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_document_be_added() {
|
|
let doc_name = Name::english("document");
|
|
let mut docdef = DocDef::new(doc_name.clone());
|
|
let name = Name::english("field");
|
|
let data = Uuid::new_v4();
|
|
docdef.add_field(name.clone(), FieldType::Uuid);
|
|
let mut test_doc: TestDocument = docdef.clone().into();
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let mut new_doc = Addition::new();
|
|
new_doc.add_field(name.clone(), data.clone());
|
|
let testing = |msg: Message| {
|
|
queue.send(msg.clone()).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
match result.get_action() {
|
|
MsgAction::Records(output) => {
|
|
assert_eq!(output.len(), 1);
|
|
for rec in output.iter() {
|
|
let holder = rec.get(&name).unwrap();
|
|
match holder {
|
|
Field::Uuid(field_data) => assert_eq!(field_data, data),
|
|
_ => unreachable!("got {:?}, should have been uuid", holder),
|
|
}
|
|
}
|
|
}
|
|
_ => unreachable!(
|
|
"\n\ngot {:?}\n\nfor {:?}\n\nshould have been records",
|
|
result, msg
|
|
),
|
|
}
|
|
};
|
|
testing(Message::new(doc_name.clone(), new_doc));
|
|
testing(Message::new(doc_name.clone(), Query::new()));
|
|
}
|
|
|
|
#[test]
|
|
fn can_add_multiple_documents() {
|
|
let doc_name = Name::english("multiple");
|
|
let mut docdef = DocDef::new(doc_name.clone());
|
|
let name = Name::english("count");
|
|
docdef.add_field(name.clone(), FieldType::Integer);
|
|
let mut test_doc: TestDocument = docdef.clone().into();
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let count = 5;
|
|
for i in 0..count {
|
|
let mut new_doc = Addition::new();
|
|
new_doc.add_field(name.clone(), i);
|
|
queue.send(Message::new(doc_name.clone(), new_doc)).unwrap();
|
|
test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
}
|
|
queue.send(Message::new(doc_name, Query::new())).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
let mut entries: HashSet<i128> = (0..count).collect();
|
|
match action {
|
|
MsgAction::Records(output) => {
|
|
let entry_count: usize = count.try_into().unwrap();
|
|
assert_eq!(
|
|
output.len(),
|
|
entry_count,
|
|
"should have the same number of entries"
|
|
);
|
|
for record in output.iter() {
|
|
let holder = record.get(&name).unwrap();
|
|
let data = match holder {
|
|
Field::Integer(item) => item.clone(),
|
|
_ => unreachable!("got {:?}, should have been integer", holder),
|
|
};
|
|
assert!(
|
|
entries.contains(&data),
|
|
"did not find {:?} in {:?}",
|
|
data,
|
|
entries
|
|
);
|
|
entries.remove(&data);
|
|
}
|
|
}
|
|
_ => unreachable!("\n\ngot {:?}\n\nshould have been records", action),
|
|
}
|
|
assert!(entries.is_empty(), "did not use {:?}", entries);
|
|
}
|
|
|
|
#[test]
|
|
fn errors_on_wrong_field_name() {
|
|
let mut test_doc = TestDocument::new(Vec::new());
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let name = Name::english("bad");
|
|
let mut addition = Addition::new();
|
|
addition.add_field(name.clone(), "doesn't matter");
|
|
queue
|
|
.send(Message::new(
|
|
test_doc.get_docdef().get_document_names()[0].clone(),
|
|
addition,
|
|
))
|
|
.unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
match result.get_action() {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::NameNotFound(data) => assert_eq!(data, &name),
|
|
_ => unreachable!("got {:?}: should have been document field not found.", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have been an error", result.get_action()),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn errors_on_wrong_field_type() {
|
|
let mut test_doc = TestDocument::new([FieldType::Uuid].to_vec());
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let mut addition = Addition::new();
|
|
addition.add_field(Name::english("field0"), "string");
|
|
queue
|
|
.send(Message::new(
|
|
test_doc.get_docdef().get_document_names()[0].clone(),
|
|
addition,
|
|
))
|
|
.unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
match result.get_action() {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::DocumentFieldWrongDataType(expected, got) => {
|
|
assert_eq!(got, &FieldType::StaticString);
|
|
assert_eq!(expected, &FieldType::Uuid);
|
|
}
|
|
_ => unreachable!(
|
|
"got {:?}: should have been document field data mismatch.",
|
|
err
|
|
),
|
|
},
|
|
_ => unreachable!("got {:?}: should have been an error", result.get_action()),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn errors_on_missing_fields() {
|
|
let mut test_doc = TestDocument::new([FieldType::Integer, FieldType::Integer].to_vec());
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let mut addition = Addition::new();
|
|
addition.add_field(Name::english("field0"), 1);
|
|
queue
|
|
.send(Message::new(
|
|
test_doc.get_docdef().get_document_names()[0].clone(),
|
|
addition,
|
|
))
|
|
.unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
match result.get_action() {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::InvalidNone => {}
|
|
_ => unreachable!("got {:?}: should have been document field missing", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have been an error", result.get_action()),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_query_return_related_entries() {
|
|
let mut test_doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let count = 5;
|
|
let expected = 3;
|
|
for i in 0..count {
|
|
test_doc.populate([i.into()].to_vec());
|
|
}
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(expected.clone()).unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
queue
|
|
.send(Message::new(
|
|
test_doc.get_docdef().get_document_names()[0].clone(),
|
|
query,
|
|
))
|
|
.unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(
|
|
data.len(),
|
|
1,
|
|
"should return one entry containing {:?} got:\n{:?}",
|
|
expected,
|
|
action
|
|
);
|
|
for doc in data.iter() {
|
|
assert_eq!(doc.get(&Name::english("field0")).unwrap(), expected.into());
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have been a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_query_work_with_greater_than() {
|
|
let mut test_doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
test_doc.populate([1.into()].to_vec());
|
|
test_doc.populate([2.into()].to_vec());
|
|
let mut calc = Calculation::new(Operand::GreaterThan);
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
calc.add_value(1).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
queue
|
|
.send(Message::new(
|
|
test_doc.get_docdef().get_document_names()[0].clone(),
|
|
query,
|
|
))
|
|
.unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(
|
|
data.len(),
|
|
1,
|
|
"should return one entry containing 2 got:\n{:?}",
|
|
action
|
|
);
|
|
for doc in data.iter() {
|
|
assert_eq!(doc.get(&Name::english("field0")).unwrap(), 2.into());
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have been a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn gets_all_documents_in_query() {
|
|
let mut test_doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let data = 1;
|
|
let count = 5;
|
|
for i in 0..count {
|
|
let holder: i128 = (i + count).try_into().unwrap();
|
|
test_doc.populate([holder.into()].to_vec());
|
|
test_doc.populate([data.into()].to_vec());
|
|
}
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(data.clone()).unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
queue
|
|
.send(Message::new(
|
|
test_doc.get_docdef().get_document_names()[0].clone(),
|
|
query,
|
|
))
|
|
.unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(
|
|
docs.len(),
|
|
count,
|
|
"should return one entry containing {:?} got:\n{:?}",
|
|
data,
|
|
action
|
|
);
|
|
for doc in docs.iter() {
|
|
assert_eq!(doc.get(&Name::english("field0")).unwrap(), data.into());
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have been a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn query_should_work_with_multiple_fields() {
|
|
let mut doc =
|
|
TestDocument::new([FieldType::StaticString, FieldType::StaticString].to_vec());
|
|
doc.start(standard_paths());
|
|
let values = [
|
|
["a".into(), "a".into()].to_vec(),
|
|
["a".into(), "b".into()].to_vec(),
|
|
["b".into(), "a".into()].to_vec(),
|
|
["b".into(), "b".into()].to_vec(),
|
|
];
|
|
for value in values.iter() {
|
|
doc.populate(value.clone());
|
|
}
|
|
let mut query = Query::new();
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("a").unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
|
.unwrap();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("b").unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
|
.unwrap();
|
|
query.add(Name::english("field1"), calc);
|
|
doc.send(query).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
let afield: Field = "a".into();
|
|
let bfield: Field = "b".into();
|
|
assert_eq!(data.len(), 1, "should return one entry:\n{:?}", action);
|
|
for doc in data.iter() {
|
|
assert_eq!(doc.get(&Name::english("field0")).unwrap(), afield);
|
|
assert_eq!(doc.get(&Name::english("field1")).unwrap(), bfield);
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have been a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn query_should_work_with_multiple_inexed_fields() {
|
|
let mut doc =
|
|
TestDocument::new([FieldType::StaticString, FieldType::StaticString].to_vec());
|
|
let docdef = doc.get_docdef_mut();
|
|
docdef
|
|
.add_index(&Name::english("field0"), IndexType::Index)
|
|
.unwrap();
|
|
docdef
|
|
.add_index(&Name::english("field1"), IndexType::Index)
|
|
.unwrap();
|
|
doc.start(standard_paths());
|
|
let values = [
|
|
["a".into(), "a".into()].to_vec(),
|
|
["a".into(), "b".into()].to_vec(),
|
|
["b".into(), "a".into()].to_vec(),
|
|
["b".into(), "b".into()].to_vec(),
|
|
];
|
|
for value in values.iter() {
|
|
doc.populate(value.clone());
|
|
}
|
|
let mut query = Query::new();
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("a").unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
|
.unwrap();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("b").unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
|
.unwrap();
|
|
query.add(Name::english("field1"), calc);
|
|
doc.send(query).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
let afield: Field = "a".into();
|
|
let bfield: Field = "b".into();
|
|
assert_eq!(data.len(), 1, "should return one entry:\n{:?}", action);
|
|
for doc in data.iter() {
|
|
assert_eq!(doc.get(&Name::english("field0")).unwrap(), afield);
|
|
assert_eq!(doc.get(&Name::english("field1")).unwrap(), bfield);
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have been a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn query_should_work_with_mixed_inexed_fields() {
|
|
let mut doc =
|
|
TestDocument::new([FieldType::StaticString, FieldType::StaticString].to_vec());
|
|
let docdef = doc.get_docdef_mut();
|
|
docdef
|
|
.add_index(&Name::english("field0"), IndexType::Index)
|
|
.unwrap();
|
|
doc.start(standard_paths());
|
|
let values = [
|
|
["a".into(), "a".into()].to_vec(),
|
|
["a".into(), "b".into()].to_vec(),
|
|
["b".into(), "a".into()].to_vec(),
|
|
["b".into(), "b".into()].to_vec(),
|
|
];
|
|
for value in values.iter() {
|
|
doc.populate(value.clone());
|
|
}
|
|
let mut query = Query::new();
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("a").unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
|
.unwrap();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("b").unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
|
.unwrap();
|
|
query.add(Name::english("field1"), calc);
|
|
doc.send(query).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
let afield: Field = "a".into();
|
|
let bfield: Field = "b".into();
|
|
assert_eq!(data.len(), 1, "should return one entry:\n{:?}", action);
|
|
for doc in data.iter() {
|
|
assert_eq!(doc.get(&Name::english("field0")).unwrap(), afield);
|
|
assert_eq!(doc.get(&Name::english("field1")).unwrap(), bfield);
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have been a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn errors_on_bad_field_name() {
|
|
let mut doc = TestDocument::new(Vec::new());
|
|
doc.start(standard_paths());
|
|
let doc_name = doc.get_docdef().get_document_names()[0].clone();
|
|
let queue = doc.get_queue();
|
|
let rx = doc.get_receiver();
|
|
let field_name = Name::english("wrong");
|
|
let mut query = Query::new();
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("something").unwrap();
|
|
query.add(field_name.clone(), calc);
|
|
let msg = Message::new(doc_name, query);
|
|
queue.send(msg).unwrap();
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Error(data) => match data {
|
|
MTTError::NameNotFound(output) => assert_eq!(output, &field_name),
|
|
_ => unreachable!("got {:?}: should been field not found", data),
|
|
},
|
|
_ => unreachable!("got {:?}: should have been a error", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "may no longer be a valid test. should stop at add_value"]
|
|
fn errors_on_bad_field_type() {
|
|
let mut doc = TestDocument::new([FieldType::Uuid].to_vec());
|
|
doc.start(standard_paths());
|
|
doc.populate([Uuid::nil().into()].to_vec());
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("notUUID").unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
.unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
doc.send(query).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(data.len(), 0, "should return one entry:\n{:?}", action);
|
|
}
|
|
_ => unreachable!("got {:?}: should have been a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn errors_on_bad_field_type_with_index() {
|
|
let mut doc = TestDocument::new([FieldType::Uuid].to_vec());
|
|
doc.get_docdef_mut()
|
|
.add_index(&Name::english("field0"), IndexType::Index)
|
|
.unwrap();
|
|
doc.start(standard_paths());
|
|
doc.populate([Uuid::nil().into()].to_vec());
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value("notUUID").unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
doc.send(query).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Error(data) => match data {
|
|
MTTError::FieldInvalidType => {}
|
|
_ => unreachable!("got {:?}: should been invalid field type", data),
|
|
},
|
|
_ => unreachable!("got {:?}: should have been a error", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_use_default_values() {
|
|
let doc_name = Name::english("default");
|
|
let mut docdef = DocDef::new(doc_name.clone());
|
|
let field_name = Name::english("holder");
|
|
docdef.add_field(field_name.clone(), FieldType::StaticString);
|
|
docdef
|
|
.set_default(&field_name, FieldType::StaticString)
|
|
.unwrap();
|
|
let mut test_doc: TestDocument = docdef.into();
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let rx = test_doc.get_receiver();
|
|
let new_doc = Addition::new();
|
|
let msg = Message::new(doc_name, new_doc);
|
|
queue.send(msg).unwrap();
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), 1);
|
|
for doc in docs.iter() {
|
|
let expected: Field = "".into();
|
|
assert_eq!(doc.get(&field_name).unwrap(), expected);
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_a_default_value_be_set() {
|
|
let doc_name = Name::english("assigned");
|
|
let mut docdef = DocDef::new(doc_name.clone());
|
|
let field_name = Name::english("id");
|
|
docdef.add_field(field_name.clone(), FieldType::Uuid);
|
|
docdef.set_default(&field_name, Uuid::nil()).unwrap();
|
|
let mut test_doc: TestDocument = docdef.into();
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let rx = test_doc.get_receiver();
|
|
let new_doc = Addition::new();
|
|
let msg = Message::new(doc_name, new_doc);
|
|
queue.send(msg).unwrap();
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), 1);
|
|
for doc in docs.iter() {
|
|
let expected: Field = Uuid::nil().into();
|
|
assert_eq!(doc.get(&field_name).unwrap(), expected);
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_default_values_be_overridden() {
|
|
let doc_name = Name::english("assigned");
|
|
let mut docdef = DocDef::new(doc_name.clone());
|
|
let field_name = Name::english("id");
|
|
docdef.add_field(field_name.clone(), FieldType::Uuid);
|
|
docdef.set_default(&field_name, FieldType::Uuid).unwrap();
|
|
let mut test_doc: TestDocument = docdef.into();
|
|
test_doc.start(standard_paths());
|
|
let queue = test_doc.get_queue();
|
|
let rx = test_doc.get_receiver();
|
|
let mut new_doc = Addition::new();
|
|
new_doc.add_field(&field_name, Uuid::nil());
|
|
let msg = Message::new(doc_name, new_doc);
|
|
queue.send(msg).unwrap();
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), 1);
|
|
for doc in docs.iter() {
|
|
let expected: Field = Uuid::nil().into();
|
|
assert_eq!(doc.get(&field_name).unwrap(), expected);
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn empty_update_query_results_in_zero_changes() {
|
|
let count = 5;
|
|
let mut ids: HashSet<Uuid> = HashSet::new();
|
|
while ids.len() < count {
|
|
ids.insert(Uuid::new_v4());
|
|
}
|
|
let id = ids.iter().last().unwrap().clone();
|
|
ids.remove(&id);
|
|
let mut doc = TestDocument::new([FieldType::Uuid].to_vec());
|
|
doc.start(standard_paths());
|
|
for id in ids.iter() {
|
|
doc.populate([id.clone().into()].to_vec());
|
|
}
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
.unwrap();
|
|
calc.add_value(Uuid::nil()).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut update = Update::new(query);
|
|
update
|
|
.get_values_mut()
|
|
.add_field(Name::english("field0"), Uuid::nil());
|
|
doc.send(update).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => assert_eq!(docs.len(), 0),
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn changes_information_requested() {
|
|
let mut doc = TestDocument::new([FieldType::Uuid, FieldType::StaticString].to_vec());
|
|
doc.start(standard_paths());
|
|
let doc_name = doc.get_docdef().get_document_names()[0].clone();
|
|
let old = "old";
|
|
let new = "new";
|
|
let id = Uuid::new_v4();
|
|
doc.populate([id.into(), old.into()].to_vec());
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
.unwrap();
|
|
calc.add_value(id.clone()).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut update = Update::new(query);
|
|
update
|
|
.get_values_mut()
|
|
.add_field(Name::english("field1"), new);
|
|
let mut testing = |msg: Message| {
|
|
doc.get_queue().send(msg.clone()).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg.get_message_id());
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), 1, "for {:?}, should have one entry", msg);
|
|
for doc in docs.iter() {
|
|
assert_eq!(doc.get(Name::english("field0")).unwrap(), id.into());
|
|
assert_eq!(doc.get(Name::english("field1")).unwrap(), new.into());
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
};
|
|
testing(Message::new(doc_name.clone(), update));
|
|
testing(Message::new(doc_name.clone(), Query::new()));
|
|
}
|
|
|
|
#[test]
|
|
fn changes_only_the_queried() {
|
|
let mut doc = TestDocument::new([FieldType::Integer, FieldType::StaticString].to_vec());
|
|
doc.start(standard_paths());
|
|
let doc_name = doc.get_docdef().get_document_names()[0].clone();
|
|
let old = "old";
|
|
let new = "new";
|
|
let count = 5;
|
|
let field_count = count.clone().try_into().unwrap();
|
|
let picked = 3;
|
|
for i in 0..field_count {
|
|
doc.populate([i.into(), old.into()].to_vec());
|
|
}
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
calc.add_value(picked.clone()).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut update = Update::new(query);
|
|
update
|
|
.get_values_mut()
|
|
.add_field(Name::english("field1"), new);
|
|
doc.get_queue()
|
|
.send(Message::new(doc_name.clone(), update))
|
|
.unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), 1, "should have one entry");
|
|
for doc in docs.iter() {
|
|
assert_eq!(doc.get(Name::english("field0")).unwrap(), picked.into());
|
|
assert_eq!(doc.get(Name::english("field1")).unwrap(), new.into());
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
doc.get_queue()
|
|
.send(Message::new(doc_name.clone(), Query::new()))
|
|
.unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), count, "should have one entry");
|
|
for doc in docs.iter() {
|
|
if doc.get(Name::english("field0")).unwrap() == picked.into() {
|
|
assert_eq!(
|
|
doc.get(Name::english("field1")).unwrap(),
|
|
new.into(),
|
|
"{:?}",
|
|
docs
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
doc.get(Name::english("field1")).unwrap(),
|
|
old.into(),
|
|
"{:?}",
|
|
docs
|
|
);
|
|
}
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_handle_multiple_updates() {
|
|
let mut doc = TestDocument::new([FieldType::Integer, FieldType::StaticString].to_vec());
|
|
doc.start(standard_paths());
|
|
let doc_name = doc.get_docdef().get_document_names()[0].clone();
|
|
let old = "old";
|
|
let new = "new";
|
|
let count = 5;
|
|
let picked = 3;
|
|
for _ in 0..count {
|
|
doc.populate([picked.into(), old.into()].to_vec());
|
|
}
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
calc.add_value(picked.clone()).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut update = Update::new(query);
|
|
update
|
|
.get_values_mut()
|
|
.add_field(Name::english("field1"), new);
|
|
let mut testing = |msg: Message| {
|
|
doc.get_queue().send(msg).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), count, "should have one entry");
|
|
for doc in docs.iter() {
|
|
assert_eq!(doc.get(Name::english("field0")).unwrap(), picked.into());
|
|
assert_eq!(doc.get(Name::english("field1")).unwrap(), new.into());
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
};
|
|
testing(Message::new(doc_name.clone(), update));
|
|
testing(Message::new(doc_name.clone(), Query::new()));
|
|
}
|
|
|
|
#[test]
|
|
fn update_errors_on_bad_field_name() {
|
|
let mut doc = TestDocument::new([FieldType::Uuid, FieldType::StaticString].to_vec());
|
|
doc.start(standard_paths());
|
|
let id = Uuid::new_v4();
|
|
let old = "old";
|
|
let new = "new";
|
|
let bad_name = Name::english("wrong");
|
|
doc.populate([id.into(), old.into()].to_vec());
|
|
let mut update = Update::new(Query::new());
|
|
update.get_values_mut().add_field(bad_name.clone(), new);
|
|
doc.send(update).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::NameNotFound(data) => assert_eq!(data, &bad_name),
|
|
_ => unreachable!("got {:?}: should have gotten an missing field", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have gotten an error", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn update_errors_on_bad_field_type() {
|
|
let mut doc = TestDocument::new([FieldType::Uuid, FieldType::StaticString].to_vec());
|
|
doc.start(standard_paths());
|
|
let id = Uuid::new_v4();
|
|
let old = "old";
|
|
let new = Uuid::nil();
|
|
doc.populate([id.into(), old.into()].to_vec());
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
.unwrap();
|
|
calc.add_value(id.clone()).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut update = Update::new(query);
|
|
update
|
|
.get_values_mut()
|
|
.add_field(Name::english("field1"), new);
|
|
doc.send(update).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::DocumentFieldWrongDataType(expected, got) => {
|
|
assert_eq!(expected, &FieldType::StaticString);
|
|
assert_eq!(got, &FieldType::Uuid);
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten incorrect file type", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have gotten an error", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_update_maintain_unique_fields() {
|
|
let mut test_doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
test_doc
|
|
.get_docdef_mut()
|
|
.add_index(&Name::english("field0"), IndexType::Unique)
|
|
.unwrap();
|
|
test_doc.start(standard_paths());
|
|
let fname = Name::english("field0");
|
|
let old = 3;
|
|
let new = 5;
|
|
test_doc.populate([old.into()].to_vec());
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
calc.add_value(old.clone()).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut update = Update::new(query);
|
|
update.get_values_mut().add_field(&fname, new);
|
|
test_doc.send(update).unwrap();
|
|
test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let mut should_clear = Addition::new();
|
|
should_clear.add_field(fname.clone(), old);
|
|
let mut should_error = Addition::new();
|
|
should_error.add_field(fname.clone(), new);
|
|
test_doc.send(should_clear).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), 1, "should have one entry");
|
|
for doc in docs.iter() {
|
|
assert_eq!(doc.get(&fname).unwrap(), old.into());
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten records", action),
|
|
}
|
|
test_doc.send(should_error).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::FieldDuplicate => {}
|
|
_ => unreachable!("got {:?}: should have gotten incorrect file type", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have gotten an error", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unique_value_remains_available_if_failure_occurs() {
|
|
let f0name = Name::english("field0");
|
|
let f1name = Name::english("field1");
|
|
let mut test_doc = TestDocument::new([FieldType::Uuid, FieldType::Uuid].to_vec());
|
|
test_doc
|
|
.get_docdef_mut()
|
|
.add_index(&f0name, IndexType::Unique)
|
|
.unwrap();
|
|
test_doc.start(standard_paths());
|
|
let f0data = Uuid::new_v4();
|
|
let f1bad_data = "NotUuid";
|
|
let f1good_data = Uuid::nil();
|
|
let mut bad_addition = Addition::new();
|
|
bad_addition.add_field(&f0name, f0data.clone());
|
|
bad_addition.add_field(&f1name, f1bad_data);
|
|
let mut good_addition = Addition::new();
|
|
good_addition.add_field(&f0name, f0data.clone());
|
|
good_addition.add_field(&f1name, f1good_data.clone());
|
|
test_doc.send(bad_addition).unwrap();
|
|
test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
test_doc.send(good_addition).unwrap();
|
|
let result = test_doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(docs) => {
|
|
assert_eq!(docs.len(), 1, "should have one entry");
|
|
for doc in docs.iter() {
|
|
assert_eq!(doc.get(&f0name).unwrap(), f0data.into());
|
|
assert_eq!(doc.get(&f1name).unwrap(), f1good_data.into());
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten records", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn updating_unique_updates_index_entries() {
|
|
let fname = Name::english("field0");
|
|
let mut doc = TestDocument::new([FieldType::StaticString].to_vec());
|
|
doc.get_docdef_mut()
|
|
.add_index(&fname, IndexType::Unique)
|
|
.unwrap();
|
|
doc.start(standard_paths());
|
|
let old = "old";
|
|
let new = "new";
|
|
let fold: Field = old.into();
|
|
doc.populate([old.into()].to_vec());
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
|
.unwrap();
|
|
calc.add_value(old).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
let mut update = Update::new(query);
|
|
update.get_values_mut().add_field(fname.clone(), new);
|
|
doc.send(update).unwrap();
|
|
doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let mut old_addition = Addition::new();
|
|
old_addition.add_field(&fname, old);
|
|
doc.send(old_addition).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(data.len(), 1);
|
|
for doc in data.iter() {
|
|
assert_eq!(doc.get(&fname).unwrap(), fold);
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten a reply", action),
|
|
}
|
|
let mut new_addition = Addition::new();
|
|
new_addition.add_field(fname.clone(), new);
|
|
doc.send(new_addition).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::FieldDuplicate => {}
|
|
_ => unreachable!("got {:?}: should have gotten an missing field", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have gotten an error", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn update_does_not_override_unique_index() {
|
|
let f0name = Name::english("field0");
|
|
let f1name = Name::english("field1");
|
|
let mut doc = TestDocument::new([FieldType::Uuid, FieldType::StaticString].to_vec());
|
|
doc.get_docdef_mut()
|
|
.add_index(&f0name, IndexType::Unique)
|
|
.unwrap();
|
|
doc.start(standard_paths());
|
|
let count = 5;
|
|
let data = "data";
|
|
let mut ids: HashSet<Uuid> = HashSet::new();
|
|
while ids.len() < count {
|
|
ids.insert(Uuid::new_v4());
|
|
}
|
|
let holder = ids.iter().last().unwrap().clone();
|
|
ids.remove(&holder);
|
|
for id in ids.iter() {
|
|
doc.populate([id.clone().into(), data.into()].to_vec());
|
|
}
|
|
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
|
.unwrap();
|
|
calc.add_value(data).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(&f1name, calc);
|
|
let mut update = Update::new(query);
|
|
update.get_values_mut().add_field(&f0name, holder.clone());
|
|
doc.send(update).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::FieldDuplicate => {}
|
|
_ => unreachable!("got {:?}: should have gotten field duplicate", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have gotten an error", action),
|
|
}
|
|
let query = Query::new();
|
|
doc.send(query).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(data.len(), ids.len());
|
|
for doc in data.iter() {
|
|
match doc.get(&f0name).unwrap() {
|
|
Field::Uuid(id) => {
|
|
assert!(ids.contains(&id));
|
|
ids.remove(&id);
|
|
}
|
|
_ => unreachable!("did not get uuid"),
|
|
}
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten reply", action),
|
|
}
|
|
assert!(ids.is_empty(), "did not find {:?}", ids);
|
|
}
|
|
|
|
#[test]
|
|
fn can_calculate_field_values() {
|
|
let fname = Name::english("field0");
|
|
let mut doc = TestDocument::new([FieldType::DateTime].to_vec());
|
|
doc.start(standard_paths());
|
|
let duration = Duration::from_secs(300);
|
|
let mut calc = Calculation::new(Operand::Add);
|
|
calc.add_value(FieldType::DateTime).unwrap();
|
|
calc.add_value(duration.clone()).unwrap();
|
|
let mut addition = Addition::new();
|
|
addition.add_field(&fname, calc);
|
|
let start = Utc::now() + duration;
|
|
doc.send(addition).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let stop = Utc::now() + duration;
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(data.len(), 1);
|
|
for doc in data.iter() {
|
|
match doc.get(&fname).unwrap() {
|
|
Field::DateTime(datetime) => assert!(datetime > start && datetime < stop),
|
|
_ => unreachable!("did not get uuid"),
|
|
}
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten reply", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_delete() {
|
|
let fname = Name::english("field0");
|
|
let mut doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
doc.start(standard_paths());
|
|
doc.populate([1.into()].to_vec());
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(1).unwrap();
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
let mut query = Query::new();
|
|
query.add(&fname, calc);
|
|
let delete = Delete::new(query.clone());
|
|
doc.send(delete).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(data.len(), 1);
|
|
for doc in data.iter() {
|
|
match doc.get(&fname).unwrap() {
|
|
Field::Integer(num) => assert_eq!(num, 1),
|
|
_ => unreachable!("did not get uuid"),
|
|
}
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}: should have gotten reply", action),
|
|
}
|
|
doc.send(query).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => assert_eq!(data.len(), 0),
|
|
_ => unreachable!("got {:?}, should have been empty", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_delete_return_query_errors() {
|
|
let field_name = Name::english("wrong");
|
|
let mut doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
doc.start(standard_paths());
|
|
let mut calc = Calculation::new(Operand::Equal);
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
calc.add_value(1).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(field_name.clone(), calc);
|
|
let delete = Delete::new(query);
|
|
doc.send(delete).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::NameNotFound(data) => assert_eq!(data, &field_name),
|
|
_ => unreachable!("got {:?}: should have gotten an missing field", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have gotten an error", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn does_delete_update_indexes() {
|
|
let fname = Name::english("field0");
|
|
let value = 1;
|
|
let mut doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
doc.get_docdef_mut()
|
|
.add_index(&fname, IndexType::Unique)
|
|
.unwrap();
|
|
doc.start(standard_paths());
|
|
doc.populate([value.into()].to_vec());
|
|
doc.send(Delete::new(Query::new())).unwrap();
|
|
doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let mut addition = Addition::new();
|
|
addition.add_field(&fname, value.clone());
|
|
doc.send(addition).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => assert_eq!(data.len(), 1),
|
|
_ => unreachable!("got {:?}, should have added entry", action),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_query_trigger_reaction() {
|
|
let mut doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
let doc_name = doc.get_docdef().get_document_names()[0].clone();
|
|
let path = Path::new(
|
|
Include::All,
|
|
Include::Just(doc_name.clone().into()),
|
|
Include::Just(Action::OnQuery),
|
|
);
|
|
let mut update = Update::new(Query::new());
|
|
let mut calc = Calculation::new(Operand::Add);
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
calc.add_value(1).unwrap();
|
|
update
|
|
.get_values_mut()
|
|
.add_field(Name::english("field0"), calc);
|
|
let function = DocFuncType::ExistingQuery(update.into());
|
|
doc.get_docdef_mut().add_route(path, function);
|
|
let mut paths = standard_paths();
|
|
paths.push(Path::new(
|
|
Include::All,
|
|
Include::Just(doc_name.into()),
|
|
Include::Just(Action::OnUpdate),
|
|
));
|
|
doc.start(paths);
|
|
doc.populate([0.into()].to_vec());
|
|
for i in 0..5 {
|
|
let expected: Field = i.try_into().unwrap();
|
|
doc.send(Query::new()).unwrap();
|
|
let result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
let action = result.get_action();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(data.len(), 1);
|
|
for rec in data.iter() {
|
|
assert_eq!(rec.get(&Name::english("field0")).unwrap(), expected);
|
|
}
|
|
}
|
|
_ => unreachable!("got {:?}, should have added entry", action),
|
|
}
|
|
let on_update = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
match on_update.get_action() {
|
|
MsgAction::OnUpdate(recs) => {
|
|
let expected: Field = (i + 1).into();
|
|
assert_eq!(recs.len(), 1);
|
|
for rec in recs.iter() {
|
|
assert_eq!(rec.get(Name::english("field0")).unwrap(), expected);
|
|
}
|
|
}
|
|
_ => unreachable!("should only be on update"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn can_an_action_trigger_an_action() {
|
|
let mut doc = TestDocument::new([FieldType::Integer].to_vec());
|
|
let queue = doc.get_queue();
|
|
let doc_name = doc.get_docdef().get_document_names()[0].clone();
|
|
Clock::start(queue.clone());
|
|
let mut calc = Calculation::new(Operand::GreaterThan);
|
|
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
|
.unwrap();
|
|
calc.add_value(1).unwrap();
|
|
let mut query = Query::new();
|
|
query.add(Name::english("field0"), calc);
|
|
let delete = Delete::new(query.clone());
|
|
let path = Path::new(
|
|
Include::All,
|
|
Include::Just(Name::english("clock").into()),
|
|
Include::Just(Action::OnUpdate),
|
|
);
|
|
let function = DocFuncType::Trigger(delete.into());
|
|
doc.get_docdef_mut().add_route(path, function);
|
|
let mut paths = standard_paths();
|
|
paths.push(Path::new(
|
|
Include::All,
|
|
Include::Just(doc_name.clone().into()),
|
|
Include::Just(Action::Delete),
|
|
));
|
|
doc.start(paths);
|
|
let queue = doc.get_queue();
|
|
for item in 1..3 {
|
|
doc.populate([item.into()].to_vec());
|
|
}
|
|
let trigger = Message::new(
|
|
Name::english("clock"),
|
|
MsgAction::OnUpdate(Records::new(Names::new())),
|
|
);
|
|
queue.send(trigger.clone()).unwrap();
|
|
sleep(TIMEOUT);
|
|
let msg = Message::new(doc_name, Query::new());
|
|
queue.send(msg.clone()).unwrap();
|
|
let mut result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
while result.get_message_id() != msg.get_message_id() {
|
|
result = doc.get_receiver().recv_timeout(TIMEOUT).unwrap();
|
|
}
|
|
let action = result.get_action();
|
|
let expected: Field = 1.into();
|
|
match action {
|
|
MsgAction::Records(data) => {
|
|
assert_eq!(data.len(), 1, "should have been one record:\n{:?}", data);
|
|
for rec in data.iter() {
|
|
assert_eq!(rec.get(Name::english("field0")).unwrap(), expected);
|
|
}
|
|
}
|
|
_ => unreachable!("should return records"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod createdocs {
|
|
use super::*;
|
|
use crate::support_tests::TIMEOUT;
|
|
|
|
struct TestCreateDoc {
|
|
queue: Queue,
|
|
rx: Receiver<Message>,
|
|
rx_id: Uuid,
|
|
}
|
|
|
|
impl TestCreateDoc {
|
|
fn new() -> Self {
|
|
let mut queue = Queue::new();
|
|
let (tx, rx) = channel();
|
|
let id = queue.add_sender(tx);
|
|
CreateDoc::start(queue.clone());
|
|
Self {
|
|
queue: queue,
|
|
rx: rx,
|
|
rx_id: id,
|
|
}
|
|
}
|
|
|
|
fn get_queue(&self) -> Queue {
|
|
self.queue.clone()
|
|
}
|
|
|
|
fn get_receiver(&self) -> &Receiver<Message> {
|
|
&self.rx
|
|
}
|
|
|
|
fn get_document_id(&self, name: &Name) -> Uuid {
|
|
let reg_request = Register::new(self.rx_id.clone(), RegMsg::GetNameID(name.clone()));
|
|
self.queue
|
|
.send(Message::new(NameType::None, reg_request))
|
|
.unwrap();
|
|
let info = self.rx.recv_timeout(TIMEOUT).unwrap();
|
|
match info.get_action() {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::DocumentNameID(ident) => ident.clone(),
|
|
_ => unreachable!("should not get here"),
|
|
},
|
|
_ => unreachable!("should not get here"),
|
|
}
|
|
}
|
|
|
|
fn register_paths(&self, paths: Vec<Path>) {
|
|
for path in paths.iter() {
|
|
let regmsg = Register::new(self.rx_id.clone(), RegMsg::AddRoute(path.clone()));
|
|
self.queue
|
|
.send(Message::new(NameType::None, regmsg))
|
|
.unwrap();
|
|
self.rx.recv_timeout(TIMEOUT).unwrap();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn create_document_creation() {
|
|
let doc_creator = TestCreateDoc::new();
|
|
let paths = [
|
|
Path::new(Include::All, Include::All, Include::Just(Action::Reply)),
|
|
Path::new(Include::All, Include::All, Include::Just(Action::Records)),
|
|
]
|
|
.to_vec();
|
|
doc_creator.register_paths(paths);
|
|
let queue = doc_creator.get_queue();
|
|
let rx = doc_creator.get_receiver();
|
|
let name = Name::english("project");
|
|
let msg1 = Message::new(NameType::None, MsgAction::Create(DocDef::new(name.clone())));
|
|
queue.send(msg1.clone()).unwrap();
|
|
let result1 = rx.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(
|
|
result1.get_message_id(),
|
|
msg1.get_message_id(),
|
|
"received {:?} from create message.",
|
|
rx
|
|
);
|
|
match result1.get_action() {
|
|
MsgAction::Reply(_) => {}
|
|
_ => unreachable!("got {:?}: should have been a reply.", result1.get_action()),
|
|
}
|
|
let doc_id: NameType = doc_creator.get_document_id(&name).into();
|
|
let msg2 = Message::new(name, Query::new());
|
|
queue.send(msg2.clone()).unwrap();
|
|
let result2 = rx.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result2.get_message_id(), msg2.get_message_id());
|
|
assert_eq!(result2.get_document_id(), &doc_id);
|
|
match result2.get_action() {
|
|
MsgAction::Records(data) => assert_eq!(data.len(), 0),
|
|
_ => unreachable!("got {:?}: should have been a reply.", result1.get_action()),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn does_duplicates_generate_error() {
|
|
let doc_creator = TestCreateDoc::new();
|
|
let paths = [Path::new(
|
|
Include::All,
|
|
Include::All,
|
|
Include::Just(Action::Error),
|
|
)]
|
|
.to_vec();
|
|
doc_creator.register_paths(paths);
|
|
let queue = doc_creator.get_queue();
|
|
let rx = doc_creator.get_receiver();
|
|
let name = Name::english("duplicate");
|
|
let msg1 = Message::new(NameType::None, MsgAction::Create(DocDef::new(name.clone())));
|
|
let msg2 = Message::new(NameType::None, MsgAction::Create(DocDef::new(name.clone())));
|
|
queue.send(msg1.clone()).unwrap();
|
|
queue.send(msg2.clone()).unwrap();
|
|
let result = rx.recv_timeout(TIMEOUT).unwrap();
|
|
assert_eq!(result.get_message_id(), msg2.get_message_id());
|
|
match result.get_action() {
|
|
MsgAction::Error(err) => match err {
|
|
MTTError::NameDuplicate(data) => assert_eq!(data, &name),
|
|
_ => unreachable!("got {:?}: should have been a duplicate name", err),
|
|
},
|
|
_ => unreachable!("got {:?}: should have been a reply.", result.get_action()),
|
|
}
|
|
/*
|
|
let router = queue.router.read().unwrap();
|
|
assert_eq!(
|
|
router.senders.len(),
|
|
3,
|
|
"there should only be 3 registered senders: createdoc, testing rx, and {:?}",
|
|
name
|
|
);
|
|
*/
|
|
}
|
|
}
|
|
|
|
pub struct Clock {
|
|
queue: Queue,
|
|
}
|
|
|
|
impl Clock {
|
|
fn new(queue: Queue) -> Self {
|
|
Self { queue: queue }
|
|
}
|
|
|
|
pub fn start(mut queue: Queue) {
|
|
let clock = Clock::new(queue.clone());
|
|
let (tx, rx) = channel();
|
|
let id = queue.add_sender(tx);
|
|
let reg_msg = Register::new(id, RegMsg::AddDocName([Name::english("clock")].to_vec()));
|
|
let msg = Message::new(NameType::None, reg_msg.clone());
|
|
queue.send(msg).unwrap();
|
|
rx.recv().unwrap();
|
|
spawn(move || {
|
|
clock.listen();
|
|
});
|
|
}
|
|
|
|
fn listen(&self) {
|
|
loop {
|
|
self.queue
|
|
.send(Message::new(
|
|
Name::english("clock"),
|
|
MsgAction::OnUpdate(Records::new(Names::new())),
|
|
))
|
|
.unwrap();
|
|
sleep(Duration::from_secs(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod clocks {
|
|
use super::*;
|
|
use chrono::TimeDelta;
|
|
|
|
static TIMEOUT: Duration = Duration::from_millis(1500);
|
|
|
|
#[test]
|
|
fn does_clock_send_reply_every_second() {
|
|
let mut queue = Queue::new();
|
|
let (tx, rx) = channel();
|
|
let id = queue.add_sender(tx);
|
|
let request = Register::new(
|
|
id.clone(),
|
|
RegMsg::AddRoute(Path::new(Include::All, Include::All, Include::All)),
|
|
);
|
|
queue.send(Message::new(NameType::None, request)).unwrap();
|
|
rx.recv_timeout(TIMEOUT).unwrap();
|
|
let mut holder: Vec<Message> = Vec::new();
|
|
let start = Utc::now();
|
|
Clock::start(queue.clone());
|
|
while holder.len() < 2 {
|
|
holder.push(rx.recv_timeout(TIMEOUT).unwrap());
|
|
}
|
|
let end = Utc::now();
|
|
assert!((end - start) > TimeDelta::seconds(1));
|
|
assert!((end - start) < TimeDelta::seconds(2));
|
|
let reg_request = Register::new(id, RegMsg::GetNameID(Name::english("clock")));
|
|
queue
|
|
.send(Message::new(NameType::None, reg_request))
|
|
.unwrap();
|
|
let info = rx.recv_timeout(TIMEOUT).unwrap();
|
|
let doc_id = match info.get_action() {
|
|
MsgAction::Register(data) => match data.get_msg() {
|
|
RegMsg::DocumentNameID(ident) => ident.clone(),
|
|
_ => unreachable!("should not get here"),
|
|
},
|
|
_ => unreachable!("should not get here"),
|
|
};
|
|
for msg in holder.iter() {
|
|
let name_id = msg.get_document_id();
|
|
match name_id {
|
|
NameType::ID(data) => assert_eq!(data, &doc_id),
|
|
_ => unreachable!("got {:?}, should have been clock", name_id),
|
|
}
|
|
let action = msg.get_action();
|
|
match action {
|
|
MsgAction::OnUpdate(result) => assert_eq!(result.len(), 0),
|
|
_ => unreachable!("got {:?}, should have been empty record", action),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Clone, Debug)]
|
|
pub struct MsgEntry {
|
|
timestamp: DateTime<Utc>,
|
|
message: Message,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl MsgEntry {
|
|
fn new(msg: Message) -> Self {
|
|
Self {
|
|
timestamp: Utc::now(),
|
|
message: msg,
|
|
}
|
|
}
|
|
|
|
fn get_timestamp(&self) -> &DateTime<Utc> {
|
|
&self.timestamp
|
|
}
|
|
|
|
fn get_message(&self) -> &Message {
|
|
&self.message
|
|
}
|
|
|
|
fn get_message_id(&self) -> &Uuid {
|
|
self.message.get_message_id()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod msg_entries {
|
|
use super::*;
|
|
|
|
#[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());
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Clone, Debug)]
|
|
struct MsgLogs {
|
|
data: HashMap<Uuid, Vec<MsgEntry>>,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl MsgLogs {
|
|
fn new() -> Self {
|
|
Self {
|
|
data: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
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) {
|
|
Some(data) => data,
|
|
None => {
|
|
self.data.insert(id.clone(), Vec::new());
|
|
self.data.get_mut(id).unwrap()
|
|
}
|
|
};
|
|
entries.push(entry);
|
|
}
|
|
|
|
fn get(&self, msg_id: &Uuid) -> Option<&Vec<MsgEntry>> {
|
|
self.data.get(msg_id)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod msg_logs {
|
|
use super::*;
|
|
|
|
#[test]
|
|
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");
|
|
}
|
|
|
|
#[test]
|
|
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 => (),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
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);
|
|
}
|
|
|
|
#[test]
|
|
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());
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
struct MessageLog {
|
|
data: MsgLogs,
|
|
queue: Queue,
|
|
rx: Receiver<Message>,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl MessageLog {
|
|
fn new(queue: Queue, rx: Receiver<Message>) -> Self {
|
|
Self {
|
|
data: MsgLogs::new(),
|
|
queue: queue,
|
|
rx: rx,
|
|
}
|
|
}
|
|
|
|
fn start(mut queue: Queue) {
|
|
let (tx, rx) = channel();
|
|
let mut logs = MessageLog::new(queue.clone(), rx);
|
|
let id = queue.add_sender(tx);
|
|
let reg_msg = Register::new(
|
|
id,
|
|
RegMsg::AddRoute(Path::new(Include::All, Include::All, Include::All)),
|
|
);
|
|
let rmsg = Message::new(NameType::None, reg_msg);
|
|
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 {
|
|
use super::*;
|
|
use crate::support_tests::TIMEOUT;
|
|
|
|
#[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();
|
|
match name_result.get_action() {
|
|
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,
|
|
Include::Just(Action::Log),
|
|
)),
|
|
);
|
|
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()),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Session {
|
|
doc_name: Name,
|
|
}
|
|
|
|
impl Session {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
doc_name: Name::english("session"),
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
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);
|
|
calc.add_value(FieldType::DateTime).unwrap();
|
|
calc.add_value(Duration::from_hours(1)).unwrap();
|
|
|
|
let name_id = Name::english("id");
|
|
docdef.add_field(name_id.clone(), FieldType::Uuid);
|
|
docdef.set_default(&name_id, FieldType::Uuid).unwrap();
|
|
docdef.add_index(&name_id, IndexType::Unique).unwrap();
|
|
|
|
let name_expire = Name::english("expire");
|
|
docdef.add_field(name_expire.clone(), FieldType::DateTime);
|
|
docdef.set_default(&name_expire, calc.clone()).unwrap();
|
|
|
|
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);
|
|
delete_calc.add_value(FieldType::DateTime).unwrap();
|
|
delete_calc
|
|
.add_value(CalcValue::Existing(FieldType::DateTime))
|
|
.unwrap();
|
|
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 {
|
|
use super::*;
|
|
use crate::support_tests::TIMEOUT;
|
|
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());
|
|
Clock::start(queue.clone());
|
|
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();
|
|
let session = Session::new();
|
|
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();
|
|
setup.recv().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn session_ids_are_unique() {
|
|
let setup = Setup::new();
|
|
let queue = setup.get_queue();
|
|
let session = Session::new();
|
|
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();
|
|
let session = Session::new();
|
|
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();
|
|
let session = Session::new();
|
|
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();
|
|
let session = Session::new();
|
|
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);
|
|
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
|
.unwrap();
|
|
calc.add_value(id).unwrap();
|
|
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();
|
|
let session = Session::new();
|
|
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),
|
|
}
|
|
}
|
|
}
|