use crate::error::TopoError;
use crate::props::PropValue;
use serde::{Deserialize, Serialize};
use smol_str::SmolStr;
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PropIndex {
pub label: SmolStr,
pub prop: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexSpec {
pub equality: Vec<PropIndex>,
pub text: Vec<PropIndex>,
}
impl IndexSpec {
pub(crate) fn validate(&self) -> Result<(), TopoError> {
validate_unique(&self.equality)?;
validate_unique(&self.text)?;
Ok(())
}
}
fn validate_unique(entries: &[PropIndex]) -> Result<(), TopoError> {
let mut seen: HashSet<(&SmolStr, &str)> = HashSet::new();
for p in entries {
if !seen.insert((&p.label, p.prop.as_str())) {
return Err(TopoError::Rejected(format!(
"duplicate index declaration for ({}, {})",
p.label, p.prop
)));
}
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub(crate) enum IndexValue {
Str(String),
Int(i64),
Bool(bool),
Bytes(Vec<u8>),
DateTime(i64),
}
impl IndexValue {
pub(crate) fn of(v: &PropValue) -> Option<IndexValue> {
match v {
PropValue::Str(s) => Some(IndexValue::Str(s.clone())),
PropValue::Int(i) => Some(IndexValue::Int(*i)),
PropValue::Bool(b) => Some(IndexValue::Bool(*b)),
PropValue::Bytes(b) => Some(IndexValue::Bytes(b.clone())),
PropValue::DateTime(dt) => Some(IndexValue::DateTime(*dt)),
PropValue::Float(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_rejects_duplicates_within_a_list_but_not_across_lists() {
let dup_within = IndexSpec {
equality: vec![
PropIndex {
label: "M".into(),
prop: "x".into(),
},
PropIndex {
label: "M".into(),
prop: "x".into(),
},
],
text: vec![],
};
assert!(matches!(dup_within.validate(), Err(TopoError::Rejected(_))));
let same_key_both_lists = IndexSpec {
equality: vec![PropIndex {
label: "M".into(),
prop: "x".into(),
}],
text: vec![PropIndex {
label: "M".into(),
prop: "x".into(),
}],
};
assert!(same_key_both_lists.validate().is_ok());
}
#[test]
fn index_value_of_is_none_for_float_and_some_for_everything_else() {
assert_eq!(IndexValue::of(&PropValue::Float(1.0)), None);
assert_eq!(
IndexValue::of(&PropValue::Str("a".into())),
Some(IndexValue::Str("a".into()))
);
assert_eq!(IndexValue::of(&PropValue::Int(1)), Some(IndexValue::Int(1)));
assert_eq!(
IndexValue::of(&PropValue::Bool(true)),
Some(IndexValue::Bool(true))
);
assert_eq!(
IndexValue::of(&PropValue::Bytes(vec![1, 2])),
Some(IndexValue::Bytes(vec![1, 2]))
);
assert_eq!(
IndexValue::of(&PropValue::DateTime(5)),
Some(IndexValue::DateTime(5))
);
}
}