Rename system-query to system-wasm

This commit is contained in:
2023-09-22 20:28:56 +01:00
parent 200727635c
commit d3c0b866f2
24 changed files with 32 additions and 710 deletions

View File

@ -0,0 +1,190 @@
use crate::FlatReqFilter;
use itertools::Itertools;
pub fn diff_filter(prev: &Vec<FlatReqFilter>, next: &Vec<FlatReqFilter>) -> Vec<FlatReqFilter> {
let mut added: Vec<FlatReqFilter> = vec![];
for n in next.iter() {
if !prev.iter().contains(&n) {
added.push(n.clone())
}
}
added
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_diff_same() {
let prev = vec![FlatReqFilter {
id: Some("a".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
}];
let next = vec![FlatReqFilter {
id: Some("a".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
}];
let result = diff_filter(&prev, &next);
assert_eq!(result, vec![])
}
#[test]
fn simple_diff_add() {
let prev = vec![FlatReqFilter {
id: Some("a".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
}];
let next = vec![
FlatReqFilter {
id: Some("a".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
FlatReqFilter {
id: Some("b".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
];
let result = diff_filter(&prev, &next);
assert_eq!(
result,
vec![FlatReqFilter {
id: Some("b".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
}]
)
}
#[test]
fn simple_diff_replace() {
let prev = vec![FlatReqFilter {
id: Some("a".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
}];
let next = vec![FlatReqFilter {
id: Some("b".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
}];
let result = diff_filter(&prev, &next);
assert_eq!(
result,
vec![FlatReqFilter {
id: Some("b".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
}]
)
}
}

View File

@ -0,0 +1,818 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[cfg(test)]
use std::fmt::Debug;
use std::hash::Hash;
use itertools::Itertools;
#[derive(Clone)]
enum StringOrNumberEntry<'a> {
String((&'static str, &'a String)),
Number((&'static str, &'a i32)),
}
#[derive(PartialEq, Clone, Serialize, Deserialize)]
pub struct ReqFilter {
#[serde(rename = "ids", skip_serializing_if = "Option::is_none")]
pub ids: Option<HashSet<String>>,
#[serde(rename = "authors", skip_serializing_if = "Option::is_none")]
pub authors: Option<HashSet<String>>,
#[serde(rename = "kinds", skip_serializing_if = "Option::is_none")]
pub kinds: Option<HashSet<i32>>,
#[serde(rename = "#e", skip_serializing_if = "Option::is_none")]
pub e_tag: Option<HashSet<String>>,
#[serde(rename = "#p", skip_serializing_if = "Option::is_none")]
pub p_tag: Option<HashSet<String>>,
#[serde(rename = "#t", skip_serializing_if = "Option::is_none")]
pub t_tag: Option<HashSet<String>>,
#[serde(rename = "#d", skip_serializing_if = "Option::is_none")]
pub d_tag: Option<HashSet<String>>,
#[serde(rename = "#r", skip_serializing_if = "Option::is_none")]
pub r_tag: Option<HashSet<String>>,
#[serde(rename = "#a", skip_serializing_if = "Option::is_none")]
pub a_tag: Option<HashSet<String>>,
#[serde(rename = "#g", skip_serializing_if = "Option::is_none")]
pub g_tag: Option<HashSet<String>>,
#[serde(rename = "search", skip_serializing_if = "Option::is_none")]
pub search: Option<HashSet<String>>,
#[serde(rename = "since", skip_serializing_if = "Option::is_none")]
pub since: Option<i32>,
#[serde(rename = "until", skip_serializing_if = "Option::is_none")]
pub until: Option<i32>,
#[serde(rename = "limit", skip_serializing_if = "Option::is_none")]
pub limit: Option<i32>,
}
#[cfg(test)]
impl Debug for ReqFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&serde_json::to_string(self).unwrap().to_owned())
}
}
#[derive(PartialEq, PartialOrd, Clone, Serialize, Deserialize)]
pub struct FlatReqFilter {
#[serde(rename = "ids", skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "authors", skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(rename = "kinds", skip_serializing_if = "Option::is_none")]
pub kind: Option<i32>,
#[serde(rename = "#e", skip_serializing_if = "Option::is_none")]
pub e_tag: Option<String>,
#[serde(rename = "#p", skip_serializing_if = "Option::is_none")]
pub p_tag: Option<String>,
#[serde(rename = "#t", skip_serializing_if = "Option::is_none")]
pub t_tag: Option<String>,
#[serde(rename = "#d", skip_serializing_if = "Option::is_none")]
pub d_tag: Option<String>,
#[serde(rename = "#r", skip_serializing_if = "Option::is_none")]
pub r_tag: Option<String>,
#[serde(rename = "#a", skip_serializing_if = "Option::is_none")]
pub a_tag: Option<String>,
#[serde(rename = "#g", skip_serializing_if = "Option::is_none")]
pub g_tag: Option<String>,
#[serde(rename = "search", skip_serializing_if = "Option::is_none")]
pub search: Option<String>,
#[serde(rename = "since", skip_serializing_if = "Option::is_none")]
pub since: Option<i32>,
#[serde(rename = "until", skip_serializing_if = "Option::is_none")]
pub until: Option<i32>,
#[serde(rename = "limit", skip_serializing_if = "Option::is_none")]
pub limit: Option<i32>,
}
#[cfg(test)]
impl Debug for FlatReqFilter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&serde_json::to_string(self).unwrap().to_owned())
}
}
pub trait Distance {
/// Calculate the distance in terms of similarity for merging
///
/// The goal of this function is to find 2 filters which are very similar where
/// one filter may have a single property change like so:
///
/// ```javascript
/// const a = { "kinds": 1, "authors": "a", "since": 99 };
/// const b = { "kinds": 1, "authors": "b", "since": 99 };
/// ```
/// In this case these 2 filters could be merged because their distance is `1`
/// ```javascript
/// const result = { "kinds": [1], "authors": ["a", "b"], "since": 99 };
/// ```
fn distance(&self, other: &Self) -> u32;
}
pub trait CanMerge {
fn can_merge(&self, other: &Self) -> bool;
}
impl Distance for FlatReqFilter {
fn distance(&self, b: &Self) -> u32 {
let mut ret = 0u32;
ret += prop_dist(&self.id, &b.id);
ret += prop_dist(&self.kind, &b.kind);
ret += prop_dist(&self.author, &b.author);
ret += prop_dist(&self.e_tag, &b.e_tag);
ret += prop_dist(&self.p_tag, &b.p_tag);
ret += prop_dist(&self.d_tag, &b.d_tag);
ret += prop_dist(&self.r_tag, &b.r_tag);
ret += prop_dist(&self.t_tag, &b.t_tag);
ret += prop_dist(&self.search, &b.search);
ret
}
}
impl CanMerge for FlatReqFilter {
fn can_merge(&self, other: &Self) -> bool {
if self.since != other.since
|| self.until != other.until
|| self.limit != other.limit
|| self.search != other.search
{
return false;
}
self.distance(other) <= 1
}
}
impl From<Vec<&FlatReqFilter>> for ReqFilter {
fn from(value: Vec<&FlatReqFilter>) -> Self {
let ret = ReqFilter {
ids: None,
authors: None,
kinds: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
};
value.iter().fold(ret, |mut acc, x| {
array_prop_append(&x.id, &mut acc.ids);
array_prop_append(&x.author, &mut acc.authors);
array_prop_append(&x.kind, &mut acc.kinds);
array_prop_append(&x.e_tag, &mut acc.e_tag);
array_prop_append(&x.p_tag, &mut acc.p_tag);
array_prop_append(&x.t_tag, &mut acc.t_tag);
array_prop_append(&x.d_tag, &mut acc.d_tag);
array_prop_append(&x.r_tag, &mut acc.r_tag);
array_prop_append(&x.a_tag, &mut acc.a_tag);
array_prop_append(&x.g_tag, &mut acc.g_tag);
array_prop_append(&x.search, &mut acc.search);
acc.since = x.since;
acc.until = x.until;
acc.limit = x.limit;
acc
})
}
}
impl From<Vec<&ReqFilter>> for ReqFilter {
fn from(value: Vec<&ReqFilter>) -> Self {
let ret = ReqFilter {
ids: None,
authors: None,
kinds: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
};
value.iter().fold(ret, |mut acc, x| {
array_prop_append_vec(&x.ids, &mut acc.ids);
array_prop_append_vec(&x.authors, &mut acc.authors);
array_prop_append_vec(&x.kinds, &mut acc.kinds);
array_prop_append_vec(&x.e_tag, &mut acc.e_tag);
array_prop_append_vec(&x.p_tag, &mut acc.p_tag);
array_prop_append_vec(&x.t_tag, &mut acc.t_tag);
array_prop_append_vec(&x.d_tag, &mut acc.d_tag);
array_prop_append_vec(&x.r_tag, &mut acc.r_tag);
array_prop_append_vec(&x.a_tag, &mut acc.a_tag);
array_prop_append_vec(&x.g_tag, &mut acc.g_tag);
array_prop_append_vec(&x.search, &mut acc.search);
acc.since = x.since;
acc.until = x.until;
acc.limit = x.limit;
acc
})
}
}
impl Into<Vec<FlatReqFilter>> for &ReqFilter {
fn into(self) -> Vec<FlatReqFilter> {
let mut ret: Vec<FlatReqFilter> = Vec::new();
let mut inputs: Vec<Vec<StringOrNumberEntry>> = vec![];
if let Some(ids) = &self.ids {
let t_ids = ids
.iter()
.map(|z| StringOrNumberEntry::String(("id", z)))
.collect();
inputs.push(t_ids);
}
if let Some(authors) = &self.authors {
let t_ids = authors
.iter()
.map(|z| StringOrNumberEntry::String(("author", z)))
.collect();
inputs.push(t_ids);
}
if let Some(kinds) = &self.kinds {
let t_ids = kinds
.iter()
.map(|z| StringOrNumberEntry::Number(("kind", z)))
.collect();
inputs.push(t_ids);
}
if let Some(e_tags) = &self.e_tag {
let t_ids = e_tags
.iter()
.map(|z| StringOrNumberEntry::String(("e_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(p_tags) = &self.p_tag {
let t_ids = p_tags
.iter()
.map(|z| StringOrNumberEntry::String(("p_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(d_tags) = &self.d_tag {
let t_ids = d_tags
.iter()
.map(|z| StringOrNumberEntry::String(("d_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(t_tags) = &self.t_tag {
let t_ids = t_tags
.iter()
.map(|z| StringOrNumberEntry::String(("t_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(r_tags) = &self.r_tag {
let t_ids = r_tags
.iter()
.map(|z| StringOrNumberEntry::String(("r_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(a_tags) = &self.a_tag {
let t_ids = a_tags
.iter()
.map(|z| StringOrNumberEntry::String(("a_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(g_tags) = &self.g_tag {
let t_ids = g_tags
.iter()
.map(|z| StringOrNumberEntry::String(("g_tag", z)))
.collect();
inputs.push(t_ids);
}
if let Some(search) = &self.search {
let t_ids = search
.iter()
.map(|z| StringOrNumberEntry::String(("search", z)))
.collect();
inputs.push(t_ids);
}
for p in inputs.iter().multi_cartesian_product() {
ret.push(FlatReqFilter {
id: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("id") {
return Some((*v).to_string());
}
}
None
}),
author: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("author") {
return Some((*v).to_string());
}
}
None
}),
kind: p.iter().find_map(|q| {
if let StringOrNumberEntry::Number((k, v)) = q {
if (*k).eq("kind") {
return Some((*v).clone());
}
}
None
}),
e_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("e_tag") {
return Some((*v).to_string());
}
}
None
}),
p_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("p_tag") {
return Some((*v).to_string());
}
}
None
}),
t_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("t_tag") {
return Some((*v).to_string());
}
}
None
}),
d_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("d_tag") {
return Some((*v).to_string());
}
}
None
}),
r_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("r_tag") {
return Some((*v).to_string());
}
}
None
}),
a_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("a_tag") {
return Some((*v).to_string());
}
}
None
}),
g_tag: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("g_tag") {
return Some((*v).to_string());
}
}
None
}),
search: p.iter().find_map(|q| {
if let StringOrNumberEntry::String((k, v)) = q {
if (*k).eq("search") {
return Some((*v).to_string());
}
}
None
}),
since: self.since,
until: self.until,
limit: self.limit,
})
}
ret
}
}
impl Distance for ReqFilter {
fn distance(&self, b: &Self) -> u32 {
let mut ret = 0u32;
ret += prop_dist_vec(&self.ids, &b.ids);
ret += prop_dist_vec(&self.kinds, &b.kinds);
ret += prop_dist_vec(&self.authors, &b.authors);
ret += prop_dist_vec(&self.e_tag, &b.e_tag);
ret += prop_dist_vec(&self.p_tag, &b.p_tag);
ret += prop_dist_vec(&self.d_tag, &b.d_tag);
ret += prop_dist_vec(&self.r_tag, &b.r_tag);
ret += prop_dist_vec(&self.t_tag, &b.t_tag);
ret += prop_dist_vec(&self.a_tag, &b.a_tag);
ret += prop_dist_vec(&self.search, &b.search);
ret
}
}
impl CanMerge for ReqFilter {
fn can_merge(&self, other: &Self) -> bool {
if self.since != other.since
|| self.until != other.until
|| self.limit != other.limit
|| self.search != other.search
{
return false;
}
self.distance(other) <= 1
}
}
#[inline(always)]
fn prop_dist<T: Eq>(a: &Option<T>, b: &Option<T>) -> u32 {
if (a.is_some() && b.is_none()) || (a.is_none() && b.is_some()) {
return 10;
} else if a.is_some() && a != b {
return 1;
}
0
}
#[inline(always)]
fn prop_dist_vec<T: Eq + Hash>(a: &Option<HashSet<T>>, b: &Option<HashSet<T>>) -> u32 {
if (a.is_some() && b.is_none()) || (a.is_none() && b.is_some()) {
return 10;
}
match (a, b) {
(Some(aa), Some(bb)) => {
if aa.len() != bb.len() {
1
} else if aa == bb {
0
} else {
1
}
}
(None, None) => 0,
_ => panic!("Should not reach here!"),
}
}
#[inline(always)]
fn array_prop_append<T: Clone + Eq + Hash>(val: &Option<T>, arr: &mut Option<HashSet<T>>) {
if let Some(ap) = val {
if arr.is_none() {
*arr = Some(HashSet::from([ap.clone()]))
} else {
arr.as_mut().unwrap().insert(ap.clone());
}
}
}
#[inline(always)]
fn array_prop_append_vec<T: Clone + Eq + Hash>(
val: &Option<HashSet<T>>,
arr: &mut Option<HashSet<T>>,
) {
if let Some(ap) = val {
if arr.is_none() {
*arr = Some(ap.clone())
} else {
ap.iter().for_each(|v| {
arr.as_mut().unwrap().insert((*v).clone());
});
}
}
}
#[cfg(test)]
mod tests {
use crate::ReqFilter;
use std::collections::HashSet;
use crate::filter::FlatReqFilter;
#[test]
fn test_expand_filter() {
let input = ReqFilter {
authors: Some(HashSet::from([
"a".to_owned(),
"b".to_owned(),
"c".to_owned(),
])),
kinds: Some(HashSet::from([1, 2, 3])),
ids: Some(HashSet::from(["x".to_owned(), "y".to_owned()])),
p_tag: Some(HashSet::from(["a".to_owned()])),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
};
let output: Vec<FlatReqFilter> = (&input).into();
let expected = vec![
FlatReqFilter {
author: Some("a".to_owned()),
kind: Some(1),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("a".to_owned()),
kind: Some(1),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("a".to_owned()),
kind: Some(2),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("a".to_owned()),
kind: Some(2),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("a".to_owned()),
kind: Some(3),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("a".to_owned()),
kind: Some(3),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("b".to_owned()),
kind: Some(1),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("b".to_owned()),
kind: Some(1),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("b".to_owned()),
kind: Some(2),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("b".to_owned()),
kind: Some(2),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("b".to_owned()),
kind: Some(3),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("b".to_owned()),
kind: Some(3),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("c".to_owned()),
kind: Some(1),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("c".to_owned()),
kind: Some(1),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("c".to_owned()),
kind: Some(2),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("c".to_owned()),
kind: Some(2),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("c".to_owned()),
kind: Some(3),
id: Some("x".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
FlatReqFilter {
author: Some("c".to_owned()),
kind: Some(3),
id: Some("y".to_owned()),
p_tag: Some("a".to_owned()),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: Some(99),
until: None,
limit: Some(10),
e_tag: None,
},
];
assert_eq!(output.len(), expected.len());
output.iter().for_each(|a| assert!(expected.contains(a)));
}
}

View File

@ -0,0 +1,159 @@
use crate::filter::{FlatReqFilter, ReqFilter};
use wasm_bindgen::prelude::*;
pub mod diff;
pub mod filter;
pub mod merge;
#[wasm_bindgen]
pub fn diff_filters(prev: JsValue, next: JsValue) -> Result<JsValue, JsValue> {
let prev_parsed: Vec<FlatReqFilter> = serde_wasm_bindgen::from_value(prev)?;
let next_parsed: Vec<FlatReqFilter> = serde_wasm_bindgen::from_value(next)?;
let result = diff::diff_filter(&prev_parsed, &next_parsed);
Ok(serde_wasm_bindgen::to_value(&result)?)
}
#[wasm_bindgen]
pub fn expand_filter(val: JsValue) -> Result<JsValue, JsValue> {
let parsed: ReqFilter = serde_wasm_bindgen::from_value(val)?;
let result: Vec<FlatReqFilter> = (&parsed).into();
Ok(serde_wasm_bindgen::to_value(&result)?)
}
#[wasm_bindgen]
pub fn get_diff(prev: JsValue, next: JsValue) -> Result<JsValue, JsValue> {
let prev_parsed: Vec<ReqFilter> = serde_wasm_bindgen::from_value(prev)?;
let next_parsed: Vec<ReqFilter> = serde_wasm_bindgen::from_value(next)?;
let expanded_prev: Vec<FlatReqFilter> = prev_parsed
.iter()
.flat_map(|v| {
let vec: Vec<FlatReqFilter> = v.into();
vec
})
.collect();
let expanded_next: Vec<FlatReqFilter> = next_parsed
.iter()
.flat_map(|v| {
let vec: Vec<FlatReqFilter> = v.into();
vec
})
.collect();
let result = diff::diff_filter(&expanded_prev, &expanded_next);
Ok(serde_wasm_bindgen::to_value(&result)?)
}
#[wasm_bindgen]
pub fn flat_merge(val: JsValue) -> Result<JsValue, JsValue> {
let val_parsed: Vec<FlatReqFilter> = serde_wasm_bindgen::from_value(val)?;
let result = merge::merge::<FlatReqFilter, ReqFilter>(val_parsed.iter().collect());
Ok(serde_wasm_bindgen::to_value(&result)?)
}
#[wasm_bindgen]
pub fn compress(val: JsValue) -> Result<JsValue, JsValue> {
let val_parsed: Vec<ReqFilter> = serde_wasm_bindgen::from_value(val)?;
let result = merge::merge::<ReqFilter, ReqFilter>(val_parsed.iter().collect());
Ok(serde_wasm_bindgen::to_value(&result)?)
}
#[cfg(test)]
mod tests {
use super::*;
use itertools::Itertools;
use std::cmp::Ordering;
use std::collections::HashSet;
#[test]
fn flat_merge_expanded() {
let input = vec![
ReqFilter {
ids: None,
kinds: Some(HashSet::from([1, 6969, 6])),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
authors: Some(HashSet::from([
"kieran".to_string(),
"snort".to_string(),
"c".to_string(),
"d".to_string(),
"e".to_string(),
])),
since: Some(1),
until: Some(100),
search: None,
limit: None,
},
ReqFilter {
ids: None,
kinds: Some(HashSet::from([4])),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
authors: Some(HashSet::from(["kieran".to_string()])),
limit: None,
},
ReqFilter {
ids: None,
authors: None,
kinds: Some(HashSet::from([4])),
e_tag: None,
p_tag: Some(HashSet::from(["kieran".to_string()])),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
ReqFilter {
ids: None,
kinds: Some(HashSet::from([1000])),
authors: Some(HashSet::from(["snort".to_string()])),
p_tag: Some(HashSet::from(["kieran".to_string()])),
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
e_tag: None,
limit: None,
},
];
let expanded: Vec<FlatReqFilter> = input
.iter()
.flat_map(|v| {
let r: Vec<FlatReqFilter> = v.into();
r
})
.sorted_by(|_, _| {
if rand::random() {
Ordering::Less
} else {
Ordering::Greater
}
})
.collect();
let merged_expanded: Vec<ReqFilter> = merge::merge(expanded.iter().collect());
assert_eq!(merged_expanded.len(), input.len());
assert!(merged_expanded.iter().all(|v| input.contains(v)));
}
}

View File

@ -0,0 +1,494 @@
use crate::filter::CanMerge;
pub fn merge<'a, T, Z>(all: Vec<&'a T>) -> Vec<Z>
where
T: CanMerge,
for<'b> Z: CanMerge + From<Vec<&'a T>> + From<Vec<&'b Z>>,
{
let mut ret: Vec<Z> = merge_once(all);
loop {
let last_len = ret.len();
ret = merge_once(ret.iter().collect());
if last_len == ret.len() {
break;
}
}
ret
}
fn merge_once<'a, T, Z>(all: Vec<&'a T>) -> Vec<Z>
where
T: CanMerge,
Z: From<Vec<&'a T>>,
{
let mut ret: Vec<Z> = vec![];
if all.is_empty() {
return ret;
}
let merge_sets: Vec<Vec<&T>> = vec![vec![all.first().unwrap()]];
let merge_sets = all.iter().skip(1).fold(merge_sets, |mut acc, x| {
let mut did_match = false;
for y in acc.iter_mut() {
if y.iter().all(|z| z.can_merge(x)) {
y.push(x);
did_match = true;
break;
}
}
if !did_match {
acc.push(vec![x]);
}
acc
});
for s in merge_sets {
ret.push(Z::from(s));
}
ret
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filter::{Distance, FlatReqFilter, ReqFilter};
use std::collections::HashSet;
#[test]
fn distance() {
let a = FlatReqFilter {
id: Some("a".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
};
let b = FlatReqFilter {
id: Some("a".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
};
let c = FlatReqFilter {
id: Some("c".to_owned()),
author: None,
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
};
let d = FlatReqFilter {
id: Some("a".to_owned()),
author: None,
kind: Some(1),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
};
let e = FlatReqFilter {
id: Some("e".to_owned()),
author: None,
kind: Some(1),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
};
assert_eq!(a.distance(&b), 0);
assert_eq!(a.distance(&c), 1);
assert_eq!(a.distance(&d), 10);
assert_eq!(a.distance(&e), 11);
}
#[test]
fn merge_set() {
let a = FlatReqFilter {
id: Some("0".to_owned()),
author: Some("a".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: Some(10),
};
let b = FlatReqFilter {
id: Some("0".to_owned()),
author: Some("b".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: Some(10),
};
let output = ReqFilter {
ids: Some(HashSet::from(["0".to_owned()])),
authors: Some(HashSet::from(["a".to_owned(), "b".to_owned()])),
kinds: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: Some(10),
};
assert_eq!(ReqFilter::from(vec![&a, &b]), output);
}
#[test]
fn can_merge_filters() {
let a = FlatReqFilter {
id: Some("0".to_owned()),
author: Some("a".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: Some(10),
};
let b = FlatReqFilter {
id: Some("0".to_owned()),
author: Some("b".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: Some(10),
};
let c = FlatReqFilter {
id: Some("0".to_owned()),
author: Some("b".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: Some(100),
};
assert!(&a.can_merge(&b));
assert!(!&b.can_merge(&c));
}
#[test]
fn flat_merge() {
let input = vec![
FlatReqFilter {
id: Some("0".to_owned()),
author: Some("a".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
FlatReqFilter {
id: Some("0".to_owned()),
author: Some("b".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
FlatReqFilter {
id: None,
author: None,
kind: Some(1),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
FlatReqFilter {
id: None,
author: None,
kind: Some(2),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
FlatReqFilter {
id: None,
author: None,
kind: Some(2),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
FlatReqFilter {
id: Some("0".to_owned()),
author: Some("c".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
FlatReqFilter {
id: None,
author: Some("c".to_owned()),
kind: Some(1),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
FlatReqFilter {
id: None,
author: Some("c".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: Some(100),
},
FlatReqFilter {
id: Some("1".to_owned()),
author: Some("c".to_owned()),
kind: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
];
let output = vec![
ReqFilter {
ids: Some(HashSet::from(["0".to_owned()])),
authors: Some(HashSet::from([
"a".to_owned(),
"b".to_owned(),
"c".to_owned(),
])),
kinds: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
ReqFilter {
ids: None,
authors: None,
kinds: Some(HashSet::from([1, 2])),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
ReqFilter {
ids: None,
authors: Some(HashSet::from(["c".to_owned()])),
kinds: Some(HashSet::from([1])),
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
ReqFilter {
ids: None,
authors: Some(HashSet::from(["c".to_owned()])),
kinds: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: Some(100),
},
ReqFilter {
ids: Some(HashSet::from(["1".to_owned()])),
authors: Some(HashSet::from(["c".to_owned()])),
kinds: None,
e_tag: None,
p_tag: None,
t_tag: None,
d_tag: None,
r_tag: None,
a_tag: None,
g_tag: None,
search: None,
since: None,
until: None,
limit: None,
},
];
assert_eq!(
merge::<FlatReqFilter, ReqFilter>(input.iter().collect()),
output
)
}
}

View File