Compare commits
52 Commits
224096cbdb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f704dec080 | |||
| 96d4508af2 | |||
| 1bc0242d00 | |||
| 900ce1f7e7 | |||
| 807b9ad456 | |||
| 837cea4ce0 | |||
| 678e433632 | |||
| 24edca6415 | |||
| 3a608967ef | |||
| 6a06672697 | |||
| e95f5220b3 | |||
| 524fd4c266 | |||
| 983abfa7c6 | |||
| 0c3a75a16d | |||
| 28618ff019 | |||
| 20e7aa69ff | |||
| 64e9a38f38 | |||
| b3ff154110 | |||
| 83dd91ae17 | |||
| 83821ee89b | |||
| d960d8fd03 | |||
| 316ae06654 | |||
| 02caf62f29 | |||
| 74c3327802 | |||
| 046d71e606 | |||
| 8a8006c521 | |||
| 5ec1330a73 | |||
| 9e81e17a23 | |||
| 92c5ac768b | |||
| 2f078bdf32 | |||
| 7d8de156c7 | |||
| 6014a3bb25 | |||
| caee3bbe6a | |||
| 02939f968f | |||
| e5c14d55cd | |||
| 1684ab3367 | |||
| af0af3373b | |||
| fc2149153e | |||
| ad92e6e6d5 | |||
| d217f511da | |||
| b96bbbe21d | |||
| c7054e4306 | |||
| aca474b42c | |||
| fb91971a1c | |||
| 50962e2b68 | |||
| bb47a7af31 | |||
| 87e737ff6f | |||
| ff9aef6d44 | |||
| 43aa53fc49 | |||
| d41b5ff418 | |||
| f8b5b477ee | |||
| 00d8283fb9 |
@@ -1,6 +1,7 @@
|
||||
mod action_type;
|
||||
mod addition;
|
||||
mod calculation;
|
||||
mod client;
|
||||
mod delete;
|
||||
mod message;
|
||||
mod query;
|
||||
@@ -8,12 +9,12 @@ mod reply;
|
||||
mod request_data;
|
||||
mod show;
|
||||
mod update;
|
||||
mod user;
|
||||
|
||||
pub use crate::document::{DocDef, Field, FieldType, IndexType, Record, Records};
|
||||
pub use crate::document::{DocDef, DocFuncType, Field, FieldType, IndexType, Record, Records};
|
||||
pub use action_type::Action;
|
||||
pub use addition::Addition;
|
||||
pub use calculation::{CalcValue, Calculation, Operand};
|
||||
pub use client::ClientAction;
|
||||
pub use delete::Delete;
|
||||
pub use message::MsgAction;
|
||||
pub use query::Query;
|
||||
@@ -21,4 +22,3 @@ pub use reply::Reply;
|
||||
use request_data::RequestData;
|
||||
pub use show::Show;
|
||||
pub use update::Update;
|
||||
pub use user::UserAction;
|
||||
|
||||
@@ -7,6 +7,7 @@ pub enum Action {
|
||||
Delete,
|
||||
DocumentCreated,
|
||||
Error,
|
||||
None,
|
||||
OnAddition,
|
||||
OnDelete,
|
||||
OnQuery,
|
||||
@@ -27,6 +28,7 @@ impl From<MsgAction> for Action {
|
||||
MsgAction::Delete(_) => Action::Delete,
|
||||
MsgAction::DocumentCreated => Action::DocumentCreated,
|
||||
MsgAction::Error(_) => Action::Error,
|
||||
MsgAction::None => Action::None,
|
||||
MsgAction::OnAddition(_) => Action::OnAddition,
|
||||
MsgAction::OnDelete(_) => Action::OnDelete,
|
||||
MsgAction::OnQuery(_) => Action::OnQuery,
|
||||
|
||||
@@ -2,38 +2,38 @@ use super::{Addition, Delete, DocDef, FieldType, Query, Update};
|
||||
use crate::{message::MessageAction, name::NameType};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum UserAction {
|
||||
pub enum ClientAction {
|
||||
Addition(Addition),
|
||||
Delete(Delete),
|
||||
Query(Query),
|
||||
Update(Update),
|
||||
}
|
||||
|
||||
impl From<Addition> for UserAction {
|
||||
impl From<Addition> for ClientAction {
|
||||
fn from(value: Addition) -> Self {
|
||||
Self::Addition(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Delete> for UserAction {
|
||||
impl From<Delete> for ClientAction {
|
||||
fn from(value: Delete) -> Self {
|
||||
Self::Delete(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Query> for UserAction {
|
||||
impl From<Query> for ClientAction {
|
||||
fn from(value: Query) -> Self {
|
||||
Self::Query(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Update> for UserAction {
|
||||
impl From<Update> for ClientAction {
|
||||
fn from(value: Update) -> Self {
|
||||
Self::Update(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageAction for UserAction {
|
||||
impl MessageAction for ClientAction {
|
||||
fn doc_name(&self) -> &NameType {
|
||||
match self {
|
||||
Self::Addition(data) => data.doc_name(),
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Addition, Delete, DocDef, Query, Records, Reply, Show, Update, UserAction};
|
||||
use super::{Addition, ClientAction, Delete, DocDef, Query, Records, Reply, Show, Update};
|
||||
use crate::{
|
||||
message::MessageAction, mtterror::MTTError, name::NameType, queue::data_director::Register,
|
||||
};
|
||||
@@ -10,6 +10,7 @@ pub enum MsgAction {
|
||||
Delete(Delete),
|
||||
DocumentCreated,
|
||||
Error(MTTError),
|
||||
None,
|
||||
OnAddition(Records),
|
||||
OnDelete(Records),
|
||||
OnQuery(Records),
|
||||
@@ -30,6 +31,7 @@ impl MessageAction for MsgAction {
|
||||
Self::Delete(data) => data.doc_name(),
|
||||
Self::DocumentCreated => &NameType::None,
|
||||
Self::Error(data) => data.doc_name(),
|
||||
Self::None => &NameType::None,
|
||||
Self::OnAddition(data) => data.doc_name(),
|
||||
Self::OnDelete(data) => data.doc_name(),
|
||||
Self::OnQuery(data) => data.doc_name(),
|
||||
@@ -98,13 +100,13 @@ impl From<Update> for MsgAction {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UserAction> for MsgAction {
|
||||
fn from(value: UserAction) -> Self {
|
||||
impl From<ClientAction> for MsgAction {
|
||||
fn from(value: ClientAction) -> Self {
|
||||
match value {
|
||||
UserAction::Addition(data) => Self::Addition(data),
|
||||
UserAction::Delete(data) => Self::Delete(data),
|
||||
UserAction::Query(data) => Self::Query(data),
|
||||
UserAction::Update(data) => Self::Update(data),
|
||||
ClientAction::Addition(data) => Self::Addition(data),
|
||||
ClientAction::Delete(data) => Self::Delete(data),
|
||||
ClientAction::Query(data) => Self::Query(data),
|
||||
ClientAction::Update(data) => Self::Update(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{Query, RequestData};
|
||||
use super::{CalcValue, Query, RequestData};
|
||||
use crate::{message::MessageAction, name::NameType};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -28,6 +28,14 @@ impl Update {
|
||||
&mut self.query
|
||||
}
|
||||
|
||||
pub fn add_field<NT, CV>(&mut self, name: NT, field: CV)
|
||||
where
|
||||
CV: Into<CalcValue>,
|
||||
NT: Into<NameType>,
|
||||
{
|
||||
self.values.add_field(name, field);
|
||||
}
|
||||
|
||||
pub fn get_values(&self) -> &RequestData {
|
||||
&self.values
|
||||
}
|
||||
|
||||
@@ -9,7 +9,12 @@ use record::{InternalRecord, InternalRecords, Oid};
|
||||
|
||||
pub use clock::Clock;
|
||||
pub use create::{CreateDoc, IndexType};
|
||||
pub use definition::DocDef;
|
||||
pub use field::{Field, FieldType};
|
||||
pub use definition::{DocDef, DocFuncType};
|
||||
pub use field::{Field, FieldType, MissingTranslation};
|
||||
pub use record::{Record, Records};
|
||||
pub use session::Session;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
enum DocFeature {
|
||||
System,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
action::{Action, MsgAction, Records},
|
||||
message::wrapper::Message,
|
||||
message::Message,
|
||||
name::{Name, NameType, Names},
|
||||
queue::{
|
||||
data_director::{Include, Path, RegMsg, Register},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
use super::DocFeature;
|
||||
use crate::{
|
||||
action::{Action, CalcValue, Field, FieldType, MsgAction},
|
||||
document::create::IndexType,
|
||||
@@ -215,6 +216,7 @@ impl PathAction {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DocDef {
|
||||
doc_names: Vec<Name>,
|
||||
features: HashSet<DocFeature>,
|
||||
field_names: Names,
|
||||
fields: HashMap<NameID, FieldSetting>,
|
||||
indexes: HashMap<NameID, IndexType>,
|
||||
@@ -227,7 +229,22 @@ impl DocDef {
|
||||
Self::with_names(names)
|
||||
}
|
||||
|
||||
pub fn system(name: Name) -> Self {
|
||||
let names = vec![name];
|
||||
Self::system_with_names(names)
|
||||
}
|
||||
|
||||
pub fn system_with_names(names: Vec<Name>) -> Self {
|
||||
let mut features = HashSet::new();
|
||||
features.insert(DocFeature::System);
|
||||
Self::create(names, features)
|
||||
}
|
||||
|
||||
pub fn with_names(names: Vec<Name>) -> Self {
|
||||
Self::create(names, HashSet::new())
|
||||
}
|
||||
|
||||
fn create(names: Vec<Name>, features: HashSet<DocFeature>) -> Self {
|
||||
let routes = vec![
|
||||
PathAction::new(
|
||||
Path::new(
|
||||
@@ -272,6 +289,7 @@ impl DocDef {
|
||||
];
|
||||
Self {
|
||||
doc_names: names,
|
||||
features: features,
|
||||
field_names: Names::new(),
|
||||
fields: HashMap::new(),
|
||||
indexes: HashMap::new(),
|
||||
@@ -295,12 +313,22 @@ impl DocDef {
|
||||
&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();
|
||||
pub fn has_feature(&self, feature: &DocFeature) -> bool {
|
||||
match self.features.get(feature) {
|
||||
Some(_) => true,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_field(&mut self, names: Vec<Name>, ftype: FieldType) {
|
||||
let id = self.field_names.add_names(names).unwrap();
|
||||
self.fields.insert(id, FieldSetting::new(ftype));
|
||||
}
|
||||
|
||||
pub fn get_field_type<NT>(&self, field_name: NT) -> Result<&FieldType, MTTError> where NT: Into<NameType> {
|
||||
pub fn get_field_type<NT>(&self, field_name: NT) -> Result<&FieldType, MTTError>
|
||||
where
|
||||
NT: Into<NameType>,
|
||||
{
|
||||
let id = match self.field_names.get_id(field_name) {
|
||||
Ok(data) => data,
|
||||
Err(err) => return Err(err),
|
||||
@@ -403,10 +431,22 @@ mod docdefs {
|
||||
use crate::action::{Query, Update};
|
||||
|
||||
#[test]
|
||||
fn message_doc_name_returns_none() {
|
||||
fn can_create_document_definition() {
|
||||
let docname = Name::english("tester");
|
||||
let mut docdef = DocDef::new(docname);
|
||||
assert_eq!(docdef.doc_name(), &NameType::None);
|
||||
assert!(
|
||||
docdef.features.is_empty(),
|
||||
"should not have special features"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_create_system_document_definition() {
|
||||
let docname = Name::english("system");
|
||||
let mut docdef = DocDef::system(docname);
|
||||
assert_eq!(docdef.features.len(), 1);
|
||||
assert_eq!(docdef.features.iter().last().unwrap(), &DocFeature::System);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -415,7 +455,7 @@ mod docdefs {
|
||||
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());
|
||||
docdef.add_field(vec![name.clone()], field_type.clone());
|
||||
let result = docdef.get_field(name).unwrap();
|
||||
match result.validate(&Uuid::new_v4().into()) {
|
||||
Ok(_) => {}
|
||||
@@ -431,8 +471,8 @@ mod docdefs {
|
||||
let field2 = Name::english("field2");
|
||||
let ftype1 = FieldType::Uuid;
|
||||
let ftype2 = FieldType::StaticString;
|
||||
docdef.add_field(field1.clone(), ftype1.clone());
|
||||
docdef.add_field(field2.clone(), ftype2.clone());
|
||||
docdef.add_field(vec![field1.clone()], ftype1.clone());
|
||||
docdef.add_field(vec![field2.clone()], ftype2.clone());
|
||||
assert_eq!(docdef.get_field_type(&field1).unwrap(), &ftype1);
|
||||
assert_eq!(docdef.get_field_type(&field2).unwrap(), &ftype2);
|
||||
}
|
||||
@@ -458,7 +498,7 @@ mod docdefs {
|
||||
let names = ["one", "two", "three"];
|
||||
let field_type = FieldType::StaticString;
|
||||
for name in names.iter() {
|
||||
docdef.add_field(Name::english(name), field_type.clone());
|
||||
docdef.add_field(vec![Name::english(name)], field_type.clone());
|
||||
}
|
||||
for name in names.iter() {
|
||||
let result = docdef.get_field(Name::english(name)).unwrap();
|
||||
@@ -474,7 +514,7 @@ mod docdefs {
|
||||
let docname = Name::english("something");
|
||||
let mut docdef = DocDef::new(docname);
|
||||
let name = Name::english("defaultfunction");
|
||||
docdef.add_field(name.clone(), FieldType::StaticString);
|
||||
docdef.add_field(vec![name.clone()], FieldType::StaticString);
|
||||
docdef.set_default(&name, FieldType::StaticString).unwrap();
|
||||
match docdef.get_field(name).unwrap().validate(&Field::None) {
|
||||
Ok(data) => match data {
|
||||
@@ -504,7 +544,7 @@ mod docdefs {
|
||||
let docname = Name::english("something");
|
||||
let mut docdef = DocDef::new(docname);
|
||||
let name = Name::english("defaultvalue");
|
||||
docdef.add_field(name.clone(), FieldType::Uuid);
|
||||
docdef.add_field(vec![name.clone()], FieldType::Uuid);
|
||||
match docdef.set_default(&name, "fred") {
|
||||
Ok(data) => unreachable!("got {:?}, should be an error", data),
|
||||
Err(err) => match err.get_error_ids().back().unwrap() {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use crate::{ErrorID, MTTError};
|
||||
use chrono::prelude::*;
|
||||
use isolang::Language;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
ops::{Add, AddAssign},
|
||||
str::Split,
|
||||
time::Duration,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
@@ -12,8 +16,8 @@ pub enum Field {
|
||||
DateTime(DateTime<Utc>),
|
||||
Duration(Duration),
|
||||
Integer(i128),
|
||||
Language(Language),
|
||||
None,
|
||||
Revision(Revision),
|
||||
StaticString(String),
|
||||
Uuid(Uuid),
|
||||
}
|
||||
@@ -70,12 +74,6 @@ impl From<Duration> for Field {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Revision> for Field {
|
||||
fn from(value: Revision) -> Self {
|
||||
Self::Revision(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Field {
|
||||
fn from(value: String) -> Self {
|
||||
Self::StaticString(value)
|
||||
@@ -114,6 +112,12 @@ impl From<i32> for Field {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Language> for Field {
|
||||
fn from(value: Language) -> Self {
|
||||
Self::Language(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Field {
|
||||
fn partial_cmp(&self, other: &Field) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
@@ -121,7 +125,6 @@ impl PartialOrd for Field {
|
||||
(Self::DateTime(d1), Self::DateTime(d2)) => d1.partial_cmp(d2),
|
||||
(Self::Duration(d1), Self::Duration(d2)) => d1.partial_cmp(d2),
|
||||
(Self::Integer(d1), Self::Integer(d2)) => d1.partial_cmp(d2),
|
||||
(Self::Revision(d1), Self::Revision(d2)) => d1.partial_cmp(d2),
|
||||
(Self::StaticString(d1), Self::StaticString(d2)) => d1.partial_cmp(d2),
|
||||
(Self::Uuid(d1), Self::Uuid(d2)) => d1.partial_cmp(d2),
|
||||
(_, _) => None,
|
||||
@@ -276,8 +279,8 @@ pub enum FieldType {
|
||||
DateTime,
|
||||
Duration,
|
||||
Integer,
|
||||
Language,
|
||||
None,
|
||||
Revision,
|
||||
StaticString,
|
||||
Uuid,
|
||||
}
|
||||
@@ -289,8 +292,8 @@ impl FieldType {
|
||||
FieldType::DateTime => Utc::now().into(),
|
||||
FieldType::Duration => Duration::from_secs(0).into(),
|
||||
FieldType::Integer => 0.into(),
|
||||
FieldType::Language => Language::from_639_1("en").unwrap().into(),
|
||||
FieldType::None => Field::None,
|
||||
FieldType::Revision => Revision::new().into(),
|
||||
FieldType::StaticString => "".into(),
|
||||
FieldType::Uuid => Uuid::new_v4().into(),
|
||||
}
|
||||
@@ -304,8 +307,8 @@ impl From<&Field> for FieldType {
|
||||
Field::DateTime(_) => Self::DateTime,
|
||||
Field::Duration(_) => Self::Duration,
|
||||
Field::Integer(_) => Self::Integer,
|
||||
Field::Language(_) => Self::Language,
|
||||
Field::None => Self::None,
|
||||
Field::Revision(_) => Self::Revision,
|
||||
Field::StaticString(_) => Self::StaticString,
|
||||
Field::Uuid(_) => Self::Uuid,
|
||||
}
|
||||
@@ -348,26 +351,636 @@ mod fieldtypes {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
struct Revision;
|
||||
#[derive(Clone, Debug)]
|
||||
enum DataOrigin {
|
||||
Human,
|
||||
Computer,
|
||||
}
|
||||
|
||||
impl Revision {
|
||||
fn new() -> Self {
|
||||
Self {}
|
||||
#[derive(Clone, Debug)]
|
||||
struct ParagraphData {
|
||||
string: String,
|
||||
origin: DataOrigin,
|
||||
initial: bool,
|
||||
}
|
||||
|
||||
impl ParagraphData {
|
||||
fn initial(string: String) -> Self {
|
||||
Self {
|
||||
string: string,
|
||||
origin: DataOrigin::Human,
|
||||
initial: true,
|
||||
}
|
||||
}
|
||||
|
||||
fn revision(&self) -> isize {
|
||||
0
|
||||
fn new(string: String, origin: DataOrigin) -> Self {
|
||||
Self {
|
||||
string: string,
|
||||
origin: origin,
|
||||
initial: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_text(&self) -> &String {
|
||||
&self.string
|
||||
}
|
||||
|
||||
fn by_human(&self) -> bool {
|
||||
match self.origin {
|
||||
DataOrigin::Human => true,
|
||||
DataOrigin::Computer => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_initial(&self) -> bool {
|
||||
self.initial
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod revisions {
|
||||
mod paragraph_data {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_create_empty_revision() {
|
||||
let rev = Revision::new();
|
||||
assert_eq!(rev.revision(), 0);
|
||||
fn can_determine_initial_information() {
|
||||
let text = Uuid::new_v4().to_string();
|
||||
let data = ParagraphData::initial(text.clone());
|
||||
assert_eq!(data.get_text(), &text);
|
||||
assert!(data.by_human(), "{:?} should have returned true", data);
|
||||
assert!(data.is_initial(), "{:?} should have returned true", data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_made_by_humans() {
|
||||
let text = Uuid::new_v4().to_string();
|
||||
let data = ParagraphData::new(text.clone(), DataOrigin::Human);
|
||||
assert_eq!(data.get_text(), &text);
|
||||
assert!(data.by_human(), "{:?} should have returned true", data);
|
||||
assert!(!data.is_initial(), "{:?} should have returned false", data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_be_made_by_computers() {
|
||||
let text = Uuid::new_v4().to_string();
|
||||
let data = ParagraphData::new(text.clone(), DataOrigin::Computer);
|
||||
assert_eq!(data.get_text(), &text);
|
||||
assert!(!data.by_human(), "{:?} should have returned false", data);
|
||||
assert!(!data.is_initial(), "{:?} should have returned false", data);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Paragraph {
|
||||
data: HashMap<Language, ParagraphData>,
|
||||
}
|
||||
|
||||
impl Paragraph {
|
||||
fn new(lang: Language, string: String) -> Self {
|
||||
let mut data = HashMap::new();
|
||||
data.insert(lang, ParagraphData::initial(string));
|
||||
Self { data: data }
|
||||
}
|
||||
|
||||
fn add_translation(&mut self, lang: Language, text: String) {
|
||||
match self.data.get(&lang) {
|
||||
Some(_) => {}
|
||||
None => {
|
||||
self.data
|
||||
.insert(lang, ParagraphData::new(text, DataOrigin::Computer));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn improve_translation(&mut self, lang: Language, text: String) {
|
||||
self.data
|
||||
.insert(lang, ParagraphData::new(text, DataOrigin::Human));
|
||||
}
|
||||
|
||||
fn get(&self, lang: &Language) -> Option<&String> {
|
||||
match self.data.get(lang) {
|
||||
Some(data) => Some(data.get_text()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_initial(&self) -> (&Language, &String) {
|
||||
for (lang, data) in self.data.iter() {
|
||||
if data.is_initial() {
|
||||
return (lang, data.get_text());
|
||||
}
|
||||
}
|
||||
unreachable!("paragraph should initialize with data");
|
||||
}
|
||||
|
||||
fn by_humans(&self) -> HashMap<Language, String> {
|
||||
let mut output = HashMap::new();
|
||||
for (lang, data) in self.data.iter() {
|
||||
if data.by_human() {
|
||||
output.insert(lang.clone(), data.get_text().clone());
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod paragraphs {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn does_paragraph_store_language_information() {
|
||||
let languages = [
|
||||
Language::from_639_1("en").unwrap(),
|
||||
Language::from_639_1("ja").unwrap(),
|
||||
];
|
||||
let data = Uuid::new_v4().to_string();
|
||||
for lang in languages.iter() {
|
||||
let result = Paragraph::new(lang.clone(), data.clone());
|
||||
assert_eq!(result.get(lang).unwrap(), &data);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn are_multiple_languages_stored() {
|
||||
let text = ["test", "テスト"];
|
||||
let languages = [
|
||||
Language::from_639_1("en").unwrap(),
|
||||
Language::from_639_1("ja").unwrap(),
|
||||
];
|
||||
let mut paragraph = Paragraph::new(languages[0].clone(), text[0].clone().to_string());
|
||||
paragraph.add_translation(languages[1].clone(), text[1].clone().to_string());
|
||||
for i in 0..text.len() {
|
||||
assert_eq!(paragraph.get(&languages[i]).unwrap(), text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_add_translation_get_ignored_if_it_already_exists() {
|
||||
let text = "something";
|
||||
let lang = Language::from_639_1("en").unwrap();
|
||||
let mut paragraph = Paragraph::new(lang.clone(), text.to_string());
|
||||
paragraph.add_translation(lang, "other".to_string());
|
||||
assert_eq!(paragraph.get(&lang).unwrap(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_improve_translation_replace_existing() {
|
||||
let text = "new";
|
||||
let lang = Language::from_639_1("en").unwrap();
|
||||
let mut paragraph = Paragraph::new(lang.clone(), "old".to_string());
|
||||
paragraph.improve_translation(lang.clone(), text.to_string());
|
||||
assert_eq!(paragraph.get(&lang).unwrap(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_determine_human_text() {
|
||||
let text = "something";
|
||||
let lang = Language::from_639_1("en").unwrap();
|
||||
let paragraph = Paragraph::new(lang.clone(), text.to_string());
|
||||
let result = paragraph.by_humans();
|
||||
assert_eq!(result.len(), 1, "got wrong numnber of texts");
|
||||
assert_eq!(result.get(&lang).unwrap(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_translation_does_not_count_as_human_text() {
|
||||
let text = "test";
|
||||
let lang = Language::from_639_1("en").unwrap();
|
||||
let mut paragraph = Paragraph::new(lang.clone(), text.to_string());
|
||||
paragraph.add_translation(Language::from_639_1("ja").unwrap(), "テスト".to_string());
|
||||
let result = paragraph.by_humans();
|
||||
assert_eq!(result.len(), 1, "got wrong numnber of texts");
|
||||
assert_eq!(result.get(&lang).unwrap(), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impove_translation_does_get_added_as_human() {
|
||||
let text = ["test", "テスト"];
|
||||
let languages = [
|
||||
Language::from_639_1("en").unwrap(),
|
||||
Language::from_639_1("ja").unwrap(),
|
||||
];
|
||||
let mut paragraph = Paragraph::new(languages[0].clone(), text[0].clone().to_string());
|
||||
paragraph.improve_translation(languages[1].clone(), text[1].clone().to_string());
|
||||
let result = paragraph.by_humans();
|
||||
assert_eq!(result.len(), 2, "got wrong numnber of texts");
|
||||
for i in 0..text.len() {
|
||||
assert_eq!(result.get(&languages[i]).unwrap(), text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_get_original_text() {
|
||||
let text = Uuid::nil().to_string();
|
||||
let lang = Language::from_639_1("en").unwrap();
|
||||
let mut paragraph = Paragraph::new(lang.clone(), text.clone());
|
||||
paragraph.add_translation(
|
||||
Language::from_639_1("ja").unwrap(),
|
||||
Uuid::new_v4().to_string(),
|
||||
);
|
||||
paragraph.improve_translation(
|
||||
Language::from_639_1("de").unwrap(),
|
||||
Uuid::new_v4().to_string(),
|
||||
);
|
||||
let (rlang, rtext) = paragraph.get_initial();
|
||||
assert_eq!(rlang, &lang);
|
||||
assert_eq!(rtext, &text);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
struct ParagraphID {
|
||||
data: Uuid,
|
||||
}
|
||||
|
||||
impl ParagraphID {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
|
||||
fn nil() -> Self {
|
||||
Self { data: Uuid::nil() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod paragraphids {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn are_paragraph_ids_unique() {
|
||||
let id1 = ParagraphID::new();
|
||||
let id2 = ParagraphID::new();
|
||||
assert_ne!(id1, id2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_create_nil() {
|
||||
let id = ParagraphID::nil();
|
||||
assert_eq!(id.data, Uuid::nil());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MissingTranslation {
|
||||
id: ParagraphID,
|
||||
needs: Language,
|
||||
has: Language,
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl MissingTranslation {
|
||||
fn new(id: ParagraphID, needs: Language, has: Language, text: String) -> Self {
|
||||
Self {
|
||||
id: id,
|
||||
needs: needs,
|
||||
has: has,
|
||||
text: text,
|
||||
}
|
||||
}
|
||||
|
||||
fn paragraph_id(&self) -> ParagraphID {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn needs(&self) -> Language {
|
||||
self.needs.clone()
|
||||
}
|
||||
|
||||
fn has(&self) -> Language {
|
||||
self.has.clone()
|
||||
}
|
||||
|
||||
fn text(&self) -> String {
|
||||
self.text.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod missing_translations {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn can_get_mising_translation_information() {
|
||||
let id = ParagraphID::new();
|
||||
let langs = [
|
||||
Language::from_639_1("en").unwrap(),
|
||||
Language::from_639_1("ja").unwrap(),
|
||||
];
|
||||
let text = Uuid::new_v4().to_string();
|
||||
let missing =
|
||||
MissingTranslation::new(id.clone(), langs[0].clone(), langs[1].clone(), text.clone());
|
||||
assert_eq!(missing.paragraph_id(), id);
|
||||
assert_eq!(missing.needs(), langs[0]);
|
||||
assert_eq!(missing.has(), langs[1]);
|
||||
assert_eq!(missing.text(), text);
|
||||
let missing2 =
|
||||
MissingTranslation::new(id.clone(), langs[1].clone(), langs[0].clone(), text.clone());
|
||||
assert_eq!(missing.paragraph_id(), id);
|
||||
assert_eq!(missing2.needs(), langs[1]);
|
||||
assert_eq!(missing2.has(), langs[0]);
|
||||
assert_eq!(missing2.text(), text);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct UniversalString {
|
||||
paragraphs: HashMap<ParagraphID, Paragraph>,
|
||||
revisions: Vec<Vec<ParagraphID>>,
|
||||
}
|
||||
|
||||
impl UniversalString {
|
||||
fn new(lang: Language, text: String) -> Self {
|
||||
let mut output = Self {
|
||||
paragraphs: HashMap::new(),
|
||||
revisions: Vec::new(),
|
||||
};
|
||||
output.update(lang, text);
|
||||
output
|
||||
}
|
||||
|
||||
fn get(&self, lang: &Language) -> Result<String, MTTError> {
|
||||
let latest = self.revisions.len() - 1;
|
||||
self.get_revision(latest, lang)
|
||||
}
|
||||
|
||||
fn get_revision(&self, rev_num: usize, lang: &Language) -> Result<String, MTTError> {
|
||||
let mut output = "".to_string();
|
||||
let mut missing: Vec<MissingTranslation> = Vec::new();
|
||||
for id in self.revisions[rev_num].iter() {
|
||||
let paragraph = self.paragraphs.get(id).unwrap();
|
||||
let text = match paragraph.get(lang) {
|
||||
Some(data) => data,
|
||||
None => {
|
||||
let (ori_lang, text) = paragraph.get_initial();
|
||||
missing.push(MissingTranslation::new(
|
||||
id.clone(),
|
||||
lang.clone(),
|
||||
ori_lang.clone(),
|
||||
text.clone(),
|
||||
));
|
||||
""
|
||||
}
|
||||
};
|
||||
output += text;
|
||||
output += "\u{2029}";
|
||||
}
|
||||
if missing.is_empty() {
|
||||
Ok(output)
|
||||
} else {
|
||||
Err(MTTError::new(ErrorID::MissingTranslation(missing)))
|
||||
}
|
||||
}
|
||||
|
||||
fn revision_count(&self) -> usize {
|
||||
self.revisions.len() - 1
|
||||
}
|
||||
|
||||
fn update(&mut self, lang: Language, text: String) {
|
||||
let mut version = Vec::new();
|
||||
for paragraph in text.as_str().split("\u{2029}") {
|
||||
if paragraph != "" {
|
||||
let mut id = ParagraphID::nil();
|
||||
for (key, value) in self.paragraphs.iter() {
|
||||
if ¶graph == value.get(&lang).unwrap() {
|
||||
id = key.clone();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if id == ParagraphID::nil() {
|
||||
id = ParagraphID::new();
|
||||
while self.paragraphs.contains_key(&id) {
|
||||
id = ParagraphID::new();
|
||||
}
|
||||
self.paragraphs.insert(
|
||||
id.clone(),
|
||||
Paragraph::new(lang.clone(), paragraph.to_string()),
|
||||
);
|
||||
}
|
||||
version.push(id);
|
||||
}
|
||||
}
|
||||
self.revisions.push(version);
|
||||
}
|
||||
|
||||
fn add_translation(&mut self, id: ParagraphID, lang: Language, text: String) {
|
||||
self.paragraphs
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
.add_translation(lang, text);
|
||||
}
|
||||
|
||||
fn improve_translation(&mut self, id: ParagraphID, lang: Language, text: String) {
|
||||
self.paragraphs
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
.improve_translation(lang, text);
|
||||
}
|
||||
|
||||
fn by_humans(&self) -> Vec<(ParagraphID, HashMap<Language, String>)> {
|
||||
let mut output = Vec::new();
|
||||
let latest = self.revisions.iter().last().unwrap();
|
||||
for id in latest.iter() {
|
||||
let para = self.paragraphs.get(id).unwrap();
|
||||
output.push((id.clone(), para.by_humans()));
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod universal_strings {
|
||||
use super::*;
|
||||
use rand::random_range;
|
||||
use std::collections::HashSet;
|
||||
|
||||
const ENGLISH_DATA: [&str; 5] = ["one", "two", "three", "four", "five"];
|
||||
const JAPANESE_DATA: [&str; 5] = ["一", "二", "三", "四", "五"];
|
||||
|
||||
struct TestData;
|
||||
|
||||
impl TestData {
|
||||
fn english() -> (Language, Vec<String>) {
|
||||
let lang = Language::from_639_1("en").unwrap();
|
||||
let mut data = Vec::new();
|
||||
for text in ENGLISH_DATA.iter() {
|
||||
data.push(text.to_string());
|
||||
}
|
||||
(lang, data)
|
||||
}
|
||||
|
||||
fn japanese() -> (Language, Vec<String>) {
|
||||
let lang = Language::from_639_1("ja").unwrap();
|
||||
let mut data = Vec::new();
|
||||
for text in JAPANESE_DATA.iter() {
|
||||
data.push(text.to_string());
|
||||
}
|
||||
(lang, data)
|
||||
}
|
||||
|
||||
fn to_input(data: Vec<String>) -> String {
|
||||
let mut output = "".to_string();
|
||||
for paragraph in data.iter() {
|
||||
output += paragraph;
|
||||
output += "\u{2029}";
|
||||
}
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn are_initial_strings_empty() {
|
||||
let text = ["test", "テスト"];
|
||||
let languages = [
|
||||
Language::from_639_1("en").unwrap(),
|
||||
Language::from_639_1("ja").unwrap(),
|
||||
];
|
||||
for i in 0..text.len() {
|
||||
let ustr = UniversalString::new(languages[i].clone(), text[i].to_string());
|
||||
let expected = text[i].to_string() + "\u{2029}";
|
||||
assert_eq!(ustr.get(&languages[i]).unwrap(), expected);
|
||||
assert_eq!(ustr.revision_count(), 0);
|
||||
assert_eq!(ustr.paragraphs.len(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_strings_with_multiple_paragraphs() {
|
||||
let (lang, data) = TestData::english();
|
||||
let input = TestData::to_input(data.clone());
|
||||
let ustr = UniversalString::new(lang.clone(), input.clone());
|
||||
assert_eq!(ustr.get(&lang).unwrap(), input);
|
||||
assert_eq!(ustr.revision_count(), 0);
|
||||
assert_eq!(ustr.paragraphs.len(), data.len(), "{:?}", ustr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_insert_text_into_string() {
|
||||
let (lang, mut data) = TestData::english();
|
||||
let initial = TestData::to_input(data.clone());
|
||||
let mut ustr = UniversalString::new(lang.clone(), initial.clone());
|
||||
let position = random_range(..data.len());
|
||||
data.insert(position, "something".to_string());
|
||||
let expected = TestData::to_input(data.clone());
|
||||
ustr.update(lang.clone(), expected.clone());
|
||||
assert_eq!(ustr.get(&lang).unwrap(), expected);
|
||||
assert_eq!(ustr.revision_count(), 1);
|
||||
assert_eq!(ustr.paragraphs.len(), data.len(), "{:?}", ustr);
|
||||
assert_eq!(ustr.get_revision(0, &lang).unwrap(), initial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_a_paragraph_be_replaced() {
|
||||
let (lang, mut data) = TestData::english();
|
||||
let initial = TestData::to_input(data.clone());
|
||||
let mut ustr = UniversalString::new(lang.clone(), initial.clone());
|
||||
let position = random_range(..data.len());
|
||||
data[position] = "replaced".to_string();
|
||||
let expected = TestData::to_input(data.clone());
|
||||
ustr.update(lang.clone(), expected.clone());
|
||||
assert_eq!(ustr.get(&lang).unwrap(), expected);
|
||||
assert_eq!(ustr.revision_count(), 1);
|
||||
assert_eq!(ustr.paragraphs.len(), (data.len() + 1), "{:?}", ustr);
|
||||
assert_eq!(ustr.get_revision(0, &lang).unwrap(), initial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_store_duplicate_data() {
|
||||
let lang = Language::from_639_1("en").unwrap();
|
||||
let mut data = Vec::new();
|
||||
for _ in 0..3 {
|
||||
data.push("same".to_string());
|
||||
}
|
||||
let initial = TestData::to_input(data.clone());
|
||||
let mut ustr = UniversalString::new(lang.clone(), initial.clone());
|
||||
assert_eq!(ustr.get(&lang).unwrap(), initial);
|
||||
assert_eq!(ustr.paragraphs.len(), 1, "{:?}", ustr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_text_be_removed() {
|
||||
let (lang, mut data) = TestData::english();
|
||||
let expected_paragraphs = data.len();
|
||||
let initial = TestData::to_input(data.clone());
|
||||
let mut ustr = UniversalString::new(lang.clone(), initial.clone());
|
||||
let position = random_range(..data.len());
|
||||
data.remove(position);
|
||||
let expected = TestData::to_input(data.clone());
|
||||
ustr.update(lang.clone(), expected.clone());
|
||||
assert_eq!(ustr.get(&lang).unwrap(), expected);
|
||||
assert_eq!(ustr.revision_count(), 1);
|
||||
assert_eq!(ustr.paragraphs.len(), expected_paragraphs, "{:?}", ustr);
|
||||
assert_eq!(ustr.get_revision(0, &lang).unwrap(), initial);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_translation_be_added() {
|
||||
let (elang, edata) = TestData::english();
|
||||
let (jlang, jdata) = TestData::japanese();
|
||||
let initial = TestData::to_input(jdata.clone());
|
||||
let expected = TestData::to_input(edata.clone());
|
||||
let mut ustr = UniversalString::new(jlang.clone(), initial.clone());
|
||||
assert_eq!(ustr.get(&jlang).unwrap(), initial);
|
||||
let err = ustr.get(&elang).unwrap_err();
|
||||
match err.get_error_ids().iter().last().unwrap() {
|
||||
ErrorID::MissingTranslation(missing) => {
|
||||
assert_eq!(
|
||||
missing.len(),
|
||||
jdata.len(),
|
||||
"should return list of translations needed"
|
||||
);
|
||||
let mut holder: HashSet<String> = jdata.iter().cloned().collect();
|
||||
for data in missing.iter() {
|
||||
assert_eq!(data.needs(), elang, "needed language is incorrect");
|
||||
assert_eq!(data.has(), jlang, "original language is incorrect");
|
||||
assert!(holder.contains(&data.text()));
|
||||
let text = data.text();
|
||||
holder.remove(&text);
|
||||
let index = jdata.iter().position(|x| x == &text).unwrap();
|
||||
ustr.add_translation(data.paragraph_id(), elang.clone(), edata[index].clone());
|
||||
}
|
||||
assert!(holder.is_empty(), "still had {:?}", holder);
|
||||
}
|
||||
_ => unreachable!("got {:?}, should have been needs translation", err),
|
||||
}
|
||||
assert_eq!(ustr.revision_count(), 0);
|
||||
assert_eq!(ustr.get(&elang).unwrap(), expected);
|
||||
let result = ustr.by_humans();
|
||||
assert_eq!(
|
||||
result.len(),
|
||||
jdata.len(),
|
||||
"incorrect number of original values"
|
||||
);
|
||||
let mut count = 0;
|
||||
for (_, lang_result) in result {
|
||||
assert_eq!(lang_result.len(), 1, "wrong number of returned languages");
|
||||
assert_eq!(lang_result.get(&jlang).unwrap(), &jdata[count]);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_translations_be_improved() {
|
||||
let (elang, edata) = TestData::english();
|
||||
let (jlang, jdata) = TestData::japanese();
|
||||
let initial = TestData::to_input(edata.clone());
|
||||
let expected = TestData::to_input(jdata.clone());
|
||||
let mut ustr = UniversalString::new(elang.clone(), initial.clone());
|
||||
for (id, lang_text) in ustr.by_humans().iter() {
|
||||
let text = lang_text.get(&elang).unwrap();
|
||||
let index = edata.iter().position(|x| x == text).unwrap();
|
||||
ustr.improve_translation(id.clone(), jlang, jdata[index].clone());
|
||||
}
|
||||
assert_eq!(ustr.revision_count(), 0);
|
||||
assert_eq!(ustr.get(&jlang).unwrap(), expected);
|
||||
let mut count = 0;
|
||||
for (_, lang_result) in ustr.by_humans().iter() {
|
||||
assert_eq!(lang_result.len(), 2, "wrong number of returned languages");
|
||||
assert_eq!(lang_result.get(&elang).unwrap(), &edata[count]);
|
||||
assert_eq!(lang_result.get(&jlang).unwrap(), &jdata[count]);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
create::IndexType,
|
||||
definition::{DocDef, DocFuncType},
|
||||
},
|
||||
message::wrapper::Message,
|
||||
message::{Message, MessageID},
|
||||
name::{Name, NameType},
|
||||
queue::{
|
||||
data_director::{Include, Path, RegMsg, Register},
|
||||
@@ -20,30 +20,75 @@ impl Session {
|
||||
pub fn doc_names() -> Vec<Name> {
|
||||
let mut names = Vec::new();
|
||||
names.push(Name::english("session"));
|
||||
names.push(Name::japanese("セッション"));
|
||||
names
|
||||
}
|
||||
|
||||
pub fn id_field_names() -> Vec<Name> {
|
||||
let mut names = Vec::new();
|
||||
names.push(Name::english("id"));
|
||||
names.push(Name::japanese("身元"));
|
||||
names
|
||||
}
|
||||
|
||||
pub fn expire_field_names() -> Vec<Name> {
|
||||
let mut names = Vec::new();
|
||||
names.push(Name::english("expire"));
|
||||
names.push(Name::japanese("期限切れ"));
|
||||
names
|
||||
}
|
||||
|
||||
pub fn language_field_names() -> Vec<Name> {
|
||||
let mut names = Vec::new();
|
||||
names.push(Name::english("language"));
|
||||
names.push(Name::japanese("言語"));
|
||||
names
|
||||
}
|
||||
|
||||
pub fn start(mut queue: Queue) {
|
||||
let (tx, rx) = channel();
|
||||
let msg_id = MessageID::new();
|
||||
let sender_id = queue.add_sender(tx);
|
||||
let path = Path::new(
|
||||
Include::Just(msg_id.clone()),
|
||||
Include::All,
|
||||
Include::Just(Action::DocumentCreated),
|
||||
);
|
||||
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path.clone()));
|
||||
let msg = Message::new(reg_msg).set_id(msg_id);
|
||||
queue.send(msg.clone());
|
||||
rx.recv().unwrap(); // Wait for completion.
|
||||
queue.send(msg.set_action(Self::document_definition()));
|
||||
rx.recv().unwrap(); // Wait for completion.
|
||||
queue.remove_sender(&sender_id);
|
||||
}
|
||||
|
||||
pub fn document_definition() -> DocDef {
|
||||
let mut docdef = DocDef::with_names(Self::doc_names());
|
||||
let name_id = Self::id_field_names()[0].clone();
|
||||
let name_expire = Self::expire_field_names()[0].clone();
|
||||
let name_lang = Self::language_field_names()[0].clone();
|
||||
|
||||
let mut docdef = DocDef::system_with_names(Self::doc_names());
|
||||
|
||||
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.add_field(Self::id_field_names(), 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.add_field(Self::expire_field_names(), FieldType::DateTime);
|
||||
docdef.set_default(&name_expire, calc.clone()).unwrap();
|
||||
docdef.add_index(&name_expire, IndexType::Index).unwrap();
|
||||
|
||||
docdef.add_field(Self::language_field_names(), FieldType::Language);
|
||||
docdef.set_default(&name_lang, FieldType::Language).unwrap();
|
||||
|
||||
let mut update = Update::new(Session::doc_names()[0].clone());
|
||||
update
|
||||
.get_values_mut()
|
||||
.add_field(name_expire.clone(), calc.clone());
|
||||
.add_field(Self::expire_field_names()[0].clone(), calc.clone());
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(Session::doc_names()[0].clone().into()),
|
||||
@@ -55,11 +100,11 @@ impl Session {
|
||||
let mut delete = Delete::new(Session::doc_names()[0].clone());
|
||||
let delete_qry = delete.get_query_mut();
|
||||
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);
|
||||
delete_calc.add_value(FieldType::DateTime).unwrap();
|
||||
delete_qry.add(Self::expire_field_names()[0].clone(), delete_calc);
|
||||
let delete_func = DocFuncType::Trigger(delete.into());
|
||||
docdef.add_route(Clock::get_path(), delete_func);
|
||||
|
||||
|
||||
323
src/lib.rs
323
src/lib.rs
@@ -5,19 +5,22 @@ mod mtterror;
|
||||
pub mod name;
|
||||
mod queue;
|
||||
|
||||
pub use action::*;
|
||||
use document::{Clock, CreateDoc, Session};
|
||||
use message::{wrapper::Message, MessageAction};
|
||||
use isolang::Language;
|
||||
use message::{Message, MessageAction, MessageID};
|
||||
use queue::{
|
||||
data_director::{RegMsg, Register},
|
||||
router::Queue,
|
||||
SenderID,
|
||||
};
|
||||
use std::{
|
||||
sync::mpsc::{channel, Receiver, Sender},
|
||||
sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender},
|
||||
time::Duration,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub use action::*;
|
||||
pub use document::MissingTranslation;
|
||||
pub use mtterror::{ErrorID, MTTError};
|
||||
pub use name::{Name, NameType};
|
||||
pub use queue::data_director::{Include, Path};
|
||||
@@ -36,82 +39,23 @@ mod support_tests {
|
||||
|
||||
static TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MoreThanText {
|
||||
pub struct MTTClient {
|
||||
queue: Queue,
|
||||
rx: Receiver<Message>,
|
||||
sender_id: SenderID,
|
||||
session_id: Uuid,
|
||||
}
|
||||
|
||||
impl MoreThanText {
|
||||
pub fn new() -> Self {
|
||||
let queue = Queue::new();
|
||||
let mut output = Self {
|
||||
queue: queue.clone(),
|
||||
};
|
||||
Clock::start(queue.clone());
|
||||
CreateDoc::start(queue.clone());
|
||||
output
|
||||
.create_document(Session::document_definition())
|
||||
.unwrap();
|
||||
output
|
||||
}
|
||||
|
||||
fn new_session() -> UserAction {
|
||||
Addition::new(Session::doc_names()[0].clone()).into()
|
||||
}
|
||||
|
||||
fn recursive_message_request<UA>(&mut self, action: UA) -> Uuid
|
||||
where
|
||||
UA: Into<UserAction>,
|
||||
{
|
||||
match self.records(action) {
|
||||
Ok(data) => {
|
||||
if data.len() == 0 {
|
||||
self.recursive_message_request(MoreThanText::new_session())
|
||||
} else {
|
||||
let rec = data.iter().last().unwrap();
|
||||
match rec.get(Name::english("id")).unwrap() {
|
||||
Field::Uuid(id) => id,
|
||||
_ => unreachable!("should always return uuid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => self.recursive_message_request(MoreThanText::new_session()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_session(&mut self, session: Option<String>) -> Uuid {
|
||||
let action = match session {
|
||||
Some(data) => match Uuid::try_from(data.as_str()) {
|
||||
Ok(id) => {
|
||||
let mut query = Query::new(Session::doc_names()[0].clone());
|
||||
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);
|
||||
query.into()
|
||||
}
|
||||
Err(_) => MoreThanText::new_session(),
|
||||
},
|
||||
None => MoreThanText::new_session(),
|
||||
};
|
||||
self.recursive_message_request(action)
|
||||
}
|
||||
|
||||
pub fn records<UA>(&mut self, request: UA) -> Result<Records, MTTError>
|
||||
where
|
||||
UA: Into<UserAction>,
|
||||
{
|
||||
let req = request.into();
|
||||
impl MTTClient {
|
||||
fn new(mut queue: Queue, sess_id: Option<String>, lang: Option<Language>) -> Self {
|
||||
let sess_name = Session::doc_names()[0].clone();
|
||||
let (tx, rx) = channel();
|
||||
let sender_id = self.queue.add_sender(tx);
|
||||
let doc_id = req.doc_name().clone();
|
||||
let msg = Message::new(req);
|
||||
let msg_id = msg.get_message_id();
|
||||
let sender_id = queue.add_sender(tx);
|
||||
let msg_id = MessageID::new();
|
||||
let paths = [
|
||||
Path::new(
|
||||
Include::Just(msg_id.clone()),
|
||||
Include::Just(doc_id.clone()),
|
||||
Include::Just(sess_name.clone().into()),
|
||||
Include::Just(Action::Records),
|
||||
),
|
||||
Path::new(
|
||||
@@ -120,42 +64,71 @@ impl MoreThanText {
|
||||
Include::Just(Action::Error),
|
||||
),
|
||||
];
|
||||
for path in paths.iter() {
|
||||
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path.clone()));
|
||||
self.queue.send(Message::new(reg_msg));
|
||||
let result = rx.recv().unwrap();
|
||||
let action = result.get_action();
|
||||
match action {
|
||||
MsgAction::Register(status) => match status.get_msg() {
|
||||
RegMsg::Error(err) => {
|
||||
let mut error = err.clone();
|
||||
error.add_parent(ErrorID::Document(msg.doc_name().clone()));
|
||||
self.queue.remove_sender(&sender_id);
|
||||
return Err(error);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => unreachable!("got {:?} should have been a registry message", action),
|
||||
let mut add = Addition::new(Session::doc_names()[0].clone());
|
||||
match lang {
|
||||
Some(language) => {
|
||||
let field: Field = language.into();
|
||||
add.add_field(Session::language_field_names()[0].clone(), field);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
self.queue.send(msg);
|
||||
let output = match rx.recv_timeout(TIMEOUT) {
|
||||
Ok(data) => match data.get_action() {
|
||||
MsgAction::Records(data) => Ok(data.clone()),
|
||||
MsgAction::Error(err) => Err(err.clone()),
|
||||
_ => unreachable!("should only receive records or errors"),
|
||||
},
|
||||
Err(_) => Err(MTTError::new(ErrorID::TimeOut)),
|
||||
let msg = Message::default().set_id(msg_id.clone());
|
||||
for path in paths.iter().cloned() {
|
||||
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path));
|
||||
queue.send(msg.set_action(reg_msg));
|
||||
let result = rx.recv().unwrap();
|
||||
}
|
||||
match sess_id {
|
||||
Some(id) => {
|
||||
let sess_id = match Uuid::try_from(id.as_str()) {
|
||||
Ok(data) => {
|
||||
let mut qry = Query::new(Session::doc_names()[0].clone());
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
||||
.unwrap();
|
||||
calc.add_value(data.clone()).unwrap();
|
||||
qry.add(Session::id_field_names()[0].clone(), calc);
|
||||
queue.send(msg.set_action(qry));
|
||||
}
|
||||
Err(_) => queue.send(msg.set_action(add.clone())),
|
||||
};
|
||||
}
|
||||
None => queue.send(msg.set_action(add.clone())),
|
||||
};
|
||||
self.queue.remove_sender(&sender_id);
|
||||
output
|
||||
let result = rx.recv().unwrap();
|
||||
let session_id = match result.get_action() {
|
||||
MsgAction::Records(result) => {
|
||||
let mut holder = result.clone();
|
||||
if holder.len() == 0 {
|
||||
queue.send(msg.set_action(add));
|
||||
let new_sess = rx.recv().unwrap();
|
||||
holder = match new_sess.get_action() {
|
||||
MsgAction::Records(new_holder) => new_holder.clone(),
|
||||
_ => unreachable!("should only receive session records"),
|
||||
}
|
||||
}
|
||||
let rec = holder.iter().last().unwrap();
|
||||
match rec.get(Session::id_field_names()[0].clone()).unwrap() {
|
||||
Field::Uuid(data) => data.clone(),
|
||||
_ => unreachable!("should always be uuid"),
|
||||
}
|
||||
}
|
||||
_ => unreachable!("should only receive session records"),
|
||||
};
|
||||
Self {
|
||||
queue: queue,
|
||||
rx: rx,
|
||||
sender_id: sender_id,
|
||||
session_id: session_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_document(&mut self, docdef: DocDef) -> Result<(), MTTError> {
|
||||
let (tx, rx) = channel();
|
||||
let sender_id = self.queue.add_sender(tx);
|
||||
let msg = Message::new(docdef);
|
||||
let msg_id = msg.get_message_id();
|
||||
pub fn session_id(&self) -> String {
|
||||
self.session_id.to_string()
|
||||
}
|
||||
|
||||
pub fn create_document(&self, docdef: DocDef) -> Result<(), MTTError> {
|
||||
let msg_id = MessageID::new();
|
||||
let paths = [
|
||||
Path::new(
|
||||
Include::Just(msg_id.clone()),
|
||||
@@ -168,22 +141,106 @@ impl MoreThanText {
|
||||
Include::Just(Action::Error),
|
||||
),
|
||||
];
|
||||
let msg = Message::default()
|
||||
.set_id(msg_id)
|
||||
.set_session(self.session_id.clone().into());
|
||||
for path in paths.iter() {
|
||||
let reg_msg = Register::new(sender_id.clone(), RegMsg::AddRoute(path.clone()));
|
||||
self.queue.send(Message::new(reg_msg));
|
||||
rx.recv().unwrap(); // Wait for completion.
|
||||
let reg_msg = Register::new(self.sender_id.clone(), RegMsg::AddRoute(path.clone()));
|
||||
self.queue.send(msg.set_action(reg_msg));
|
||||
self.rx.recv().unwrap(); // Wait for completion.
|
||||
}
|
||||
self.queue.send(msg);
|
||||
let output = match rx.recv_timeout(TIMEOUT) {
|
||||
self.queue.send(msg.set_action(docdef));
|
||||
match self.rx.recv_timeout(TIMEOUT) {
|
||||
Ok(data) => match data.get_action() {
|
||||
MsgAction::DocumentCreated => Ok(()),
|
||||
MsgAction::Error(err) => Err(err.clone()),
|
||||
_ => unreachable!("should only receive confirmation or errors"),
|
||||
},
|
||||
Err(_) => Err(MTTError::new(ErrorID::TimeOut)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn records<UA>(&self, request: UA) -> Result<Records, MTTError>
|
||||
where
|
||||
UA: Into<ClientAction>,
|
||||
{
|
||||
let req = request.into();
|
||||
let doc_id = req.doc_name().clone();
|
||||
let msg_id = MessageID::new();
|
||||
let paths = [
|
||||
Path::new(
|
||||
Include::Just(msg_id.clone()),
|
||||
Include::Just(doc_id.clone()),
|
||||
Include::Just(Action::Records),
|
||||
),
|
||||
Path::new(
|
||||
Include::Just(msg_id.clone()),
|
||||
Include::All,
|
||||
Include::Just(Action::Error),
|
||||
),
|
||||
];
|
||||
let msg = Message::default()
|
||||
.set_id(msg_id.clone())
|
||||
.set_session(self.session_id.clone().into());
|
||||
for path in paths.iter() {
|
||||
let reg_msg = Register::new(self.sender_id.clone(), RegMsg::AddRoute(path.clone()));
|
||||
self.queue.send(msg.set_action(reg_msg));
|
||||
let result = self.rx.recv().unwrap();
|
||||
let action = result.get_action();
|
||||
match action {
|
||||
MsgAction::Register(status) => match status.get_msg() {
|
||||
RegMsg::Error(err) => {
|
||||
let mut error = err.clone();
|
||||
error.add_parent(ErrorID::Document(doc_id.clone()));
|
||||
return Err(error);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => unreachable!("got {:?} should have been a registry message", action),
|
||||
}
|
||||
}
|
||||
self.queue.send(msg.set_action(req));
|
||||
match self.rx.recv_timeout(TIMEOUT) {
|
||||
Ok(data) => match data.get_action() {
|
||||
MsgAction::Records(data) => Ok(data.clone()),
|
||||
MsgAction::Error(err) => Err(err.clone()),
|
||||
_ => unreachable!("should only receive records or errors"),
|
||||
},
|
||||
Err(_) => Err(MTTError::new(ErrorID::TimeOut)),
|
||||
};
|
||||
self.queue.remove_sender(&sender_id);
|
||||
output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MTTClient {
|
||||
fn drop(&mut self) {
|
||||
self.queue.remove_sender(&self.sender_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MoreThanText {
|
||||
queue: Queue,
|
||||
}
|
||||
|
||||
impl MoreThanText {
|
||||
pub fn new() -> Self {
|
||||
let queue = Queue::new();
|
||||
CreateDoc::start(queue.clone()); // needs to be first.
|
||||
Clock::start(queue.clone());
|
||||
Session::start(queue.clone());
|
||||
Self { queue: queue }
|
||||
}
|
||||
|
||||
pub fn client(&self) -> MTTClient {
|
||||
MTTClient::new(self.queue.clone(), None, None)
|
||||
}
|
||||
|
||||
pub fn client_with_language(&self, lang: Language) -> MTTClient {
|
||||
MTTClient::new(self.queue.clone(), None, Some(lang))
|
||||
}
|
||||
|
||||
pub fn client_with_session(&self, id: String, lang: Option<Language>) -> MTTClient {
|
||||
MTTClient::new(self.queue.clone(), Some(id), lang)
|
||||
}
|
||||
|
||||
pub fn get_document(&self, name: &str, id: &str) -> Result<String, MTTError> {
|
||||
@@ -198,6 +255,7 @@ impl MoreThanText {
|
||||
pub struct TestMoreThanText {
|
||||
mtt: MoreThanText,
|
||||
queue: Queue,
|
||||
channel: Option<Receiver<Message>>,
|
||||
}
|
||||
|
||||
impl TestMoreThanText {
|
||||
@@ -207,22 +265,12 @@ impl TestMoreThanText {
|
||||
Self {
|
||||
mtt: mtt,
|
||||
queue: queue,
|
||||
channel: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_session(&mut self, session: Option<String>) -> Uuid {
|
||||
self.mtt.validate_session(session)
|
||||
}
|
||||
|
||||
pub fn records<UA>(&mut self, request: UA) -> Result<Records, MTTError>
|
||||
where
|
||||
UA: Into<UserAction>,
|
||||
{
|
||||
self.mtt.records(request)
|
||||
}
|
||||
|
||||
pub fn create_document(&mut self, docdef: DocDef) -> Result<(), MTTError> {
|
||||
self.mtt.create_document(docdef)
|
||||
pub fn get_morethantext(&self) -> MoreThanText {
|
||||
self.mtt.clone()
|
||||
}
|
||||
|
||||
pub fn send_time_pulse(&self) {
|
||||
@@ -230,7 +278,7 @@ impl TestMoreThanText {
|
||||
self.queue.send(msg);
|
||||
}
|
||||
|
||||
pub fn register_channel(&self, paths: Vec<Path>) -> Receiver<Message> {
|
||||
pub fn register_channel(&mut self, paths: Vec<Path>) {
|
||||
let mut queue = self.mtt.queue.clone();
|
||||
let (tx, rx) = channel();
|
||||
let sender_id = queue.add_sender(tx);
|
||||
@@ -239,6 +287,29 @@ impl TestMoreThanText {
|
||||
queue.send(Message::new(reg_msg));
|
||||
rx.recv().unwrap(); // Wait for completion.
|
||||
}
|
||||
rx
|
||||
self.channel = Some(rx);
|
||||
}
|
||||
|
||||
pub fn recv(&self) -> Result<Message, RecvTimeoutError> {
|
||||
match &self.channel {
|
||||
Some(rx) => rx.recv_timeout(Duration::from_millis(500)),
|
||||
None => panic!("test environment does not have a channel setup"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_trigger_records(&self, action: Action) -> Records {
|
||||
let msg = self.recv().unwrap();
|
||||
let msg_action = msg.get_action();
|
||||
if action == msg_action.clone().into() {
|
||||
match msg_action {
|
||||
MsgAction::OnAddition(data) => data.clone(),
|
||||
MsgAction::OnDelete(data) => data.clone(),
|
||||
MsgAction::OnQuery(data) => data.clone(),
|
||||
MsgAction::OnUpdate(data) => data.clone(),
|
||||
_ => panic!("{:?} is not a trigger", action),
|
||||
}
|
||||
} else {
|
||||
panic!("received {:?} instead of {:?} trigger", msg, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/main.rs
11
src/main.rs
@@ -55,7 +55,7 @@ async fn create_app(state: MoreThanText) -> Router {
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
struct SessionID(Uuid);
|
||||
struct SessionID(String);
|
||||
|
||||
impl<S> FromRequestParts<S> for SessionID
|
||||
where
|
||||
@@ -73,7 +73,11 @@ where
|
||||
let requested = req_id.clone();
|
||||
let (tx, mut rx) = channel(1);
|
||||
spawn(async move {
|
||||
tx.send(state.validate_session(requested)).await.unwrap();
|
||||
let id = match requested {
|
||||
Some(data) => state.client_with_session(data, None).session_id(),
|
||||
None => state.client().session_id(),
|
||||
};
|
||||
tx.send(id).await.unwrap();
|
||||
});
|
||||
let id = rx.recv().await.unwrap();
|
||||
if !req_id.is_some_and(|x| x == id.to_string()) {
|
||||
@@ -84,7 +88,7 @@ where
|
||||
}
|
||||
|
||||
async fn mtt_conn(
|
||||
_sess_id: SessionID,
|
||||
sess_id: SessionID,
|
||||
method: Method,
|
||||
path: Path<HashMap<String, String>>,
|
||||
state: State<MoreThanText>,
|
||||
@@ -92,6 +96,7 @@ async fn mtt_conn(
|
||||
) -> impl IntoResponse {
|
||||
let (tx, mut rx) = channel(1);
|
||||
spawn(async move {
|
||||
let client = state.client_with_session(sess_id.0, None);
|
||||
match method {
|
||||
Method::GET => match path.get("document") {
|
||||
Some(doc) => tx.send(state.get_document(doc, "home")).await.unwrap(),
|
||||
|
||||
309
src/message.rs
309
src/message.rs
@@ -1,9 +1,312 @@
|
||||
pub mod wrapper;
|
||||
|
||||
use crate::name::NameType;
|
||||
use crate::{
|
||||
action::{Field, MsgAction},
|
||||
name::NameType,
|
||||
queue::data_director::{Include, Path, Route},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub trait MessageAction {
|
||||
fn doc_name(&self) -> &NameType {
|
||||
&NameType::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct MessageID {
|
||||
data: Uuid,
|
||||
}
|
||||
|
||||
impl MessageID {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod message_ids {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn are_message_ids_unique() {
|
||||
let id1 = MessageID::new();
|
||||
let id2 = MessageID::new();
|
||||
assert!(id1 != id2);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Message {
|
||||
msg_id: MessageID,
|
||||
action: MsgAction,
|
||||
route: Route,
|
||||
session: Field,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new<A>(action: A) -> Self
|
||||
where
|
||||
A: Into<MsgAction>,
|
||||
{
|
||||
Self {
|
||||
msg_id: MessageID::new(),
|
||||
action: action.into(),
|
||||
route: Route::default(),
|
||||
session: Field::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_id(&self, msg_id: MessageID) -> Self {
|
||||
let mut output = self.clone();
|
||||
output.msg_id = msg_id;
|
||||
output
|
||||
}
|
||||
|
||||
pub fn set_session(&self, session: Field) -> Self {
|
||||
let mut output = self.clone();
|
||||
output.session = session;
|
||||
output
|
||||
}
|
||||
|
||||
pub fn set_action<A>(&self, action: A) -> Self
|
||||
where
|
||||
A: Into<MsgAction>,
|
||||
{
|
||||
let mut output = self.clone();
|
||||
output.action = action.into();
|
||||
output
|
||||
}
|
||||
|
||||
pub fn get_message_id(&self) -> &MessageID {
|
||||
&self.msg_id
|
||||
}
|
||||
|
||||
pub fn get_action(&self) -> &MsgAction {
|
||||
&self.action
|
||||
}
|
||||
|
||||
pub fn get_path(&self) -> Path {
|
||||
Path::new(
|
||||
Include::Just(self.msg_id.clone()),
|
||||
Include::Just(self.action.doc_name().clone()),
|
||||
Include::Just(self.action.clone().into()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_route(&self) -> Route {
|
||||
self.route.clone()
|
||||
}
|
||||
|
||||
pub fn set_route(&mut self, route: Route) {
|
||||
self.route = route;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Message {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
msg_id: MessageID::new(),
|
||||
action: MsgAction::None,
|
||||
route: Route::default(),
|
||||
session: Field::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageAction for Message {
|
||||
fn doc_name(&self) -> &NameType {
|
||||
self.get_action().doc_name()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod messages {
|
||||
use super::*;
|
||||
use crate::{
|
||||
action::{DocDef, Query, Reply},
|
||||
name::{name_id_support::test_name_id, Name},
|
||||
ErrorID, MTTError,
|
||||
};
|
||||
|
||||
fn is_there_a_default_message() {
|
||||
let msg = Message::default();
|
||||
match msg.action {
|
||||
MsgAction::None => {}
|
||||
_ => panic!("should have been no action"),
|
||||
}
|
||||
assert_eq!(msg.session, Field::None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_create_new_messsage() {
|
||||
let doc_name = Name::english("something");
|
||||
let qry = Query::new(doc_name.clone());
|
||||
let msg = Message::new(qry);
|
||||
let expected: NameType = doc_name.into();
|
||||
match msg.action {
|
||||
MsgAction::Query(data) => assert_eq!(data.doc_name(), &expected),
|
||||
_ => unreachable!("should have been a query"),
|
||||
}
|
||||
match msg.session {
|
||||
Field::None => {}
|
||||
_ => unreachable!("should have been none"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_id_be_set() {
|
||||
let doc_name = Name::english("identification");
|
||||
let qry = Query::new(doc_name.clone());
|
||||
let msg_id = MessageID::new();
|
||||
let msg = Message::new(qry).set_id(msg_id.clone());
|
||||
assert_eq!(msg.msg_id, msg_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_session_be_set() {
|
||||
let doc_name = Name::english("identification");
|
||||
let qry = Query::new(doc_name.clone());
|
||||
let sess_id: Field = Uuid::new_v4().into();
|
||||
let msg = Message::new(qry).set_session(sess_id.clone());
|
||||
assert_eq!(msg.session, sess_id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_action_be_set() {
|
||||
let doc_name = Name::english("action");
|
||||
let expected: NameType = doc_name.clone().into();
|
||||
let docdef = DocDef::new(doc_name.clone());
|
||||
let qry = Query::new(doc_name.clone());
|
||||
let msg = Message::new(docdef).set_action(qry);
|
||||
match msg.action {
|
||||
MsgAction::Query(data) => assert_eq!(data.doc_name(), &expected),
|
||||
_ => unreachable!("should have been a query"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_the_document_be_a_named_reference() {
|
||||
let dts = [Name::english("one"), Name::english("two")];
|
||||
for document in dts.into_iter() {
|
||||
let msg = Message::new(MsgAction::Create(DocDef::new(document.clone())));
|
||||
match msg.get_action() {
|
||||
MsgAction::Create(_) => {}
|
||||
_ => unreachable!("should have been a create document"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_the_document_be_an_id() {
|
||||
let document = test_name_id();
|
||||
let msg = Message::new(Query::new(document.clone()));
|
||||
match msg.get_action() {
|
||||
MsgAction::Query(_) => {}
|
||||
_ => unreachable!("should have been an access query"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_messages_contain_routes() {
|
||||
let name = Name::english("whatever");
|
||||
let mut msg = Message::new(Query::new(name.clone()));
|
||||
let default_route = msg.get_route();
|
||||
match default_route.msg_id {
|
||||
Include::Just(_) => unreachable!("should defalt to all"),
|
||||
Include::All => {}
|
||||
}
|
||||
match default_route.doc_id {
|
||||
Include::Just(_) => unreachable!("should defalt to all"),
|
||||
Include::All => {}
|
||||
}
|
||||
match default_route.action {
|
||||
Include::Just(_) => unreachable!("should defalt to all"),
|
||||
Include::All => {}
|
||||
}
|
||||
let doc_id = test_name_id();
|
||||
let route = Route::new(
|
||||
Include::Just(msg.get_message_id().clone()),
|
||||
Include::Just(doc_id.clone()),
|
||||
Include::Just(msg.get_action().into()),
|
||||
);
|
||||
msg.set_route(route);
|
||||
let result = msg.get_route();
|
||||
match result.msg_id {
|
||||
Include::Just(data) => assert_eq!(&data, msg.get_message_id()),
|
||||
Include::All => unreachable!("should have message id"),
|
||||
}
|
||||
match result.doc_id {
|
||||
Include::Just(data) => assert_eq!(data, doc_id),
|
||||
Include::All => unreachable!("should have document id"),
|
||||
}
|
||||
match result.action {
|
||||
Include::Just(data) => assert_eq!(data, msg.get_action().into()),
|
||||
Include::All => unreachable!("should have action"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_the_message_id_random() {
|
||||
let mut ids: Vec<MessageID> = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let msg = Message::new(Query::new(Name::english("test")));
|
||||
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(Query::new(name.clone()));
|
||||
let responce = Reply::new(Name::english("something"));
|
||||
let reply = msg.set_action(responce);
|
||||
assert_eq!(reply.get_message_id(), msg.get_message_id());
|
||||
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(Query::new(name.clone()));
|
||||
let err_msg = Uuid::new_v4().to_string();
|
||||
let result = msg.set_action(MTTError::new(ErrorID::DocumentNotFound));
|
||||
assert_eq!(result.get_message_id(), msg.get_message_id());
|
||||
match result.get_action() {
|
||||
MsgAction::Error(data) => match data.get_error_ids().back().unwrap() {
|
||||
ErrorID::DocumentNotFound => {}
|
||||
_ => unreachable!("got {:?}, should have received not found", data),
|
||||
},
|
||||
_ => unreachable!("should have been a reply"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_a_response_message() {
|
||||
let doc_id = test_name_id();
|
||||
let msg = Message::new(Query::new(doc_id.clone()));
|
||||
let data = Uuid::new_v4().to_string();
|
||||
let result1 = msg.set_action(MTTError::new(ErrorID::DocumentNotFound));
|
||||
let result2 = msg.set_action(Reply::new(NameType::None));
|
||||
assert_eq!(result1.get_message_id(), msg.get_message_id());
|
||||
assert_eq!(result2.get_message_id(), msg.get_message_id());
|
||||
let action1 = result1.get_action();
|
||||
match action1 {
|
||||
MsgAction::Error(err) => match err.get_error_ids().back().unwrap() {
|
||||
ErrorID::DocumentNotFound => {}
|
||||
_ => 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
use super::MessageAction;
|
||||
use crate::{
|
||||
action::{CalcValue, Field, FieldType, MsgAction, Operand, Query, Reply},
|
||||
mtterror::{ErrorID, MTTError},
|
||||
name::{NameType, Names},
|
||||
queue::data_director::{Include, Path, Route},
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
time::Duration,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Message {
|
||||
msg_id: Uuid,
|
||||
// document_id: NameType,
|
||||
action: MsgAction,
|
||||
route: Route,
|
||||
// session: Option<?>
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new<A>(action: A) -> Self
|
||||
where
|
||||
A: Into<MsgAction>,
|
||||
{
|
||||
Self {
|
||||
msg_id: Uuid::new_v4(),
|
||||
action: action.into(),
|
||||
route: Route::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_message_id(&self) -> &Uuid {
|
||||
&self.msg_id
|
||||
}
|
||||
|
||||
pub fn get_action(&self) -> &MsgAction {
|
||||
&self.action
|
||||
}
|
||||
|
||||
pub fn get_path(&self) -> Path {
|
||||
Path::new(
|
||||
Include::Just(self.msg_id.clone()),
|
||||
Include::Just(self.action.doc_name().clone()),
|
||||
Include::Just(self.action.clone().into()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_route(&self) -> Route {
|
||||
self.route.clone()
|
||||
}
|
||||
|
||||
pub fn set_route(&mut self, route: Route) {
|
||||
self.route = route;
|
||||
}
|
||||
|
||||
pub fn response<A>(&self, action: A) -> Self
|
||||
where
|
||||
A: Into<MsgAction>,
|
||||
{
|
||||
Self {
|
||||
msg_id: self.msg_id.clone(),
|
||||
action: action.into(),
|
||||
route: Route::default(),
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
action: action.into(),
|
||||
route: Route::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageAction for Message {
|
||||
fn doc_name(&self) -> &NameType {
|
||||
self.get_action().doc_name()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod messages {
|
||||
use super::*;
|
||||
use crate::{
|
||||
action::DocDef,
|
||||
name::{name_id_support::test_name_id, Name},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn can_the_document_be_a_named_reference() {
|
||||
let dts = [Name::english("one"), Name::english("two")];
|
||||
for document in dts.into_iter() {
|
||||
let msg = Message::new(MsgAction::Create(DocDef::new(document.clone())));
|
||||
match msg.get_action() {
|
||||
MsgAction::Create(_) => {}
|
||||
_ => unreachable!("should have been a create document"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_the_document_be_an_id() {
|
||||
let document = test_name_id();
|
||||
let msg = Message::new(Query::new(document.clone()));
|
||||
match msg.get_action() {
|
||||
MsgAction::Query(_) => {}
|
||||
_ => unreachable!("should have been an access query"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_messages_contain_routes() {
|
||||
let name = Name::english("whatever");
|
||||
let mut msg = Message::new(Query::new(name.clone()));
|
||||
let default_route = msg.get_route();
|
||||
match default_route.msg_id {
|
||||
Include::Just(_) => unreachable!("should defalt to all"),
|
||||
Include::All => {}
|
||||
}
|
||||
match default_route.doc_id {
|
||||
Include::Just(_) => unreachable!("should defalt to all"),
|
||||
Include::All => {}
|
||||
}
|
||||
match default_route.action {
|
||||
Include::Just(_) => unreachable!("should defalt to all"),
|
||||
Include::All => {}
|
||||
}
|
||||
let doc_id = test_name_id();
|
||||
let route = Route::new(
|
||||
Include::Just(msg.get_message_id().clone()),
|
||||
Include::Just(doc_id.clone()),
|
||||
Include::Just(msg.get_action().into()),
|
||||
);
|
||||
msg.set_route(route);
|
||||
let result = msg.get_route();
|
||||
match result.msg_id {
|
||||
Include::Just(data) => assert_eq!(&data, msg.get_message_id()),
|
||||
Include::All => unreachable!("should have message id"),
|
||||
}
|
||||
match result.doc_id {
|
||||
Include::Just(data) => assert_eq!(data, doc_id),
|
||||
Include::All => unreachable!("should have document id"),
|
||||
}
|
||||
match result.action {
|
||||
Include::Just(data) => assert_eq!(data, msg.get_action().into()),
|
||||
Include::All => unreachable!("should have action"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_the_message_id_random() {
|
||||
let mut ids: Vec<Uuid> = Vec::new();
|
||||
for _ in 0..5 {
|
||||
let msg = Message::new(Query::new(Name::english("test")));
|
||||
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(Query::new(name.clone()));
|
||||
let responce = Reply::new(Name::english("something"));
|
||||
let reply = msg.response(responce);
|
||||
assert_eq!(reply.get_message_id(), msg.get_message_id());
|
||||
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(Query::new(name.clone()));
|
||||
let err_msg = Uuid::new_v4().to_string();
|
||||
let result = msg.response(MTTError::new(ErrorID::DocumentNotFound));
|
||||
assert_eq!(result.get_message_id(), msg.get_message_id());
|
||||
match result.get_action() {
|
||||
MsgAction::Error(data) => match data.get_error_ids().back().unwrap() {
|
||||
ErrorID::DocumentNotFound => {}
|
||||
_ => unreachable!("got {:?}, should have received not found", data),
|
||||
},
|
||||
_ => unreachable!("should have been a reply"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_make_a_response_message() {
|
||||
let doc_id = test_name_id();
|
||||
let msg = Message::new(Query::new(doc_id.clone()));
|
||||
let data = Uuid::new_v4().to_string();
|
||||
let result1 = msg.response(MTTError::new(ErrorID::DocumentNotFound));
|
||||
let result2 = msg.response(Reply::new(NameType::None));
|
||||
assert_eq!(result1.get_message_id(), msg.get_message_id());
|
||||
assert_eq!(result2.get_message_id(), msg.get_message_id());
|
||||
let action1 = result1.get_action();
|
||||
match action1 {
|
||||
MsgAction::Error(err) => match err.get_error_ids().back().unwrap() {
|
||||
ErrorID::DocumentNotFound => {}
|
||||
_ => 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Operation {
|
||||
field_name: String,
|
||||
operation: Operand,
|
||||
value: Field,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Operation {
|
||||
pub 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()
|
||||
}
|
||||
|
||||
pub fn validate(&self, field: &Field) -> bool {
|
||||
self.operation.validate(field, &self.value)
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&NameType, &CalcValue)> {
|
||||
self.data.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod documents {
|
||||
use super::*;
|
||||
use crate::name::Name;
|
||||
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ use crate::{
|
||||
action::{Field, FieldType},
|
||||
message::MessageAction,
|
||||
name::{Name, NameType},
|
||||
MissingTranslation,
|
||||
};
|
||||
use isolang::Language;
|
||||
use std::{collections::VecDeque, error::Error, fmt};
|
||||
@@ -19,8 +20,9 @@ pub enum ErrorID {
|
||||
FieldMissingData,
|
||||
FieldNameAlreadyExists,
|
||||
FieldTypeExpected(FieldType),
|
||||
IndexEntryAlreadyExists,
|
||||
IndexEntryAlreadyExists(Field),
|
||||
InvalidFieldName(Name),
|
||||
MissingTranslation(Vec<MissingTranslation>),
|
||||
NameAlreadyExists,
|
||||
NameLanguageNotUnique,
|
||||
NameNotFound(NameType),
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
pub mod data_director;
|
||||
pub mod router;
|
||||
|
||||
pub use router::SenderID;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::SenderID;
|
||||
use crate::{
|
||||
action::{Action, MsgAction},
|
||||
message::{wrapper::Message, MessageAction},
|
||||
message::{Message, MessageAction, MessageID},
|
||||
mtterror::MTTError,
|
||||
name::{Name, NameID, NameType, Names},
|
||||
queue::router::Queue,
|
||||
@@ -55,18 +56,19 @@ pub enum RegMsg {
|
||||
Error(MTTError),
|
||||
GetNameID(Name),
|
||||
Ok,
|
||||
RemoveSender(Uuid),
|
||||
RemoveRoute(Route),
|
||||
RemoveSender(SenderID),
|
||||
RouteID(RouteID),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Register {
|
||||
msg: RegMsg,
|
||||
sender_id: Uuid,
|
||||
sender_id: SenderID,
|
||||
}
|
||||
|
||||
impl Register {
|
||||
pub fn new(sender_id: Uuid, reg_msg: RegMsg) -> Self {
|
||||
pub fn new(sender_id: SenderID, reg_msg: RegMsg) -> Self {
|
||||
Self {
|
||||
msg: reg_msg,
|
||||
sender_id: sender_id,
|
||||
@@ -77,7 +79,7 @@ impl Register {
|
||||
&self.msg
|
||||
}
|
||||
|
||||
pub fn get_sender_id(&self) -> &Uuid {
|
||||
pub fn get_sender_id(&self) -> &SenderID {
|
||||
&self.sender_id
|
||||
}
|
||||
|
||||
@@ -99,13 +101,13 @@ mod registries {
|
||||
#[test]
|
||||
fn does_registry_store_data() {
|
||||
let name_id = test_name_id();
|
||||
let sender_data_id = Uuid::new_v4();
|
||||
let sender_data_id = SenderID::new();
|
||||
let inputs = [
|
||||
RegMsg::DocumentNameID(name_id.clone()),
|
||||
RegMsg::RemoveSender(sender_data_id.clone()),
|
||||
];
|
||||
for regmsg in inputs.iter() {
|
||||
let sender_id = Uuid::new_v4();
|
||||
let sender_id = SenderID::new();
|
||||
let reg = Register::new(sender_id.clone(), regmsg.clone());
|
||||
assert_eq!(reg.doc_name(), &NameType::None);
|
||||
assert_eq!(reg.get_sender_id(), &sender_id);
|
||||
@@ -120,13 +122,13 @@ mod registries {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Path {
|
||||
pub msg_id: Include<Uuid>,
|
||||
pub msg_id: Include<MessageID>,
|
||||
pub doc: Include<NameType>,
|
||||
pub action: Include<Action>,
|
||||
}
|
||||
|
||||
impl Path {
|
||||
pub fn new(id: Include<Uuid>, doc: Include<NameType>, action: Include<Action>) -> Self {
|
||||
pub fn new(id: Include<MessageID>, doc: Include<NameType>, action: Include<Action>) -> Self {
|
||||
Self {
|
||||
msg_id: id,
|
||||
doc: doc,
|
||||
@@ -139,7 +141,7 @@ impl Path {
|
||||
NT: Into<NameType>,
|
||||
{
|
||||
Self {
|
||||
msg_id: Include::Just(Uuid::new_v4()),
|
||||
msg_id: Include::Just(MessageID::new()),
|
||||
doc: Include::Just(name.into()),
|
||||
action: Include::Just(action.into()),
|
||||
}
|
||||
@@ -182,7 +184,7 @@ mod paths {
|
||||
#[test]
|
||||
fn message_ids_are_unique_for_message_paths() {
|
||||
let count = 10;
|
||||
let mut ids: Vec<Uuid> = Vec::new();
|
||||
let mut ids: Vec<MessageID> = Vec::new();
|
||||
for _ in 0..count {
|
||||
let path =
|
||||
Path::for_message(NameType::None, &MsgAction::Show(Show::new(NameType::None)));
|
||||
@@ -200,11 +202,11 @@ mod paths {
|
||||
pub struct Route {
|
||||
pub action: Include<Action>,
|
||||
pub doc_id: Include<NameID>,
|
||||
pub msg_id: Include<Uuid>,
|
||||
pub msg_id: Include<MessageID>,
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn new(msg_id: Include<Uuid>, doc: Include<NameID>, action: Include<Action>) -> Self {
|
||||
pub fn new(msg_id: Include<MessageID>, doc: Include<NameID>, action: Include<Action>) -> Self {
|
||||
Self {
|
||||
action: action,
|
||||
doc_id: doc,
|
||||
@@ -252,7 +254,7 @@ impl From<&RouteID> for Route {
|
||||
pub struct RouteID {
|
||||
action: Option<Action>,
|
||||
doc_id: Option<NameID>,
|
||||
msg_id: Option<Uuid>,
|
||||
msg_id: Option<MessageID>,
|
||||
}
|
||||
|
||||
impl From<Route> for RouteID {
|
||||
@@ -275,7 +277,7 @@ impl From<Route> for RouteID {
|
||||
}
|
||||
|
||||
struct RouteStorage {
|
||||
data: HashMap<RouteID, HashSet<Uuid>>,
|
||||
data: HashMap<RouteID, HashSet<SenderID>>,
|
||||
}
|
||||
|
||||
impl RouteStorage {
|
||||
@@ -285,7 +287,7 @@ impl RouteStorage {
|
||||
}
|
||||
}
|
||||
|
||||
fn add(&mut self, route: Route, sender_id: Uuid) -> RouteID {
|
||||
fn add(&mut self, route: Route, sender_id: SenderID) -> RouteID {
|
||||
let route_id: RouteID = route.into();
|
||||
let set = match self.data.get_mut(&route_id) {
|
||||
Some(result) => result,
|
||||
@@ -299,7 +301,7 @@ impl RouteStorage {
|
||||
route_id
|
||||
}
|
||||
|
||||
fn remove_sender_id(&mut self, sender_id: &Uuid) {
|
||||
fn remove_sender_id(&mut self, sender_id: &SenderID) {
|
||||
let mut removal: Vec<RouteID> = Vec::new();
|
||||
for (route_id, set) in self.data.iter_mut() {
|
||||
set.remove(sender_id);
|
||||
@@ -312,7 +314,24 @@ impl RouteStorage {
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, route: Route) -> HashSet<Uuid> {
|
||||
fn remove_route(&mut self, route: Route, sender: SenderID) {
|
||||
let route_id: RouteID = route.into();
|
||||
let mut remove = false;
|
||||
match self.data.get_mut(&route_id) {
|
||||
Some(store) => {
|
||||
store.remove(&sender);
|
||||
if store.len() == 0 {
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
if remove {
|
||||
self.data.remove(&route_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, route: Route) -> HashSet<SenderID> {
|
||||
let mut output = HashSet::new();
|
||||
for (route_id, set) in self.data.iter() {
|
||||
if route == route_id.into() {
|
||||
@@ -330,10 +349,10 @@ mod route_storeage {
|
||||
#[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 id1 = SenderID::new();
|
||||
let id2 = SenderID::new();
|
||||
let route1 = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
let route2 = Route::new(Include::Just(MessageID::new()), 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());
|
||||
@@ -359,7 +378,7 @@ mod route_storeage {
|
||||
#[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 route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
let result = routes.get(route);
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
@@ -368,11 +387,11 @@ mod route_storeage {
|
||||
fn returns_all_entries_using_the_same_route() {
|
||||
let count = 5;
|
||||
let mut routes = RouteStorage::new();
|
||||
let mut ids: HashSet<Uuid> = HashSet::new();
|
||||
let mut ids: HashSet<SenderID> = HashSet::new();
|
||||
while ids.len() < count {
|
||||
ids.insert(Uuid::new_v4());
|
||||
ids.insert(SenderID::new());
|
||||
}
|
||||
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
||||
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
for id in ids.iter() {
|
||||
routes.add(route.clone(), id.clone());
|
||||
}
|
||||
@@ -384,8 +403,8 @@ mod route_storeage {
|
||||
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);
|
||||
let id = SenderID::new();
|
||||
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
for _ in 0..count {
|
||||
routes.add(route.clone(), id.clone());
|
||||
}
|
||||
@@ -397,10 +416,10 @@ mod route_storeage {
|
||||
#[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);
|
||||
let id1 = SenderID::new();
|
||||
let id2 = SenderID::new();
|
||||
let route1 = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
let route2 = Route::new(Include::Just(MessageID::new()), 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);
|
||||
@@ -414,11 +433,11 @@ mod route_storeage {
|
||||
fn can_remove_sender_id() {
|
||||
let mut routes = RouteStorage::new();
|
||||
let count = 5;
|
||||
let mut ids: HashSet<Uuid> = HashSet::new();
|
||||
let mut ids: HashSet<SenderID> = HashSet::new();
|
||||
while ids.len() < count {
|
||||
ids.insert(Uuid::new_v4());
|
||||
ids.insert(SenderID::new());
|
||||
}
|
||||
let route = Route::new(Include::Just(Uuid::new_v4()), Include::All, Include::All);
|
||||
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
for id in ids.iter() {
|
||||
routes.add(route.clone(), id.clone());
|
||||
}
|
||||
@@ -432,12 +451,37 @@ mod route_storeage {
|
||||
#[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);
|
||||
let id = SenderID::new();
|
||||
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
routes.add(route.clone(), id.clone());
|
||||
routes.remove_sender_id(&id);
|
||||
assert_eq!(routes.data.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_route_be_removed() {
|
||||
let mut routes = RouteStorage::new();
|
||||
let id = SenderID::new();
|
||||
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
routes.add(route.clone(), id.clone());
|
||||
routes.remove_route(route.clone(), id);
|
||||
assert_eq!(routes.data.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_shared_route_be_removed() {
|
||||
let mut routes = RouteStorage::new();
|
||||
let id1 = SenderID::new();
|
||||
let id2 = SenderID::new();
|
||||
let route = Route::new(Include::Just(MessageID::new()), Include::All, Include::All);
|
||||
routes.add(route.clone(), id1.clone());
|
||||
routes.add(route.clone(), id2.clone());
|
||||
routes.remove_route(route.clone(), id1);
|
||||
assert_eq!(routes.data.len(), 1);
|
||||
let ids = routes.get(route.clone());
|
||||
let id = ids.iter().last().unwrap();
|
||||
assert_eq!(id, &id2);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DocRegistry {
|
||||
@@ -470,7 +514,7 @@ impl DocRegistry {
|
||||
match msg.get_action() {
|
||||
MsgAction::Register(data) => {
|
||||
let id = data.get_sender_id();
|
||||
let reply = msg.response(self.register_action(data));
|
||||
let reply = msg.set_action(self.register_action(data));
|
||||
self.queue.forward(id, reply);
|
||||
}
|
||||
_ => match self.path_to_route(&msg.get_path()) {
|
||||
@@ -480,7 +524,7 @@ impl DocRegistry {
|
||||
self.queue.forward(sender_id, msg.clone());
|
||||
}
|
||||
}
|
||||
Err(err) => self.queue.send(msg.response(MsgAction::Error(err))),
|
||||
Err(err) => self.queue.send(msg.set_action(MsgAction::Error(err))),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -521,6 +565,11 @@ impl DocRegistry {
|
||||
self.routes.remove_sender_id(sender_id);
|
||||
reg.response(RegMsg::Ok)
|
||||
}
|
||||
RegMsg::RemoveRoute(route) => {
|
||||
self.routes
|
||||
.remove_route(route.clone(), reg.get_sender_id().clone());
|
||||
reg.response(RegMsg::Ok)
|
||||
}
|
||||
_ => reg.response(RegMsg::Ok),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
message::wrapper::Message,
|
||||
message::Message,
|
||||
name::NameType,
|
||||
queue::data_director::{DocRegistry, RegMsg, Register},
|
||||
};
|
||||
@@ -12,9 +12,44 @@ use std::{
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct SenderID {
|
||||
data: Uuid,
|
||||
}
|
||||
|
||||
impl SenderID {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
data: Uuid::new_v4(),
|
||||
}
|
||||
}
|
||||
|
||||
fn nil() -> Self {
|
||||
Self { data: Uuid::nil() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod sender_ids {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn are_sender_ids_unique() {
|
||||
let id1 = SenderID::new();
|
||||
let id2 = SenderID::new();
|
||||
assert!(id1 != id2, "ids should be random");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_nil_available() {
|
||||
let id = SenderID::nil();
|
||||
assert_eq!(id.data, Uuid::nil());
|
||||
}
|
||||
}
|
||||
|
||||
struct Router {
|
||||
doc_registry: Sender<Message>,
|
||||
senders: HashMap<Uuid, Sender<Message>>,
|
||||
senders: HashMap<SenderID, Sender<Message>>,
|
||||
}
|
||||
|
||||
impl Router {
|
||||
@@ -25,23 +60,23 @@ impl Router {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_sender(&mut self, sender: Sender<Message>) -> Uuid {
|
||||
let mut id = Uuid::new_v4();
|
||||
fn add_sender(&mut self, sender: Sender<Message>) -> SenderID {
|
||||
let mut id = SenderID::new();
|
||||
while self.senders.contains_key(&id) {
|
||||
id = Uuid::new_v4();
|
||||
id = SenderID::new();
|
||||
}
|
||||
self.senders.insert(id.clone(), sender);
|
||||
id
|
||||
}
|
||||
|
||||
fn remove_sender(&mut self, id: &Uuid) {
|
||||
let action = Register::new(Uuid::nil(), RegMsg::RemoveSender(id.clone()));
|
||||
fn remove_sender(&mut self, id: &SenderID) {
|
||||
let action = Register::new(SenderID::nil(), RegMsg::RemoveSender(id.clone()));
|
||||
self.doc_registry.send(Message::new(action)).unwrap();
|
||||
self.senders.remove(id);
|
||||
}
|
||||
|
||||
fn forward(&self, id: &Uuid, msg: Message) {
|
||||
if id == &Uuid::nil() {
|
||||
fn forward(&self, id: &SenderID, msg: Message) {
|
||||
if id == &SenderID::nil() {
|
||||
return;
|
||||
}
|
||||
match self.senders.get(id) {
|
||||
@@ -70,17 +105,17 @@ impl Queue {
|
||||
output
|
||||
}
|
||||
|
||||
pub fn add_sender(&mut self, sender: Sender<Message>) -> Uuid {
|
||||
pub fn add_sender(&mut self, sender: Sender<Message>) -> SenderID {
|
||||
let mut router = self.router.write().unwrap();
|
||||
router.add_sender(sender)
|
||||
}
|
||||
|
||||
pub fn remove_sender(&mut self, id: &Uuid) {
|
||||
pub fn remove_sender(&mut self, id: &SenderID) {
|
||||
let mut router = self.router.write().unwrap();
|
||||
router.remove_sender(id);
|
||||
}
|
||||
|
||||
pub fn forward(&self, id: &Uuid, msg: Message) {
|
||||
pub fn forward(&self, id: &SenderID, msg: Message) {
|
||||
let router = self.router.read().unwrap();
|
||||
router.forward(id, msg);
|
||||
}
|
||||
@@ -145,14 +180,14 @@ mod routers {
|
||||
fn can_forward_message() {
|
||||
let mut setup = Setup::new();
|
||||
let router = setup.get_router_mut();
|
||||
let mut receivers: HashMap<Uuid, Receiver<Message>> = HashMap::new();
|
||||
let mut receivers: HashMap<SenderID, Receiver<Message>> = HashMap::new();
|
||||
for _ in 0..10 {
|
||||
let (tx, rx) = channel();
|
||||
let id = router.add_sender(tx);
|
||||
receivers.insert(id, rx);
|
||||
}
|
||||
for (id, recv) in receivers.iter() {
|
||||
let msg = Message::new(Query::new(Name::english(id.to_string().as_str())));
|
||||
let msg = Message::new(Query::new(Name::english("something")));
|
||||
router.forward(id, msg.clone());
|
||||
let result = recv.recv_timeout(TIMEOUT).unwrap();
|
||||
assert_eq!(result.get_message_id(), msg.get_message_id());
|
||||
@@ -164,7 +199,7 @@ mod routers {
|
||||
let mut setup = Setup::new();
|
||||
let router = setup.get_router_mut();
|
||||
let count = 10;
|
||||
let mut holder: HashSet<Uuid> = HashSet::new();
|
||||
let mut holder: HashSet<SenderID> = HashSet::new();
|
||||
for _ in 0..count {
|
||||
let (tx, _) = channel();
|
||||
holder.insert(router.add_sender(tx));
|
||||
@@ -176,7 +211,7 @@ mod routers {
|
||||
fn can_remove_sender() {
|
||||
let mut setup = Setup::new();
|
||||
let router = setup.get_router_mut();
|
||||
let mut receivers: HashMap<Uuid, Receiver<Message>> = HashMap::new();
|
||||
let mut receivers: HashMap<SenderID, Receiver<Message>> = HashMap::new();
|
||||
for _ in 0..10 {
|
||||
let (tx, rx) = channel();
|
||||
let id = router.add_sender(tx);
|
||||
@@ -207,7 +242,7 @@ mod routers {
|
||||
_ => unreachable!("got {:?}, should have been register", action),
|
||||
}
|
||||
for (id, recv) in receivers.iter() {
|
||||
let msg = Message::new(Query::new(Name::english(id.to_string().as_str())));
|
||||
let msg = Message::new(Query::new(Name::english("something")));
|
||||
router.forward(id, msg.clone());
|
||||
let result = recv.recv_timeout(TIMEOUT).unwrap();
|
||||
assert_eq!(result.get_message_id(), msg.get_message_id());
|
||||
@@ -218,7 +253,7 @@ mod routers {
|
||||
fn ignores_bad_id_removals() {
|
||||
let mut setup = Setup::new();
|
||||
let router = setup.get_router_mut();
|
||||
let removed = Uuid::new_v4();
|
||||
let removed = SenderID::new();
|
||||
router.remove_sender(&removed);
|
||||
assert_eq!(router.senders.len(), 0, "should have no senders.");
|
||||
let announce = setup.recv().unwrap();
|
||||
@@ -251,7 +286,7 @@ mod queues {
|
||||
struct Setup {
|
||||
test_mod: Queue,
|
||||
rx: Receiver<Message>,
|
||||
rx_id: Uuid,
|
||||
rx_id: SenderID,
|
||||
}
|
||||
|
||||
impl Setup {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
mod support;
|
||||
|
||||
use chrono::Utc;
|
||||
use morethantext::{
|
||||
action::{Addition, DocDef, Field, FieldType, Query},
|
||||
ErrorID, MTTError, MoreThanText, Name,
|
||||
Action, CalcValue, Calculation, ErrorID, Include, IndexType, MTTError, MoreThanText, Name,
|
||||
Operand, Path, TestMoreThanText,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
use support::{random_name, TestDocument};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -12,6 +14,7 @@ use uuid::Uuid;
|
||||
fn can_new_documents_be_added() {
|
||||
let count = 5;
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let doc_name = random_name();
|
||||
let field_name = random_name();
|
||||
let mut data: HashSet<Field> = HashSet::new();
|
||||
@@ -19,15 +22,15 @@ fn can_new_documents_be_added() {
|
||||
data.insert(i.into());
|
||||
}
|
||||
let mut docdef = DocDef::new(doc_name.clone());
|
||||
docdef.add_field(field_name.clone(), FieldType::Integer);
|
||||
mtt.create_document(docdef);
|
||||
docdef.add_field(vec![field_name.clone()], FieldType::Integer);
|
||||
client.create_document(docdef);
|
||||
for item in data.iter() {
|
||||
let mut add = Addition::new(doc_name.clone());
|
||||
add.add_field(field_name.clone(), item.clone());
|
||||
mtt.records(add).unwrap();
|
||||
client.records(add).unwrap();
|
||||
}
|
||||
let qry = Query::new(doc_name.clone());
|
||||
let recs = mtt.records(qry).unwrap();
|
||||
let recs = client.records(qry).unwrap();
|
||||
assert_eq!(recs.len(), data.len());
|
||||
for rec in recs.iter() {
|
||||
let result = rec.get(&field_name).unwrap();
|
||||
@@ -45,41 +48,44 @@ fn can_new_documents_be_added() {
|
||||
#[test]
|
||||
fn does_it_error_on_a_bad_document_name() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let doc_name = Name::english("empty");
|
||||
let mut expected = MTTError::new(ErrorID::NameNotFound(doc_name.clone().into()));
|
||||
expected.add_parent(ErrorID::Document(doc_name.clone().into()));
|
||||
let add = Addition::new(doc_name.clone());
|
||||
let result = mtt.records(add).unwrap_err();
|
||||
let result = client.records(add).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_it_error_on_bad_field_name() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let doc_name = Name::english("holder");
|
||||
let field_name = Name::english("missing");
|
||||
let docdef = DocDef::new(doc_name.clone());
|
||||
mtt.create_document(docdef);
|
||||
client.create_document(docdef);
|
||||
let mut add = Addition::new(doc_name.clone());
|
||||
add.add_field(field_name.clone(), "something");
|
||||
let mut expected = MTTError::new(ErrorID::NameNotFound(field_name.clone().into()));
|
||||
expected.add_parent(ErrorID::Field(field_name.clone().into()));
|
||||
expected.add_parent(ErrorID::Document(doc_name.clone().into()));
|
||||
let result = mtt.records(add).unwrap_err();
|
||||
let result = client.records(add).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_it_error_on_bad_field_type() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Uuid]);
|
||||
mtt.create_document(test_doc.get_docdef());
|
||||
client.create_document(test_doc.get_docdef());
|
||||
let mut add = Addition::new(test_doc.get_doc_name().clone());
|
||||
add.add_field(test_doc.get_field_name(0), "something");
|
||||
let mut expected = MTTError::new(ErrorID::FieldTypeExpected(FieldType::Uuid));
|
||||
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().clone().into()));
|
||||
let result = mtt.records(add).unwrap_err();
|
||||
let result = client.records(add).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
@@ -87,13 +93,140 @@ fn does_it_error_on_bad_field_type() {
|
||||
#[ignore = "requires session to store language preference"]
|
||||
fn does_it_error_on_missing_fields() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer, FieldType::Integer]);
|
||||
mtt.create_document(test_doc.get_docdef());
|
||||
client.create_document(test_doc.get_docdef());
|
||||
let mut add = Addition::new(test_doc.get_doc_name().clone());
|
||||
add.add_field(test_doc.get_field_name(0), 1);
|
||||
let mut expected = MTTError::new(ErrorID::FieldInvalidNone);
|
||||
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().clone().into()));
|
||||
let result = mtt.records(add).unwrap_err();
|
||||
let result = client.records(add).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_default_values_be_used() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let ftype = FieldType::StaticString;
|
||||
let test_doc = TestDocument::new(vec![ftype.clone()]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.set_default(&test_doc.get_field_name(0), ftype.clone());
|
||||
client.create_document(docdef);
|
||||
let add = Addition::new(test_doc.get_doc_name().clone());
|
||||
let results = client.records(add).unwrap();
|
||||
let rec = results.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), "".into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_default_values_be_set() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let ftype = FieldType::StaticString;
|
||||
let fdefault = Uuid::new_v4().to_string();
|
||||
let test_doc = TestDocument::new(vec![ftype.clone()]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.set_default(&test_doc.get_field_name(0), fdefault.clone());
|
||||
client.create_document(docdef);
|
||||
let add = Addition::new(test_doc.get_doc_name().clone());
|
||||
let results = client.records(add).unwrap();
|
||||
let rec = results.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
fdefault.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_default_values_be_overwritten() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let ftype = FieldType::StaticString;
|
||||
let fdefault = Uuid::new_v4().to_string();
|
||||
let used = "something";
|
||||
let test_doc = TestDocument::new(vec![ftype.clone()]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.set_default(&test_doc.get_field_name(0), fdefault.clone());
|
||||
client.create_document(docdef);
|
||||
let mut add = Addition::new(test_doc.get_doc_name().clone());
|
||||
add.add_field(test_doc.get_field_name(0), used);
|
||||
let results = client.records(add).unwrap();
|
||||
let rec = results.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), used.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_default_values_be_calculated() {
|
||||
let duration = Duration::from_secs(300);
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::DateTime]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
let mut calc = Calculation::new(Operand::Add);
|
||||
calc.add_value(FieldType::DateTime);
|
||||
calc.add_value(duration.clone());
|
||||
docdef.set_default(&test_doc.get_field_name(0), calc);
|
||||
client.create_document(docdef).unwrap();
|
||||
let add = Addition::new(test_doc.get_doc_name());
|
||||
let start = Utc::now() + duration;
|
||||
let results = client.records(add).unwrap();
|
||||
let end = Utc::now() + duration;
|
||||
let rec = results.iter().last().unwrap();
|
||||
let field = rec.get(&test_doc.get_field_name(0)).unwrap();
|
||||
assert!(field > start.into());
|
||||
assert!(field < end.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn are_unique_indexes_maintained_with_additions() {
|
||||
let data = 1;
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
|
||||
client.create_document(docdef);
|
||||
let mut add = Addition::new(test_doc.get_doc_name());
|
||||
add.add_field(test_doc.get_field_name(0), data.clone());
|
||||
client.records(add.clone()).unwrap();
|
||||
let mut err = MTTError::new(ErrorID::IndexEntryAlreadyExists(data.into()));
|
||||
err.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
err.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let result = client.records(add).unwrap_err();
|
||||
assert_eq!(result.to_string(), err.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_addition_send_on_query_message() {
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer]);
|
||||
client.create_document(test_doc.get_docdef()).unwrap();
|
||||
test_env.register_channel(vec![Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnAddition),
|
||||
)]);
|
||||
let mut add = Addition::new(test_doc.get_doc_name());
|
||||
add.add_field(test_doc.get_field_name(0), 2);
|
||||
let add_result = client.records(add).unwrap();
|
||||
let trigger_result = test_env.get_trigger_records(Action::OnAddition);
|
||||
assert_eq!(trigger_result.len(), add_result.len());
|
||||
assert_eq!(
|
||||
trigger_result
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.get(test_doc.get_field_name(0))
|
||||
.unwrap(),
|
||||
add_result
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.get(test_doc.get_field_name(0))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
173
tests/client_test.rs
Normal file
173
tests/client_test.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
mod support;
|
||||
|
||||
use isolang::Language;
|
||||
use morethantext::{
|
||||
Action, Addition, DocDef, ErrorID, Field, FieldType, Include, MTTError, MoreThanText, Name,
|
||||
Path, Query, TestMoreThanText,
|
||||
};
|
||||
use std::{collections::HashSet, sync::mpsc::RecvTimeoutError};
|
||||
use support::setup_range;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn doc_name() -> Name {
|
||||
Name::english("session")
|
||||
}
|
||||
|
||||
fn lang_name() -> Name {
|
||||
Name::english("language")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn are_session_ids_unique() {
|
||||
let count = 10;
|
||||
let mtt = MoreThanText::new();
|
||||
let mut ids: HashSet<String> = HashSet::new();
|
||||
for _ in 0..count {
|
||||
let client = mtt.client();
|
||||
ids.insert(client.session_id());
|
||||
}
|
||||
assert_eq!(ids.len(), count, "ids = {:?}", ids);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_existing_sessions_be_used() {
|
||||
let mtt = MoreThanText::new();
|
||||
let client1 = mtt.client();
|
||||
let id = client1.session_id();
|
||||
drop(client1);
|
||||
let client2 = mtt.client_with_session(id.clone(), None);
|
||||
assert_eq!(client2.session_id(), id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_expired_session_ids_return_new() {
|
||||
let id = Uuid::new_v4().to_string();
|
||||
let mtt = MoreThanText::new();
|
||||
let client = mtt.client_with_session(id.clone(), None);
|
||||
assert_ne!(client.session_id(), id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_bad_id_string_get_new() {
|
||||
let id = "Not uuid".to_string();
|
||||
let mtt = MoreThanText::new();
|
||||
let client = mtt.client_with_session(id.clone(), None);
|
||||
assert_ne!(client.session_id(), id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_new_clients_set_langauge() {
|
||||
let lang = Language::from_639_1("fr").unwrap();
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mtt = test_env.get_morethantext();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnAddition),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
mtt.client_with_language(lang.clone());
|
||||
let result = test_env.get_trigger_records(Action::OnAddition);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(&lang_name()).unwrap(), lang.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_lanaguage_set_for_expired_session() {
|
||||
let lang = Language::from_639_1("fr").unwrap();
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mtt = test_env.get_morethantext();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnAddition),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
mtt.client_with_session(Uuid::new_v4().to_string(), Some(lang));
|
||||
let result = test_env.get_trigger_records(Action::OnAddition);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(&lang_name()).unwrap(), lang.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_lanaguage_set_for_bad_session() {
|
||||
let lang = Language::from_639_1("de").unwrap();
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mtt = test_env.get_morethantext();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnAddition),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
mtt.client_with_session("bad".to_string(), Some(lang));
|
||||
let result = test_env.get_trigger_records(Action::OnAddition);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(&lang_name()).unwrap(), lang.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_existing_sessions_keep_language_unchanged() {
|
||||
let lang1 = Language::from_639_1("de").unwrap();
|
||||
let lang2 = Language::from_639_1("fr").unwrap();
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mtt = test_env.get_morethantext();
|
||||
let client = mtt.client_with_language(lang1);
|
||||
let id = client.session_id();
|
||||
drop(client);
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnUpdate),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
mtt.client_with_session(id.clone(), Some(lang2));
|
||||
let result = test_env.get_trigger_records(Action::OnUpdate);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(&lang_name()).unwrap(), lang1.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_client_create_a_document() {
|
||||
let doc_name = Name::english("dragon");
|
||||
let docdef = DocDef::new(doc_name);
|
||||
let mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
client.create_document(docdef).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_error_on_create_document() {
|
||||
let doc_name = Name::english("dragon");
|
||||
let docdef = DocDef::new(doc_name.clone());
|
||||
let mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
client.create_document(docdef.clone()).unwrap();
|
||||
let expected = MTTError::new(ErrorID::NameAlreadyExists);
|
||||
let result = client.create_document(docdef).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_perform_client_tasks() {
|
||||
let count = 5;
|
||||
let (test_env, test_doc) = setup_range(count.clone());
|
||||
let qry = Query::new(test_doc.get_doc_name());
|
||||
let client = test_env.get_morethantext().client();
|
||||
let result = client.records(qry).unwrap();
|
||||
assert_eq!(result.len(), count);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_error_on_client_tasks() {
|
||||
let count = 5;
|
||||
let (test_env, test_doc) = setup_range(count.clone());
|
||||
let mut add = Addition::new(test_doc.get_doc_name());
|
||||
add.add_field(test_doc.get_field_name(0), "pie");
|
||||
let mut expected = MTTError::new(ErrorID::FieldTypeExpected(FieldType::Integer));
|
||||
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let client = test_env.get_morethantext().client();
|
||||
let result = client.records(add).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
187
tests/delete_test.rs
Normal file
187
tests/delete_test.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
mod support;
|
||||
|
||||
use morethantext::{
|
||||
Action, Addition, CalcValue, Calculation, Delete, ErrorID, Field, FieldType, Include,
|
||||
IndexType, MTTError, MoreThanText, Name, Operand, Path, Query,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use support::{setup_range, TestDocument};
|
||||
|
||||
#[test]
|
||||
fn can_delete() {
|
||||
let data = "fred";
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::StaticString]);
|
||||
client.create_document(test_doc.get_docdef()).unwrap();
|
||||
test_doc.populate(mtt.clone(), vec![data]);
|
||||
let delete = Delete::new(test_doc.get_doc_name());
|
||||
let result = client.records(delete).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), data.into());
|
||||
let qry = Query::new(test_doc.get_doc_name());
|
||||
let qresult = client.records(qry).unwrap();
|
||||
assert_eq!(qresult.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_delete_specific() {
|
||||
let selected = 1;
|
||||
let (test_env, test_doc) = setup_range(3);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(selected.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
let mut delete = Delete::new(test_doc.get_doc_name());
|
||||
delete.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
let result = client.records(delete).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
selected.into()
|
||||
);
|
||||
let qry = Query::new(test_doc.get_doc_name());
|
||||
let qresult = client.records(qry).unwrap();
|
||||
assert_eq!(qresult.len(), 2);
|
||||
let mut expected: HashSet<Field> = HashSet::new();
|
||||
expected.insert(0.into());
|
||||
expected.insert(2.into());
|
||||
for qrec in qresult.iter() {
|
||||
if !expected.remove(&qrec.get(&test_doc.get_field_name(0)).unwrap()) {
|
||||
assert!(false, "{:?} should not have been stored", qrec);
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
expected.len(),
|
||||
0,
|
||||
"{:?} should have been in the query",
|
||||
expected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_delete_multiple() {
|
||||
let count: i128 = 5;
|
||||
let selected: i128 = 2; // must be less than count and not less than 0
|
||||
let bound = selected + 1;
|
||||
let (test_env, test_doc) = setup_range(count.clone().try_into().unwrap());
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let mut calc = Calculation::new(Operand::GreaterThan);
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
calc.add_value(selected.clone()).unwrap();
|
||||
let mut delete = Delete::new(test_doc.get_doc_name());
|
||||
delete.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
let result = client.records(delete).unwrap();
|
||||
assert_eq!(result.len(), 2);
|
||||
let mut expected: HashSet<Field> = HashSet::new();
|
||||
for i in bound..count {
|
||||
expected.insert(i.into());
|
||||
}
|
||||
for rec in result.iter() {
|
||||
if !expected.remove(&rec.get(&test_doc.get_field_name(0)).unwrap()) {
|
||||
assert!(false, "{:?} should not have been deleted", rec);
|
||||
}
|
||||
}
|
||||
assert_eq!(expected.len(), 0, "{:?} should have been deleted", expected);
|
||||
let qry = Query::new(test_doc.get_doc_name());
|
||||
let qresult = client.records(qry).unwrap();
|
||||
assert_eq!(qresult.len(), bound.clone().try_into().unwrap());
|
||||
let mut qexpected: HashSet<Field> = HashSet::new();
|
||||
for i in 0..bound {
|
||||
qexpected.insert(i.into());
|
||||
}
|
||||
for rec in qresult.iter() {
|
||||
if !qexpected.remove(&rec.get(&test_doc.get_field_name(0)).unwrap()) {
|
||||
assert!(false, "{:?} should not have been deleted", rec);
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
qexpected.len(),
|
||||
0,
|
||||
"{:?} should have been deleted",
|
||||
qexpected
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_delete_error_on_a_bad_query() {
|
||||
let (test_env, test_doc) = setup_range(1);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let bad_name = Name::japanese("正しくない");
|
||||
let mut delete = Delete::new(test_doc.get_doc_name());
|
||||
let mut qry_calc = Calculation::new(Operand::Equal);
|
||||
qry_calc.add_value(0).unwrap();
|
||||
qry_calc
|
||||
.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
delete.get_query_mut().add(bad_name.clone(), qry_calc);
|
||||
let mut expected = MTTError::new(ErrorID::NameNotFound(bad_name.clone().into()));
|
||||
expected.add_parent(ErrorID::Field(bad_name.into()));
|
||||
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let result = client.records(delete).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_delete_update_indexes() {
|
||||
let id = "something";
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::StaticString]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
|
||||
client.create_document(docdef).unwrap();
|
||||
test_doc.populate(mtt.clone(), vec![id]);
|
||||
client
|
||||
.records(Delete::new(test_doc.get_doc_name()))
|
||||
.unwrap();
|
||||
let mut add = Addition::new(test_doc.get_doc_name());
|
||||
add.add_field(test_doc.get_field_name(0), id);
|
||||
let result = client.records(add).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), id.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_delete_send_trigger() {
|
||||
let selected = 2;
|
||||
let (mut test_env, test_doc) = setup_range(3);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
test_env.register_channel(vec![Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnDelete),
|
||||
)]);
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(selected.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
let mut delete = Delete::new(test_doc.get_doc_name());
|
||||
delete.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
let delete_result = client.records(delete).unwrap();
|
||||
let trigger_result = test_env.get_trigger_records(Action::OnDelete);
|
||||
assert_eq!(trigger_result.len(), delete_result.len());
|
||||
assert_eq!(
|
||||
trigger_result
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.get(test_doc.get_field_name(0))
|
||||
.unwrap(),
|
||||
delete_result
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.get(test_doc.get_field_name(0))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
@@ -13,9 +13,10 @@ pub static TIMEOUT: Duration = Duration::from_millis(500);
|
||||
#[test]
|
||||
fn are_errors_produced_for_duplicate_names() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let docdef = DocDef::new(Name::english("duplicate"));
|
||||
mtt.create_document(docdef.clone()).unwrap();
|
||||
match mtt.create_document(docdef) {
|
||||
client.create_document(docdef.clone()).unwrap();
|
||||
match client.create_document(docdef) {
|
||||
Ok(_) => assert!(false, "should have failed"),
|
||||
Err(err) => match err.get_error_ids().back().unwrap() {
|
||||
ErrorID::NameAlreadyExists => {}
|
||||
@@ -27,42 +28,45 @@ fn are_errors_produced_for_duplicate_names() {
|
||||
#[test]
|
||||
fn does_document_respond_to() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let doc_name = random_name();
|
||||
let mut requests: Vec<UserAction> = Vec::new();
|
||||
let mut requests: Vec<ClientAction> = Vec::new();
|
||||
requests.push(Addition::new(doc_name.clone()).into());
|
||||
requests.push(Delete::new(doc_name.clone()).into());
|
||||
requests.push(Query::new(doc_name.clone()).into());
|
||||
requests.push(Update::new(doc_name.clone()).into());
|
||||
let docdef = DocDef::new(doc_name.clone());
|
||||
mtt.create_document(docdef).unwrap();
|
||||
client.create_document(docdef).unwrap();
|
||||
for req in requests.iter() {
|
||||
let result = mtt.records(req.clone()).unwrap();
|
||||
let result = client.records(req.clone()).unwrap();
|
||||
assert_eq!(result.len(), 0, "from {:?}", req);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_document_ignore_other_document_requests() {
|
||||
let mut mtt = TestMoreThanText::new();
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let quiet = Name::english("quiet");
|
||||
let alt = Name::english("alt");
|
||||
mtt.create_document(DocDef::new(quiet.clone())).unwrap();
|
||||
mtt.create_document(DocDef::new(alt.clone())).unwrap();
|
||||
client.create_document(DocDef::new(quiet.clone())).unwrap();
|
||||
client.create_document(DocDef::new(alt.clone())).unwrap();
|
||||
let paths = vec![Path::new(
|
||||
Include::All,
|
||||
Include::Just(quiet.clone().into()),
|
||||
Include::All,
|
||||
)];
|
||||
let rx = mtt.register_channel(paths);
|
||||
let mut requests: Vec<UserAction> = Vec::new();
|
||||
test_env.register_channel(paths);
|
||||
let mut requests: Vec<ClientAction> = Vec::new();
|
||||
requests.push(Addition::new(alt.clone()).into());
|
||||
requests.push(Delete::new(alt.clone()).into());
|
||||
requests.push(Query::new(alt.clone()).into());
|
||||
requests.push(Update::new(alt.clone()).into());
|
||||
for req in requests.iter() {
|
||||
mtt.records(req.clone()).unwrap();
|
||||
client.records(req.clone()).unwrap();
|
||||
}
|
||||
match rx.recv_timeout(TIMEOUT) {
|
||||
match test_env.recv() {
|
||||
Ok(msg) => unreachable!("got {:?} should have timed out", msg),
|
||||
Err(err) => match err {
|
||||
RecvTimeoutError::Timeout => {}
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
mod support;
|
||||
|
||||
use morethantext::{
|
||||
CalcValue, Calculation, DocDef, ErrorID, Field, FieldType, IndexType, MTTError, MoreThanText,
|
||||
Name, Operand, Query,
|
||||
Action, CalcValue, Calculation, DocDef, ErrorID, Field, FieldType, Include, IndexType,
|
||||
MTTError, MoreThanText, Name, Operand, Path, Query, TestMoreThanText,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use support::TestDocument;
|
||||
use support::{setup_range, TestDocument};
|
||||
|
||||
const COUNT: usize = 5;
|
||||
|
||||
fn setup_range() -> (MoreThanText, TestDocument) {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer]);
|
||||
mtt.create_document(test_doc.get_docdef()).unwrap();
|
||||
let mut data: Vec<Vec<i128>> = Vec::new();
|
||||
for i in 0..COUNT {
|
||||
let holder: i128 = i.try_into().unwrap();
|
||||
data.push(vec![holder]);
|
||||
}
|
||||
test_doc.populate_multiple(&mut mtt, data);
|
||||
(mtt, test_doc)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_empty_query_get_all_documents() {
|
||||
let (mut mtt, test_doc) = setup_range();
|
||||
let (test_env, test_doc) = setup_range(COUNT);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
let result = mtt.records(query).unwrap();
|
||||
let result = client.records(query).unwrap();
|
||||
assert_eq!(result.len(), 5, "got {:?}", result);
|
||||
let mut holder: HashSet<Field> = HashSet::new();
|
||||
for rec in result.iter() {
|
||||
@@ -42,7 +31,9 @@ fn does_empty_query_get_all_documents() {
|
||||
|
||||
#[test]
|
||||
fn does_query_pull_specific_information() {
|
||||
let (mut mtt, test_doc) = setup_range();
|
||||
let (test_env, test_doc) = setup_range(COUNT);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let expected = 3;
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(expected.clone()).unwrap();
|
||||
@@ -50,7 +41,7 @@ fn does_query_pull_specific_information() {
|
||||
.unwrap();
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
query.add(test_doc.get_field_name(0), calc);
|
||||
let result = mtt.records(query).unwrap();
|
||||
let result = client.records(query).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
@@ -61,7 +52,9 @@ fn does_query_pull_specific_information() {
|
||||
|
||||
#[test]
|
||||
fn does_query_work_with_less_than() {
|
||||
let (mut mtt, test_doc) = setup_range();
|
||||
let (test_env, test_doc) = setup_range(COUNT);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let expected = 2;
|
||||
let mut calc = Calculation::new(Operand::LessThan);
|
||||
calc.add_value(expected.clone()).unwrap();
|
||||
@@ -69,7 +62,7 @@ fn does_query_work_with_less_than() {
|
||||
.unwrap();
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
query.add(test_doc.get_field_name(0), calc);
|
||||
let result = mtt.records(query).unwrap();
|
||||
let result = client.records(query).unwrap();
|
||||
assert_eq!(result.len(), 2, "got {:?}", result);
|
||||
let mut holder: HashSet<Field> = HashSet::new();
|
||||
for rec in result.iter() {
|
||||
@@ -85,7 +78,9 @@ fn does_query_work_with_less_than() {
|
||||
|
||||
#[test]
|
||||
fn does_query_work_with_less_than_equal() {
|
||||
let (mut mtt, test_doc) = setup_range();
|
||||
let (test_env, test_doc) = setup_range(COUNT);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let expected = 2;
|
||||
let mut calc = Calculation::new(Operand::LessThanEqual);
|
||||
calc.add_value(expected.clone()).unwrap();
|
||||
@@ -93,7 +88,7 @@ fn does_query_work_with_less_than_equal() {
|
||||
.unwrap();
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
query.add(test_doc.get_field_name(0), calc);
|
||||
let result = mtt.records(query).unwrap();
|
||||
let result = client.records(query).unwrap();
|
||||
assert_eq!(result.len(), 3, "got {:?}", result);
|
||||
let mut holder: HashSet<Field> = HashSet::new();
|
||||
for rec in result.iter() {
|
||||
@@ -109,7 +104,9 @@ fn does_query_work_with_less_than_equal() {
|
||||
|
||||
#[test]
|
||||
fn does_query_work_with_greater_than() {
|
||||
let (mut mtt, test_doc) = setup_range();
|
||||
let (test_env, test_doc) = setup_range(COUNT);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let expected = 2;
|
||||
let mut calc = Calculation::new(Operand::GreaterThan);
|
||||
calc.add_value(expected.clone()).unwrap();
|
||||
@@ -117,7 +114,7 @@ fn does_query_work_with_greater_than() {
|
||||
.unwrap();
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
query.add(test_doc.get_field_name(0), calc);
|
||||
let result = mtt.records(query).unwrap();
|
||||
let result = client.records(query).unwrap();
|
||||
assert_eq!(result.len(), 2, "got {:?}", result);
|
||||
let mut holder: HashSet<Field> = HashSet::new();
|
||||
for rec in result.iter() {
|
||||
@@ -132,7 +129,9 @@ fn does_query_work_with_greater_than() {
|
||||
|
||||
#[test]
|
||||
fn does_query_work_with_greater_than_equal() {
|
||||
let (mut mtt, test_doc) = setup_range();
|
||||
let (test_env, test_doc) = setup_range(COUNT);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let expected = 2;
|
||||
let mut calc = Calculation::new(Operand::GreaterThanEqual);
|
||||
calc.add_value(expected.clone()).unwrap();
|
||||
@@ -140,7 +139,7 @@ fn does_query_work_with_greater_than_equal() {
|
||||
.unwrap();
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
query.add(test_doc.get_field_name(0), calc);
|
||||
let result = mtt.records(query).unwrap();
|
||||
let result = client.records(query).unwrap();
|
||||
assert_eq!(result.len(), 3, "got {:?}", result);
|
||||
let mut holder: HashSet<Field> = HashSet::new();
|
||||
for rec in result.iter() {
|
||||
@@ -156,15 +155,16 @@ fn does_query_work_with_greater_than_equal() {
|
||||
#[test]
|
||||
fn can_query_use_multiple_fields() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::StaticString, FieldType::StaticString]);
|
||||
mtt.create_document(test_doc.get_docdef()).unwrap();
|
||||
client.create_document(test_doc.get_docdef()).unwrap();
|
||||
let input = vec![
|
||||
vec!["a", "a"],
|
||||
vec!["a", "b"],
|
||||
vec!["b", "a"],
|
||||
vec!["b", "b"],
|
||||
];
|
||||
test_doc.populate_multiple(&mut mtt, input);
|
||||
test_doc.populate_multiple(mtt.clone(), input);
|
||||
let mut calc1 = Calculation::new(Operand::Equal);
|
||||
calc1.add_value("a").unwrap();
|
||||
calc1
|
||||
@@ -178,7 +178,7 @@ fn can_query_use_multiple_fields() {
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
query.add(test_doc.get_field_name(0), calc1);
|
||||
query.add(test_doc.get_field_name(1), calc2);
|
||||
let results = mtt.records(query).unwrap();
|
||||
let results = client.records(query).unwrap();
|
||||
assert_eq!(results.len(), 1, "got {:?}", results);
|
||||
let rec = results.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), "a".into());
|
||||
@@ -188,6 +188,7 @@ fn can_query_use_multiple_fields() {
|
||||
#[test]
|
||||
fn can_query_use_multiple_indexed_fields() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::StaticString, FieldType::StaticString]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef
|
||||
@@ -196,14 +197,14 @@ fn can_query_use_multiple_indexed_fields() {
|
||||
docdef
|
||||
.add_index(&test_doc.get_field_name(1), IndexType::Index)
|
||||
.unwrap();
|
||||
mtt.create_document(docdef).unwrap();
|
||||
client.create_document(docdef).unwrap();
|
||||
let input = vec![
|
||||
vec!["a", "a"],
|
||||
vec!["a", "b"],
|
||||
vec!["b", "a"],
|
||||
vec!["b", "b"],
|
||||
];
|
||||
test_doc.populate_multiple(&mut mtt, input);
|
||||
test_doc.populate_multiple(mtt.clone(), input);
|
||||
let mut calc1 = Calculation::new(Operand::Equal);
|
||||
calc1.add_value("a").unwrap();
|
||||
calc1
|
||||
@@ -217,7 +218,7 @@ fn can_query_use_multiple_indexed_fields() {
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
query.add(test_doc.get_field_name(0), calc1);
|
||||
query.add(test_doc.get_field_name(1), calc2);
|
||||
let results = mtt.records(query).unwrap();
|
||||
let results = client.records(query).unwrap();
|
||||
assert_eq!(results.len(), 1, "got {:?}", results);
|
||||
let rec = results.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), "a".into());
|
||||
@@ -227,19 +228,20 @@ fn can_query_use_multiple_indexed_fields() {
|
||||
#[test]
|
||||
fn can_query_use_multiple_mixed_index_fields() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::StaticString, FieldType::StaticString]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef
|
||||
.add_index(&test_doc.get_field_name(0), IndexType::Index)
|
||||
.unwrap();
|
||||
mtt.create_document(docdef).unwrap();
|
||||
client.create_document(docdef).unwrap();
|
||||
let input = vec![
|
||||
vec!["a", "a"],
|
||||
vec!["a", "b"],
|
||||
vec!["b", "a"],
|
||||
vec!["b", "b"],
|
||||
];
|
||||
test_doc.populate_multiple(&mut mtt, input);
|
||||
test_doc.populate_multiple(mtt.clone(), input);
|
||||
let mut calc1 = Calculation::new(Operand::Equal);
|
||||
calc1.add_value("a").unwrap();
|
||||
calc1
|
||||
@@ -253,7 +255,7 @@ fn can_query_use_multiple_mixed_index_fields() {
|
||||
let mut query = Query::new(test_doc.get_doc_name());
|
||||
query.add(test_doc.get_field_name(0), calc1);
|
||||
query.add(test_doc.get_field_name(1), calc2);
|
||||
let results = mtt.records(query).unwrap();
|
||||
let results = client.records(query).unwrap();
|
||||
assert_eq!(results.len(), 1, "got {:?}", results);
|
||||
let rec = results.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), "a".into());
|
||||
@@ -263,10 +265,11 @@ fn can_query_use_multiple_mixed_index_fields() {
|
||||
#[test]
|
||||
fn does_it_error_on_bad_field_name() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let doc_name = Name::english("holder");
|
||||
let field_name = Name::english("missing");
|
||||
let docdef = DocDef::new(doc_name.clone());
|
||||
mtt.create_document(docdef);
|
||||
client.create_document(docdef);
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value("a").unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
||||
@@ -276,15 +279,16 @@ fn does_it_error_on_bad_field_name() {
|
||||
let mut expected = MTTError::new(ErrorID::NameNotFound(field_name.clone().into()));
|
||||
expected.add_parent(ErrorID::Field(field_name.clone().into()));
|
||||
expected.add_parent(ErrorID::Document(doc_name.clone().into()));
|
||||
let result = mtt.records(qry).unwrap_err();
|
||||
let result = client.records(qry).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_it_error_on_bad_field_type() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Uuid]);
|
||||
mtt.create_document(test_doc.get_docdef());
|
||||
client.create_document(test_doc.get_docdef());
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value("a").unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::StaticString))
|
||||
@@ -294,6 +298,42 @@ fn does_it_error_on_bad_field_type() {
|
||||
let mut expected = MTTError::new(ErrorID::FieldTypeExpected(FieldType::Uuid));
|
||||
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().clone().into()));
|
||||
let result = mtt.records(qry).unwrap_err();
|
||||
let result = client.records(qry).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_query_send_on_query_message() {
|
||||
let selected = 2;
|
||||
let (mut test_env, test_doc) = setup_range(3);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
test_env.register_channel(vec![Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnQuery),
|
||||
)]);
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(selected.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
let mut qry = Query::new(test_doc.get_doc_name());
|
||||
qry.add(test_doc.get_field_name(0), calc);
|
||||
let query_result = client.records(qry).unwrap();
|
||||
let trigger_result = test_env.get_trigger_records(Action::OnQuery);
|
||||
assert_eq!(trigger_result.len(), query_result.len());
|
||||
assert_eq!(
|
||||
trigger_result
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.get(test_doc.get_field_name(0))
|
||||
.unwrap(),
|
||||
query_result
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.get(test_doc.get_field_name(0))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,39 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use isolang::Language;
|
||||
use morethantext::{
|
||||
action::{Addition, CalcValue, Calculation, Field, FieldType, Operand, Query, Record},
|
||||
MTTError, MoreThanText, Name, TestMoreThanText, Update,
|
||||
Action, ErrorID, Include, MTTError, MoreThanText, Name, Path, TestMoreThanText, Update,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
|
||||
const DELAY: Duration = Duration::from_hours(1);
|
||||
|
||||
fn doc_name() -> Name {
|
||||
Name::english("session")
|
||||
}
|
||||
|
||||
fn id_name() -> Name {
|
||||
Name::english("id")
|
||||
}
|
||||
|
||||
fn expire_name() -> Name {
|
||||
Name::english("expire")
|
||||
}
|
||||
|
||||
fn lang_name() -> Name {
|
||||
Name::english("language")
|
||||
}
|
||||
|
||||
fn get_session(mtt: &mut MoreThanText, id: &Uuid) -> Result<Record, MTTError> {
|
||||
let client = mtt.client();
|
||||
let mut qry = Query::new(doc_name());
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
||||
.unwrap();
|
||||
calc.add_value(id.clone()).unwrap();
|
||||
qry.add(Name::english("id"), calc.clone());
|
||||
match mtt.records(qry) {
|
||||
match client.records(qry) {
|
||||
Ok(data) => Ok(data.iter().last().unwrap()),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
@@ -27,9 +43,9 @@ fn get_session(mtt: &mut MoreThanText, id: &Uuid) -> Result<Record, MTTError> {
|
||||
fn are_session_ids_unique() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let count = 10;
|
||||
let mut result: Vec<Uuid> = Vec::new();
|
||||
let mut result: Vec<String> = Vec::new();
|
||||
for _ in 0..count {
|
||||
let id = mtt.validate_session(None);
|
||||
let id = mtt.client().session_id();
|
||||
assert!(!result.contains(&id), "found {} in {:?}", id, result);
|
||||
result.push(id);
|
||||
}
|
||||
@@ -37,99 +53,184 @@ fn are_session_ids_unique() {
|
||||
|
||||
#[test]
|
||||
fn bad_session_id_returns_new_id() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let id1 = mtt.validate_session(Some("stuff".to_string()));
|
||||
let id2 = mtt.validate_session(Some("stuff".to_string()));
|
||||
assert_ne!(id1, id2);
|
||||
let id = "stuff".to_string();
|
||||
let result = MoreThanText::new().client().session_id();
|
||||
assert_ne!(result, id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creates_new_session_if_bad_or_expired() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let id1 = mtt.validate_session(Some(Uuid::nil().to_string()));
|
||||
let id2 = mtt.validate_session(Some(Uuid::nil().to_string()));
|
||||
assert_ne!(id1, id2);
|
||||
fn creates_new_session_if_missing_or_expired() {
|
||||
let id = Uuid::nil().to_string();
|
||||
let result = MoreThanText::new()
|
||||
.client_with_session(id.clone(), None)
|
||||
.session_id();
|
||||
assert_ne!(result, id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_same_session_id_when_valid() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let id = mtt.validate_session(None);
|
||||
let result = mtt.validate_session(Some(id.to_string()));
|
||||
assert_eq!(result, id);
|
||||
let expected = mtt.client().session_id();
|
||||
let result = mtt.client_with_session(expected.clone(), None).session_id();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_expiration_date_set_in_the_future() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let start_time = Utc::now() + Duration::from_hours(1);
|
||||
let id = mtt.validate_session(None);
|
||||
let end_time = Utc::now() + Duration::from_hours(1);
|
||||
let rec = get_session(&mut mtt, &id).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),
|
||||
};
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnAddition),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
let start_time = Utc::now() + DELAY;
|
||||
let id = test_env.get_morethantext().client();
|
||||
let end_time = Utc::now() + DELAY;
|
||||
let result = test_env.get_trigger_records(Action::OnAddition);
|
||||
let rec = result.iter().last().unwrap();
|
||||
let expire = rec.get(&expire_name()).unwrap();
|
||||
assert!(
|
||||
expire > start_time.into(),
|
||||
"{:?} should be after {:?}",
|
||||
expire,
|
||||
start_time
|
||||
);
|
||||
assert!(
|
||||
expire < end_time.into(),
|
||||
"{:?} should be after {:?}",
|
||||
expire,
|
||||
end_time
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "hangs without completing"]
|
||||
fn are_session_ids_unique_on_update() {
|
||||
fn are_session_ids_unique_in_storage() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let id = mtt.validate_session(None);
|
||||
let client = mtt.client();
|
||||
let id: Uuid = mtt.client().session_id().try_into().unwrap();
|
||||
let mut addition = Addition::new(doc_name());
|
||||
addition.add_field(Name::english("id"), id);
|
||||
match mtt.records(addition) {
|
||||
addition.add_field(id_name(), id);
|
||||
let mut error = MTTError::new(ErrorID::IndexEntryAlreadyExists(id.into()));
|
||||
error.add_parent(ErrorID::Field(id_name().into()));
|
||||
error.add_parent(ErrorID::Document(doc_name().into()));
|
||||
match client.records(addition) {
|
||||
Ok(data) => unreachable!("got {:?} should have been error", data),
|
||||
Err(err) => match err {
|
||||
_ => unreachable!("got {:?}, should have been not unique", err),
|
||||
},
|
||||
Err(err) => assert_eq!(err.to_string(), error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_expire_update_on_query() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let id = mtt.validate_session(None);
|
||||
let start_time = Utc::now() + Duration::from_secs(3600);
|
||||
mtt.validate_session(Some(id.to_string()));
|
||||
let end_time = Utc::now() + Duration::from_secs(3601);
|
||||
let rec = get_session(&mut mtt, &id).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),
|
||||
}
|
||||
fn does_expire_updates_on_query() {
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let id = mtt.client().session_id();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnUpdate),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
let start_time = Utc::now() + DELAY;
|
||||
mtt.client_with_session(id, None);
|
||||
let result = test_env.get_trigger_records(Action::OnUpdate);
|
||||
let rec = result.iter().last().unwrap();
|
||||
let expire = rec.get(&expire_name()).unwrap();
|
||||
assert!(
|
||||
expire > start_time.into(),
|
||||
"{:?} should be after {:?}",
|
||||
expire,
|
||||
start_time
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "failing to update"]
|
||||
fn are_expired_sessions_removed() {
|
||||
let mut mtt = TestMoreThanText::new();
|
||||
let id = mtt.validate_session(None);
|
||||
let mut update = Update::new(doc_name());
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let client = test_env.get_morethantext().client();
|
||||
let id: Uuid = test_env
|
||||
.get_morethantext()
|
||||
.client()
|
||||
.session_id()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
||||
.unwrap();
|
||||
calc.add_value(id.clone()).unwrap();
|
||||
update
|
||||
.get_query_mut()
|
||||
.add(Name::english("id"), calc.clone());
|
||||
let mut update = Update::new(doc_name());
|
||||
let expire = Utc::now() - Duration::from_secs(10);
|
||||
update
|
||||
.get_values_mut()
|
||||
.add_field(Name::english("expire"), expire);
|
||||
mtt.records(update).unwrap();
|
||||
mtt.send_time_pulse();
|
||||
let mut qry = Query::new(doc_name());
|
||||
qry.add(Name::english("id"), calc.clone());
|
||||
let result = mtt.records(qry).unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
update.get_query_mut().add(id_name(), calc.clone());
|
||||
update.get_values_mut().add_field(expire_name(), expire);
|
||||
client.records(update).unwrap();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnDelete),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
test_env.send_time_pulse();
|
||||
let result = test_env.get_trigger_records(Action::OnDelete);
|
||||
assert_eq!(result.len(), 1, "incorrect number of records\n{:?}", result);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(id_name()).unwrap(), id.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_english_the_default_language() {
|
||||
let lang_name = Name::english("language");
|
||||
let lang = Language::from_639_1("en").unwrap();
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnAddition),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
mtt.client();
|
||||
let result = test_env.get_trigger_records(Action::OnAddition);
|
||||
assert_eq!(result.len(), 1, "incorrect number of records");
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(&lang_name).unwrap(), lang.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_language_be_assigned() {
|
||||
let lang_name = Name::english("language");
|
||||
let lang = Language::from_639_1("ja").unwrap();
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnAddition),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
mtt.client_with_language(lang.clone());
|
||||
let result = test_env.get_trigger_records(Action::OnAddition);
|
||||
assert_eq!(result.len(), 1, "incorrect number of records");
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(&lang_name).unwrap(), lang.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_change_language() {
|
||||
let lang_name = Name::english("language");
|
||||
let elang = Language::from_639_1("en").unwrap();
|
||||
let jlang = Language::from_639_1("ja").unwrap();
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let id = mtt.client_with_language(jlang.clone()).session_id();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_name().into()),
|
||||
Include::Just(Action::OnUpdate),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
mtt.client_with_session(id, Some(elang));
|
||||
let result = test_env.get_trigger_records(Action::OnUpdate);
|
||||
assert_eq!(result.len(), 1, "incorrect number of records");
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(&lang_name).unwrap(), jlang.into());
|
||||
}
|
||||
|
||||
@@ -1,27 +1,45 @@
|
||||
use morethantext::{Addition, DocDef, Field, FieldType, MoreThanText, Name};
|
||||
use morethantext::{Addition, DocDef, Field, FieldType, MoreThanText, Name, TestMoreThanText};
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn random_name() -> Name {
|
||||
Name::english(Uuid::new_v4().to_string().as_str())
|
||||
}
|
||||
|
||||
pub fn setup_range(count: usize) -> (TestMoreThanText, TestDocument) {
|
||||
let test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer]);
|
||||
client.create_document(test_doc.get_docdef()).unwrap();
|
||||
let mut data: Vec<Vec<i128>> = Vec::new();
|
||||
for i in 0..count {
|
||||
let holder: i128 = i.try_into().unwrap();
|
||||
data.push(vec![holder]);
|
||||
}
|
||||
test_doc.populate_multiple(mtt.clone(), data);
|
||||
(test_env, test_doc)
|
||||
}
|
||||
|
||||
pub struct TestDocument {
|
||||
docdef: DocDef,
|
||||
doc_name: Name,
|
||||
field_names: Vec<Name>,
|
||||
field_types: Vec<FieldType>,
|
||||
}
|
||||
|
||||
impl TestDocument {
|
||||
pub fn new(fields: Vec<FieldType>) -> Self {
|
||||
let doc_name = random_name();
|
||||
let mut docdef = DocDef::new(doc_name.clone());
|
||||
let mut fnames = Vec::new();
|
||||
for i in 0..fields.len() {
|
||||
let name = Name::english(format!("field{}", i).as_str());
|
||||
docdef.add_field(vec![name.clone()], fields[i].clone());
|
||||
fnames.push(name);
|
||||
}
|
||||
Self {
|
||||
doc_name: random_name(),
|
||||
docdef: docdef,
|
||||
doc_name: doc_name,
|
||||
field_names: fnames,
|
||||
field_types: fields,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,18 +48,14 @@ impl TestDocument {
|
||||
}
|
||||
|
||||
pub fn get_docdef(&self) -> DocDef {
|
||||
let mut output = DocDef::new(self.doc_name.clone());
|
||||
for i in 0..self.field_types.len() {
|
||||
output.add_field(self.field_names[i].clone(), self.field_types[i].clone());
|
||||
}
|
||||
output
|
||||
self.docdef.clone()
|
||||
}
|
||||
|
||||
pub fn get_field_name(&self, position: usize) -> Name {
|
||||
self.field_names[position].clone()
|
||||
}
|
||||
|
||||
pub fn populate<F>(&self, mtt: &mut MoreThanText, data: Vec<F>)
|
||||
pub fn populate<F>(&self, mut mtt: MoreThanText, data: Vec<F>)
|
||||
where
|
||||
F: Into<Field> + Clone,
|
||||
{
|
||||
@@ -49,7 +63,7 @@ impl TestDocument {
|
||||
self.populate_multiple(mtt, wrapper);
|
||||
}
|
||||
|
||||
pub fn populate_multiple<F>(&self, mtt: &mut MoreThanText, data: Vec<Vec<F>>)
|
||||
pub fn populate_multiple<F>(&self, mut mtt: MoreThanText, data: Vec<Vec<F>>)
|
||||
where
|
||||
F: Into<Field> + Clone,
|
||||
{
|
||||
@@ -59,7 +73,7 @@ impl TestDocument {
|
||||
let field: Field = rec[i].clone().into();
|
||||
add.add_field(self.field_names[i].clone(), field);
|
||||
}
|
||||
mtt.records(add).unwrap();
|
||||
mtt.client().records(add).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ fn can_field_types_be_varied() {
|
||||
fn can_record_be_added_to_document() {
|
||||
let count = 5;
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let mut fields: Vec<FieldType> = Vec::new();
|
||||
let mut input: Vec<String> = Vec::new();
|
||||
for i in 0..count {
|
||||
@@ -66,10 +67,10 @@ fn can_record_be_added_to_document() {
|
||||
input.push(i.to_string());
|
||||
}
|
||||
let test_doc = TestDocument::new(fields);
|
||||
mtt.create_document(test_doc.get_docdef());
|
||||
test_doc.populate(&mut mtt, input);
|
||||
client.create_document(test_doc.get_docdef());
|
||||
test_doc.populate(mtt.clone(), input);
|
||||
let query = Query::new(test_doc.get_doc_name());
|
||||
let results = mtt.records(query).unwrap();
|
||||
let results = client.records(query).unwrap();
|
||||
assert_eq!(results.len(), 1);
|
||||
for rec in results.iter() {
|
||||
for i in 0..count {
|
||||
@@ -83,16 +84,17 @@ fn can_record_be_added_to_document() {
|
||||
fn can_document_be_populated_with_multiple_records() {
|
||||
let count = 5;
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer]);
|
||||
mtt.create_document(test_doc.get_docdef());
|
||||
client.create_document(test_doc.get_docdef());
|
||||
let mut data: Vec<Vec<i128>> = Vec::new();
|
||||
for i in 0..count {
|
||||
let item: i128 = i.try_into().unwrap();
|
||||
let holder: Vec<i128> = vec![item];
|
||||
data.push(holder);
|
||||
}
|
||||
test_doc.populate_multiple(&mut mtt, data);
|
||||
test_doc.populate_multiple(mtt.clone(), data);
|
||||
let query = Query::new(test_doc.get_doc_name());
|
||||
let results = mtt.records(query).unwrap();
|
||||
let results = client.records(query).unwrap();
|
||||
assert_eq!(results.len(), count);
|
||||
}
|
||||
|
||||
232
tests/trigger_test.rs
Normal file
232
tests/trigger_test.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
mod support;
|
||||
|
||||
use morethantext::{
|
||||
Action, Addition, CalcValue, Calculation, Delete, DocDef, DocFuncType, FieldType, Include,
|
||||
MoreThanText, Name, Operand, Path, Query, TestMoreThanText, Update,
|
||||
};
|
||||
use support::TestDocument;
|
||||
|
||||
#[test]
|
||||
fn can_a_trigger_cause_an_update() {
|
||||
let data0 = 0;
|
||||
let data1 = 0;
|
||||
let data1_expected = 1;
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer, FieldType::Integer]);
|
||||
let mut calc = Calculation::new(Operand::Add);
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
calc.add_value(1).unwrap();
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(1), calc);
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnQuery),
|
||||
);
|
||||
let function = DocFuncType::ExistingQuery(update.into());
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.add_route(path, function);
|
||||
client.create_document(docdef);
|
||||
test_doc.populate(mtt.clone(), vec![data0.clone(), data1.clone()]);
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnUpdate),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
let first_qry = client.records(Query::new(test_doc.get_doc_name())).unwrap();
|
||||
assert_eq!(first_qry.len(), 1);
|
||||
let first_rec = first_qry.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
first_rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
data0.clone().into()
|
||||
);
|
||||
assert_eq!(
|
||||
first_rec.get(test_doc.get_field_name(1)).unwrap(),
|
||||
data1.clone().into()
|
||||
);
|
||||
let result = test_env.get_trigger_records(Action::OnUpdate);
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
data0.clone().into()
|
||||
);
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(1)).unwrap(),
|
||||
data1_expected.clone().into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_trigger_update_specific_record() {
|
||||
let count = 3;
|
||||
let selected = 1; // must be greater than or equal to 0 and less than count
|
||||
let initial_data = 0;
|
||||
let expected = 1;
|
||||
let mut input: Vec<Vec<i128>> = Vec::new();
|
||||
for i in 0..count {
|
||||
input.push(vec![i.clone(), initial_data.clone()]);
|
||||
}
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer, FieldType::Integer]);
|
||||
let mut calc = Calculation::new(Operand::Add);
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
calc.add_value(1).unwrap();
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(1), calc);
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnQuery),
|
||||
);
|
||||
let function = DocFuncType::ExistingQuery(update.into());
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.add_route(path, function);
|
||||
client.create_document(docdef);
|
||||
test_doc.populate_multiple(mtt.clone(), input);
|
||||
let mut qry_calc = Calculation::new(Operand::Equal);
|
||||
qry_calc
|
||||
.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
qry_calc.add_value(selected.clone()).unwrap();
|
||||
let mut qry = Query::new(test_doc.get_doc_name());
|
||||
qry.add(test_doc.get_field_name(0), qry_calc);
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnUpdate),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
let first_result = client.records(qry).unwrap();
|
||||
assert_eq!(first_result.len(), 1);
|
||||
let first_rec = first_result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
first_rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
selected.clone().into()
|
||||
);
|
||||
assert_eq!(
|
||||
first_rec.get(test_doc.get_field_name(1)).unwrap(),
|
||||
initial_data.clone().into()
|
||||
);
|
||||
let result = test_env.get_trigger_records(Action::OnUpdate);
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
selected.clone().into()
|
||||
);
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(1)).unwrap(),
|
||||
expected.clone().into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_a_trigger_from_another_document_be_used() {
|
||||
let count = 3;
|
||||
let selected = 1; // must be greater than or equal to 0 and less than count
|
||||
let mut input: Vec<Vec<i128>> = Vec::new();
|
||||
for i in 0..count {
|
||||
input.push(vec![i]);
|
||||
}
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer]);
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
calc.add_value(1).unwrap();
|
||||
let mut delete = Delete::new(test_doc.get_doc_name());
|
||||
delete.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(Name::english("clock").into()),
|
||||
Include::Just(Action::OnUpdate),
|
||||
);
|
||||
let function = DocFuncType::Trigger(delete.into());
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.add_route(path, function);
|
||||
client.create_document(docdef);
|
||||
test_doc.populate_multiple(mtt.clone(), input);
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnDelete),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
test_env.send_time_pulse();
|
||||
let result = test_env.get_trigger_records(Action::OnDelete);
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
selected.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_triggers_work_with_multiple_languages() {
|
||||
let initial_data = 1;
|
||||
let doc_names = vec![Name::english("test"), Name::japanese("テスト")];
|
||||
let field_names = vec![Name::english("something"), Name::japanese("何か")];
|
||||
let mut docdef = DocDef::with_names(doc_names.clone());
|
||||
docdef.add_field(field_names.clone(), FieldType::Integer);
|
||||
let mut calc = Calculation::new(Operand::Add);
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
calc.add_value(1).unwrap();
|
||||
let mut update = Update::new(doc_names[0].clone());
|
||||
update.add_field(field_names[0].clone(), calc);
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_names[0].clone().into()),
|
||||
Include::Just(Action::OnQuery),
|
||||
);
|
||||
let function = DocFuncType::ExistingQuery(update.into());
|
||||
docdef.add_route(path, function);
|
||||
let mut test_env = TestMoreThanText::new();
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
client.create_document(docdef).unwrap();
|
||||
let mut data = Addition::new(doc_names[0].clone());
|
||||
data.add_field(field_names[0].clone(), initial_data.clone());
|
||||
client.records(data).unwrap();
|
||||
let mut qry_calc = Calculation::new(Operand::LessThan);
|
||||
qry_calc.add_value(0).unwrap();
|
||||
qry_calc
|
||||
.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
let path = Path::new(
|
||||
Include::All,
|
||||
Include::Just(doc_names[0].clone().into()),
|
||||
Include::Just(Action::OnUpdate),
|
||||
);
|
||||
test_env.register_channel(vec![path]);
|
||||
for i in 0..doc_names.len() {
|
||||
let holder: i128 = i.clone().try_into().unwrap();
|
||||
let mut query = Query::new(doc_names[i].clone());
|
||||
query.add(field_names[i].clone(), qry_calc.clone());
|
||||
let qry_result = client.records(query.clone()).unwrap();
|
||||
assert_eq!(qry_result.len(), 1, "got {:?} from {:?}", qry_result, query);
|
||||
let qry_rec = qry_result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
qry_rec.get(field_names[i].clone()).unwrap(),
|
||||
(initial_data + holder).into()
|
||||
);
|
||||
let onupdate = test_env.get_trigger_records(Action::OnUpdate);
|
||||
assert_eq!(onupdate.len(), 1, "got {:?}", onupdate);
|
||||
let on_rec = onupdate.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
on_rec.get(field_names[i].clone()).unwrap(),
|
||||
(initial_data + holder + 1).into()
|
||||
);
|
||||
}
|
||||
}
|
||||
341
tests/update_test.rs
Normal file
341
tests/update_test.rs
Normal file
@@ -0,0 +1,341 @@
|
||||
mod support;
|
||||
|
||||
use morethantext::{
|
||||
Action, Addition, CalcValue, Calculation, ErrorID, Field, FieldType, Include, IndexType,
|
||||
MTTError, MoreThanText, Name, Operand, Path, Query, Records, Update,
|
||||
};
|
||||
use std::collections::HashSet;
|
||||
use support::{setup_range, TestDocument};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[test]
|
||||
fn is_it_possible_to_update_nothing() {
|
||||
let count = 3;
|
||||
let outside: i128 = (count + 2).try_into().unwrap();
|
||||
let (test_env, test_doc) = setup_range(count);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(0), 20);
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(outside.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
update.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
let result = client.records(update).unwrap();
|
||||
assert_eq!(result.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_it_update_information() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Uuid, FieldType::StaticString]);
|
||||
client.create_document(test_doc.get_docdef());
|
||||
let field0 = Uuid::new_v4();
|
||||
let field1 = "new";
|
||||
let mut input: Vec<Field> = Vec::new();
|
||||
input.push(field0.clone().into());
|
||||
input.push("old".into());
|
||||
test_doc.populate(mtt.clone(), input);
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(1), field1);
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(field0.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Uuid))
|
||||
.unwrap();
|
||||
update.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
let tester = |result: Records| {
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
field0.clone().into()
|
||||
);
|
||||
assert_eq!(rec.get(test_doc.get_field_name(1)).unwrap(), field1.into());
|
||||
};
|
||||
tester(client.records(update).unwrap());
|
||||
tester(client.records(Query::new(test_doc.get_doc_name())).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn are_the_updates_limited_to_the_queried() {
|
||||
let count = 3;
|
||||
let choice = 1;
|
||||
let changed = 20;
|
||||
let (test_env, test_doc) = setup_range(count);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(0), changed.clone());
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(choice.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
update.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
let result = client.records(update).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), changed.into());
|
||||
let mut expected: HashSet<Field> = HashSet::new();
|
||||
expected.insert(0.into());
|
||||
expected.insert(2.into());
|
||||
expected.insert(20.into());
|
||||
let docs = client.records(Query::new(test_doc.get_doc_name())).unwrap();
|
||||
for doc_data in docs.iter() {
|
||||
let id = doc_data.get(test_doc.get_field_name(0)).unwrap();
|
||||
expected.remove(&id);
|
||||
}
|
||||
assert_eq!(
|
||||
expected.len(),
|
||||
0,
|
||||
"missed {:?} from docs: {:?}",
|
||||
expected,
|
||||
docs
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_multiple_documents_be_update() {
|
||||
let count = 3;
|
||||
let choice = 1;
|
||||
let (test_env, test_doc) = setup_range(count);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let mut add_up = Calculation::new(Operand::Add);
|
||||
add_up
|
||||
.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
add_up.add_value(20);
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(0), add_up);
|
||||
let mut calc = Calculation::new(Operand::GreaterThanEqual);
|
||||
calc.add_value(choice.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
update.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
let result = client.records(update).unwrap();
|
||||
assert_eq!(result.len(), 2);
|
||||
for rec in result.iter() {
|
||||
assert!(rec.get(test_doc.get_field_name(0)).unwrap() > 10.into());
|
||||
}
|
||||
let mut expected: HashSet<Field> = HashSet::new();
|
||||
expected.insert(2.into());
|
||||
expected.insert(20.into());
|
||||
expected.insert(21.into());
|
||||
let docs = client.records(Query::new(test_doc.get_doc_name())).unwrap();
|
||||
for doc_data in docs.iter() {
|
||||
let id = doc_data.get(test_doc.get_field_name(0)).unwrap();
|
||||
expected.remove(&id);
|
||||
}
|
||||
assert_eq!(
|
||||
expected.len(),
|
||||
0,
|
||||
"missed {:?} from docs: {:?}",
|
||||
expected,
|
||||
docs
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_update_error_on_a_bad_query() {
|
||||
let (test_env, test_doc) = setup_range(1);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let bad_name = Name::japanese("正しくない");
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
let mut qry_calc = Calculation::new(Operand::Equal);
|
||||
qry_calc.add_value(0).unwrap();
|
||||
qry_calc
|
||||
.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
update.get_query_mut().add(bad_name.clone(), qry_calc);
|
||||
update.add_field(test_doc.get_field_name(0), 5);
|
||||
let mut expected = MTTError::new(ErrorID::NameNotFound(bad_name.clone().into()));
|
||||
expected.add_parent(ErrorID::Field(bad_name.into()));
|
||||
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let result = client.records(update).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_update_error_on_a_bad_field_name() {
|
||||
let (test_env, test_doc) = setup_range(1);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let bad_name = Name::japanese("正しくない");
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
let mut qry_calc = Calculation::new(Operand::Equal);
|
||||
qry_calc.add_value(0).unwrap();
|
||||
qry_calc
|
||||
.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
update
|
||||
.get_query_mut()
|
||||
.add(test_doc.get_field_name(0), qry_calc);
|
||||
update.add_field(bad_name.clone(), 5);
|
||||
let mut expected = MTTError::new(ErrorID::NameNotFound(bad_name.clone().into()));
|
||||
expected.add_parent(ErrorID::Field(bad_name.into()));
|
||||
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let result = client.records(update).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_update_error_on_a_bad_field_type() {
|
||||
let (test_env, test_doc) = setup_range(1);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
let mut qry_calc = Calculation::new(Operand::Equal);
|
||||
qry_calc.add_value(0).unwrap();
|
||||
qry_calc
|
||||
.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
update
|
||||
.get_query_mut()
|
||||
.add(test_doc.get_field_name(0), qry_calc);
|
||||
update.add_field(test_doc.get_field_name(0), "wrong type");
|
||||
let mut expected = MTTError::new(ErrorID::FieldTypeExpected(FieldType::Integer));
|
||||
expected.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
expected.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let result = client.records(update).unwrap_err();
|
||||
assert_eq!(result.to_string(), expected.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_update_maintain_unique_index() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::StaticString]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
|
||||
client.create_document(docdef);
|
||||
let old_data = "old";
|
||||
let new_data = "new";
|
||||
test_doc.populate(mtt.clone(), vec![old_data]);
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(0), new_data);
|
||||
client.records(update).unwrap();
|
||||
let result = client.records(Query::new(test_doc.get_doc_name())).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let rec = result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
new_data.into()
|
||||
);
|
||||
let mut add_new = Addition::new(test_doc.get_doc_name());
|
||||
add_new.add_field(test_doc.get_field_name(0), new_data);
|
||||
let mut err = MTTError::new(ErrorID::IndexEntryAlreadyExists(new_data.into()));
|
||||
err.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
err.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let error = client.records(add_new).unwrap_err();
|
||||
assert_eq!(error.to_string(), err.to_string());
|
||||
let mut add_old = Addition::new(test_doc.get_doc_name());
|
||||
add_old.add_field(test_doc.get_field_name(0), old_data);
|
||||
let result = client.records(add_old).unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let add_rec = result.iter().last().unwrap();
|
||||
assert_eq!(
|
||||
add_rec.get(test_doc.get_field_name(0)).unwrap(),
|
||||
old_data.into()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_index_remain_unchanged_on_update_failure() {
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::StaticString, FieldType::StaticString]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
|
||||
client.create_document(docdef);
|
||||
let id = "one";
|
||||
test_doc.populate(mtt.clone(), vec!["one", "data"]);
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(0), "two");
|
||||
update.add_field(test_doc.get_field_name(1), 2);
|
||||
client.records(update).unwrap_err();
|
||||
let mut add = Addition::new(test_doc.get_doc_name());
|
||||
add.add_field(test_doc.get_field_name(0), id);
|
||||
add.add_field(test_doc.get_field_name(1), "something");
|
||||
let mut err = MTTError::new(ErrorID::IndexEntryAlreadyExists(id.into()));
|
||||
err.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
err.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let error = client.records(add).unwrap_err();
|
||||
assert_eq!(error.to_string(), err.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires session to hold language information"]
|
||||
fn does_update_error_when_it_overrides_unique_index() {
|
||||
let count = 3;
|
||||
let mut mtt = MoreThanText::new();
|
||||
let client = mtt.client();
|
||||
let test_doc = TestDocument::new(vec![FieldType::Integer]);
|
||||
let mut docdef = test_doc.get_docdef();
|
||||
docdef.add_index(&test_doc.get_field_name(0), IndexType::Unique);
|
||||
client.create_document(docdef);
|
||||
let mut input: Vec<Vec<i128>> = Vec::new();
|
||||
for i in 0..count {
|
||||
input.push(vec![i]);
|
||||
}
|
||||
test_doc.populate_multiple(mtt.clone(), input);
|
||||
let new_data = 5;
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.add_field(test_doc.get_field_name(0), new_data.clone());
|
||||
let mut err = MTTError::new(ErrorID::IndexEntryAlreadyExists(new_data.into()));
|
||||
err.add_parent(ErrorID::Field(test_doc.get_field_name(0).into()));
|
||||
err.add_parent(ErrorID::Document(test_doc.get_doc_name().into()));
|
||||
let result = client.records(update).unwrap_err();
|
||||
assert_eq!(result.to_string(), err.to_string());
|
||||
for i in 0..count {
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(i.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
let mut qry = Query::new(test_doc.get_doc_name());
|
||||
qry.add(test_doc.get_field_name(0), calc);
|
||||
let holder = client.records(qry).unwrap();
|
||||
assert_eq!(holder.len(), 1);
|
||||
let rec = holder.iter().last().unwrap();
|
||||
assert_eq!(rec.get(test_doc.get_field_name(0)).unwrap(), i.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_update_send_on_update_message() {
|
||||
let selected = 2;
|
||||
let (mut test_env, test_doc) = setup_range(3);
|
||||
let mut mtt = test_env.get_morethantext();
|
||||
let client = mtt.client();
|
||||
test_env.register_channel(vec![Path::new(
|
||||
Include::All,
|
||||
Include::Just(test_doc.get_doc_name().into()),
|
||||
Include::Just(Action::OnUpdate),
|
||||
)]);
|
||||
let mut calc = Calculation::new(Operand::Equal);
|
||||
calc.add_value(selected.clone()).unwrap();
|
||||
calc.add_value(CalcValue::Existing(FieldType::Integer))
|
||||
.unwrap();
|
||||
let mut update = Update::new(test_doc.get_doc_name());
|
||||
update.get_query_mut().add(test_doc.get_field_name(0), calc);
|
||||
update.add_field(test_doc.get_field_name(0), 5);
|
||||
let update_result = client.records(update).unwrap();
|
||||
let trigger_result = test_env.get_trigger_records(Action::OnUpdate);
|
||||
assert_eq!(trigger_result.len(), update_result.len());
|
||||
assert_eq!(
|
||||
trigger_result
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.get(test_doc.get_field_name(0))
|
||||
.unwrap(),
|
||||
update_result
|
||||
.iter()
|
||||
.last()
|
||||
.unwrap()
|
||||
.get(test_doc.get_field_name(0))
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user