use crate::error::EventsRoutingError;
use crate::walk_state::WalkStateUnit;
use cm_rust::DictionaryValue;
use cm_types::Name;
use maplit::btreemap;
use moniker::ExtendedMoniker;
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub struct EventSubscription<NameType = Name>
where
NameType: Clone,
{
pub event_name: NameType,
}
impl<T: Clone> EventSubscription<T> {
pub fn new(event_name: T) -> Self {
Self { event_name }
}
}
type OptionFilterMap = Option<BTreeMap<String, DictionaryValue>>;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct EventFilter {
moniker: ExtendedMoniker,
filter: Option<BTreeMap<String, DictionaryValue>>,
is_debug: bool,
}
impl EventFilter {
pub fn new(
moniker: impl Into<ExtendedMoniker>,
filter: Option<BTreeMap<String, DictionaryValue>>,
) -> Self {
Self { moniker: moniker.into(), filter, is_debug: false }
}
pub fn debug(moniker: ExtendedMoniker) -> Self {
Self { moniker, filter: None, is_debug: true }
}
pub fn has_fields(&self, fields: &OptionFilterMap) -> bool {
if self.is_debug {
return true;
}
Self::validate_subset(&self.moniker, &fields, &self.filter).is_ok()
}
pub fn contains(&self, key: impl Into<String>, values: Vec<String>) -> bool {
self.has_fields(&Some(btreemap! {key.into() => DictionaryValue::StrVec(values)}))
}
fn validate_subset(
moniker: &ExtendedMoniker,
self_filter: &OptionFilterMap,
next_filter: &OptionFilterMap,
) -> Result<(), EventsRoutingError> {
match (self_filter, next_filter) {
(None, None) => {}
(None, Some(_)) => {}
(Some(filter), Some(next_filter)) => {
for (key, value) in filter {
if !(next_filter.contains_key(key)
&& is_subset(value, next_filter.get(key).as_ref().unwrap()))
{
return Err(EventsRoutingError::InvalidFilter { moniker: moniker.clone() });
}
}
}
(Some(_), None) => {
return Err(EventsRoutingError::InvalidFilter { moniker: moniker.clone() });
}
}
Ok(())
}
}
impl WalkStateUnit for EventFilter {
type Error = EventsRoutingError;
fn validate_next(&self, next_state: &EventFilter) -> Result<(), Self::Error> {
Self::validate_subset(&self.moniker, &self.filter, &next_state.filter)
}
fn finalize_error(&self) -> Self::Error {
EventsRoutingError::MissingFilter { moniker: self.moniker.clone() }
}
}
fn is_subset(prev_value: &DictionaryValue, next_value: &DictionaryValue) -> bool {
match (prev_value, next_value) {
(DictionaryValue::Str(field), DictionaryValue::Str(next_field)) => field == next_field,
(DictionaryValue::StrVec(fields), DictionaryValue::StrVec(next_fields)) => {
fields.iter().all(|field| next_fields.contains(field))
}
(DictionaryValue::Str(field), DictionaryValue::StrVec(next_fields)) => {
next_fields.contains(field)
}
(DictionaryValue::StrVec(fields), DictionaryValue::Str(next_field)) => {
if fields.is_empty() {
return true;
}
if fields.len() > 1 {
return false;
}
fields.contains(next_field)
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use maplit::btreemap;
use moniker::Moniker;
#[test]
fn test_filter_walk_state() {
let none_filter = EventFilter::new(Moniker::root(), None);
let empty_filter = EventFilter::new(Moniker::root(), Some(btreemap! {}));
let single_field_filter = EventFilter::new(
Moniker::root(),
Some(btreemap! {
"field".to_string() => DictionaryValue::Str("/foo".to_string()),
}),
);
let single_field_filter_2 = EventFilter::new(
Moniker::root(),
Some(btreemap! {
"field".to_string() => DictionaryValue::Str("/bar".to_string()),
}),
);
let multi_field_filter = EventFilter::new(
Moniker::root(),
Some(btreemap! {
"field".to_string() => DictionaryValue::StrVec(vec![
"/bar".to_string(), "/baz".to_string()])
}),
);
let multi_field_filter_2 = EventFilter::new(
Moniker::root(),
Some(btreemap! {
"field".to_string() => DictionaryValue::StrVec(vec![
"/bar".to_string(), "/baz".to_string(), "/foo".to_string()])
}),
);
let multi_field_single = EventFilter::new(
Moniker::root(),
Some(btreemap! {
"field".to_string() => DictionaryValue::StrVec(vec!["/foo".to_string()])
}),
);
let multi_field_empty = EventFilter::new(
Moniker::root(),
Some(btreemap! {
"field".to_string() => DictionaryValue::StrVec(vec![])
}),
);
assert_matches!(none_filter.validate_next(&none_filter), Ok(()));
assert_matches!(
single_field_filter.validate_next(&none_filter),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(
single_field_filter.validate_next(&empty_filter),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(single_field_filter.validate_next(&single_field_filter), Ok(()));
assert_matches!(
single_field_filter.validate_next(&single_field_filter_2),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(
single_field_filter.validate_next(&multi_field_filter),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(single_field_filter.validate_next(&multi_field_filter_2), Ok(()));
assert_matches!(
multi_field_filter.validate_next(&none_filter),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(
multi_field_filter_2.validate_next(&multi_field_filter),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(
multi_field_filter.validate_next(&single_field_filter),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(
multi_field_filter.validate_next(&single_field_filter_2),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(multi_field_filter.validate_next(&multi_field_filter), Ok(()));
assert_matches!(multi_field_filter.validate_next(&multi_field_filter_2), Ok(()));
assert_matches!(
multi_field_filter.validate_next(&empty_filter),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(
empty_filter.validate_next(&none_filter),
Err(EventsRoutingError::InvalidFilter { .. })
);
assert_matches!(empty_filter.validate_next(&empty_filter), Ok(()));
assert_matches!(empty_filter.validate_next(&single_field_filter), Ok(()));
assert_matches!(empty_filter.validate_next(&multi_field_filter), Ok(()));
assert_matches!(multi_field_single.validate_next(&single_field_filter), Ok(()));
assert_matches!(multi_field_empty.validate_next(&single_field_filter), Ok(()));
}
#[test]
fn contains_filter() {
let filter = EventFilter::new(
Moniker::root(),
Some(btreemap! {
"field".to_string() => DictionaryValue::StrVec(vec!["/foo".to_string(), "/bar".to_string()]),
}),
);
assert!(filter.contains("field", vec!["/foo".to_string()]));
assert!(filter.contains("field", vec!["/foo".to_string(), "/bar".to_string()]));
assert!(!filter.contains("field2", vec!["/foo".to_string()]));
assert!(!filter
.contains("field2", vec!["/foo".to_string(), "/bar".to_string(), "/baz".to_string()]));
assert!(!filter.contains("field2", vec!["/baz".to_string()]));
}
}