use std::collections::BTreeSet;
use crate::MemoryDimensionIdentity;
use crate::value_objects::entry_labels::EntryLabels;
use crate::value_objects::label_selector::{LabelSelector, LabelSelectorOperator};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DimensionSelectionMode {
All,
Only,
Except,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DimensionScopeMode {
CurrentAbout,
Abouts,
AllAbouts,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DimensionSelection {
mode: DimensionSelectionMode,
dimensions: BTreeSet<String>,
scope_mode: DimensionScopeMode,
abouts: BTreeSet<String>,
scope_ids: BTreeSet<String>,
selectors: Vec<LabelSelector>,
}
impl DimensionSelection {
pub fn all() -> Self {
Self {
mode: DimensionSelectionMode::All,
dimensions: BTreeSet::new(),
scope_mode: DimensionScopeMode::CurrentAbout,
abouts: BTreeSet::new(),
scope_ids: BTreeSet::new(),
selectors: Vec::new(),
}
}
pub fn only(values: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
mode: DimensionSelectionMode::Only,
dimensions: normalize_dimensions(values),
..Self::all()
}
}
pub fn except(values: impl IntoIterator<Item = impl Into<String>>) -> Self {
Self {
mode: DimensionSelectionMode::Except,
dimensions: normalize_dimensions(values),
..Self::all()
}
}
pub fn mode(&self) -> DimensionSelectionMode {
self.mode
}
pub fn dimensions(&self) -> &BTreeSet<String> {
&self.dimensions
}
pub fn scope_mode(&self) -> DimensionScopeMode {
self.scope_mode
}
pub fn abouts(&self) -> &BTreeSet<String> {
&self.abouts
}
pub fn scope_ids(&self) -> &BTreeSet<String> {
&self.scope_ids
}
pub fn selectors(&self) -> &[LabelSelector] {
&self.selectors
}
pub fn with_scope_ids(
mut self,
scope_ids: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.scope_ids = normalize_dimensions(scope_ids);
self
}
pub fn with_selectors(mut self, selectors: impl IntoIterator<Item = LabelSelector>) -> Self {
self.selectors = selectors
.into_iter()
.collect::<BTreeSet<_>>()
.into_iter()
.collect();
self
}
pub fn with_current_about_scope(mut self) -> Self {
self.scope_mode = DimensionScopeMode::CurrentAbout;
self.abouts.clear();
self
}
pub fn with_about_scope(mut self, abouts: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.scope_mode = DimensionScopeMode::Abouts;
self.abouts = normalize_dimensions(abouts);
self
}
pub fn with_all_about_scope(mut self) -> Self {
self.scope_mode = DimensionScopeMode::AllAbouts;
self.abouts.clear();
self
}
pub fn resolve_current_about(&self, current_about: &str) -> Self {
if self.scope_mode != DimensionScopeMode::CurrentAbout {
return self.clone();
}
self.clone().with_about_scope([current_about.to_string()])
}
pub fn includes(&self, dimension: &str) -> bool {
self.includes_dimension(dimension)
}
pub fn includes_dimension(&self, dimension: &str) -> bool {
match self.mode {
DimensionSelectionMode::All => true,
DimensionSelectionMode::Only => self.dimensions.contains(dimension),
DimensionSelectionMode::Except => !self.dimensions.contains(dimension),
}
}
pub fn includes_coordinate(&self, dimension: &str, scope_id: &str) -> bool {
self.includes_dimension(dimension)
&& self.includes_scope(scope_id)
&& self.includes_dimension_scope(scope_id)
}
pub fn includes_scope(&self, scope_id: &str) -> bool {
match self.scope_mode {
DimensionScopeMode::AllAbouts => true,
DimensionScopeMode::CurrentAbout => true,
DimensionScopeMode::Abouts => MemoryDimensionIdentity::parse(scope_id)
.map(|identity| self.abouts.contains(identity.about()))
.unwrap_or(false),
}
}
pub fn includes_dimension_scope(&self, scope_id: &str) -> bool {
if self.scope_ids.is_empty() {
return true;
}
let scope_id = scope_id.trim();
self.scope_ids.contains(scope_id)
|| MemoryDimensionIdentity::parse(scope_id)
.map(|identity| self.scope_ids.contains(identity.dimension_id()))
.unwrap_or(false)
}
pub fn admits(&self, labels: &EntryLabels) -> bool {
self.selectors
.iter()
.all(|selector| selector.admits(labels))
}
pub fn has_selectors(&self) -> bool {
!self.selectors.is_empty()
}
pub fn positive_selector_ids(&self) -> Vec<String> {
self.selectors
.iter()
.flat_map(|selector| match selector.operator() {
LabelSelectorOperator::Exists => vec![selector.key().to_string()],
LabelSelectorOperator::In => selector.values().iter().cloned().collect(),
LabelSelectorOperator::NotIn | LabelSelectorOperator::NotExists => Vec::new(),
})
.collect::<BTreeSet<_>>()
.into_iter()
.collect()
}
}
impl Default for DimensionSelection {
fn default() -> Self {
Self::all()
}
}
fn normalize_dimensions(values: impl IntoIterator<Item = impl Into<String>>) -> BTreeSet<String> {
values
.into_iter()
.map(Into::into)
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
.collect()
}
#[cfg(test)]
mod tests {
use super::{DimensionSelection, EntryLabels, LabelSelector, LabelSelectorOperator};
#[test]
fn selection_filters_dimensions() {
let only = DimensionSelection::only(["conversation", "entity", " "]);
assert!(only.includes("conversation"));
assert!(!only.includes("benchmark_record"));
let except = DimensionSelection::except(["entity"]);
assert!(except.includes("conversation"));
assert!(!except.includes("entity"));
}
#[test]
fn selection_filters_about_scope_when_resolved() {
let current = DimensionSelection::only(["timeline"]).resolve_current_about("question:a");
assert!(current.includes_coordinate("timeline", "about:question:a:dimension:timeline"));
assert!(!current.includes_coordinate("timeline", "about:question:b:dimension:timeline"));
let all = DimensionSelection::only(["timeline"]).with_all_about_scope();
assert!(all.includes_coordinate("timeline", "about:question:b:dimension:timeline"));
}
#[test]
fn selection_filters_exact_dimension_scope_ids() {
let local = DimensionSelection::only(["conversation"])
.resolve_current_about("question:a")
.with_scope_ids(["conversation:alpha"]);
assert!(local.includes_coordinate(
"conversation",
"about:question:a:dimension:conversation:alpha"
));
assert!(!local.includes_coordinate(
"conversation",
"about:question:a:dimension:conversation:beta"
));
assert!(
!local.includes_coordinate("topic", "about:question:a:dimension:conversation:alpha")
);
let namespaced = DimensionSelection::all()
.with_scope_ids(["about:question:a:dimension:conversation:alpha"]);
assert!(namespaced.includes_coordinate(
"conversation",
"about:question:a:dimension:conversation:alpha"
));
assert!(!namespaced.includes_coordinate(
"conversation",
"about:question:b:dimension:conversation:alpha"
));
}
#[test]
fn selectors_read_the_whole_entry_and_conjoin() {
let selection = DimensionSelection::all().with_selectors([
LabelSelector::new("env", LabelSelectorOperator::In, ["prod"]).expect("selector"),
LabelSelector::new(
"task",
LabelSelectorOperator::NotExists,
Vec::<String>::new(),
)
.expect("selector"),
]);
let prod_without_task =
EntryLabels::from_pairs([("env", "prod"), ("agentic_process", "p-1")]);
let prod_with_task = EntryLabels::from_pairs([("env", "prod"), ("task", "t-1")]);
let dev = EntryLabels::from_pairs([("env", "dev")]);
assert!(selection.admits(&prod_without_task));
assert!(!selection.admits(&prod_with_task));
assert!(!selection.admits(&dev));
assert!(DimensionSelection::all().admits(&dev));
}
#[test]
fn except_and_notexists_are_not_duals() {
let except = DimensionSelection::except(["task"]);
assert!(except.includes_coordinate("agentic_process", "about:a:dimension:p-1"));
let entry = EntryLabels::from_pairs([("agentic_process", "p-1"), ("task", "t-1")]);
let absent = DimensionSelection::all().with_selectors([LabelSelector::new(
"task",
LabelSelectorOperator::NotExists,
Vec::<String>::new(),
)
.expect("selector")]);
assert!(!absent.admits(&entry));
}
#[test]
fn positive_selectors_name_what_an_index_can_pick_by() {
let selection = DimensionSelection::all().with_selectors([
LabelSelector::new(
"incident",
LabelSelectorOperator::Exists,
Vec::<String>::new(),
)
.expect("selector"),
LabelSelector::new("env", LabelSelectorOperator::In, ["prod", "staging"])
.expect("selector"),
LabelSelector::new("customer", LabelSelectorOperator::NotIn, ["acme"])
.expect("selector"),
]);
assert_eq!(
selection.positive_selector_ids(),
vec![
"incident".to_string(),
"prod".to_string(),
"staging".to_string()
]
);
assert!(
DimensionSelection::all()
.with_selectors([LabelSelector::new(
"customer",
LabelSelectorOperator::NotIn,
["acme"]
)
.expect("selector")])
.positive_selector_ids()
.is_empty()
);
}
#[test]
fn duplicate_selectors_collapse_into_a_stable_order() {
let first =
LabelSelector::new("env", LabelSelectorOperator::In, ["prod"]).expect("selector");
let second =
LabelSelector::new("task", LabelSelectorOperator::Exists, Vec::<String>::new())
.expect("selector");
let selection = DimensionSelection::all().with_selectors([
second.clone(),
first.clone(),
first.clone(),
]);
assert_eq!(selection.selectors(), &[first, second]);
}
}