use std::collections::BTreeSet;
use crate::MemoryDimensionIdentity;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NeighborhoodRequest {
root_node_id: String,
depth: u32,
scopes: BTreeSet<String>,
}
impl NeighborhoodRequest {
pub fn new(root_node_id: impl Into<String>, depth: u32) -> Self {
Self {
root_node_id: root_node_id.into(),
depth,
scopes: BTreeSet::new(),
}
}
pub fn with_scopes(mut self, scopes: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.scopes = scopes
.into_iter()
.map(Into::into)
.map(|scope| scope.trim().to_string())
.filter(|scope| !scope.is_empty())
.collect();
self
}
pub fn root_node_id(&self) -> &str {
&self.root_node_id
}
pub fn depth(&self) -> u32 {
self.depth
}
pub fn scopes(&self) -> &BTreeSet<String> {
&self.scopes
}
pub fn admits(&self, node_id: &str) -> bool {
if self.scopes.is_empty() || MemoryDimensionIdentity::parse(node_id).is_none() {
return true;
}
self.scopes.iter().any(|scope| {
node_id == scope
|| node_id
.strip_prefix(scope.as_str())
.is_some_and(|rest| rest.starts_with(':'))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_request_without_scopes_admits_everything() {
let request = NeighborhoodRequest::new("about:project", 3);
assert!(request.admits("about:project:dimension:timeline"));
assert!(request.admits("project:entry:one"));
assert_eq!(request.depth(), 3);
assert_eq!(request.root_node_id(), "about:project");
}
#[test]
fn only_dimensions_are_ever_refused() {
let request = NeighborhoodRequest::new("about:project", 3)
.with_scopes(["about:project:dimension:timeline"]);
assert!(request.admits("about:project:dimension:timeline"));
assert!(!request.admits("about:project:dimension:conversation"));
assert!(request.admits("project:entry:one"));
}
#[test]
fn asking_for_a_dimension_asks_for_its_scopes() {
let request = NeighborhoodRequest::new("about:project", 3)
.with_scopes(["about:project:dimension:conversation"]);
assert!(request.admits("about:project:dimension:conversation:alpha"));
assert!(!request.admits("about:project:dimension:conversationalist"));
}
#[test]
fn an_exact_scope_does_not_admit_its_siblings() {
let request = NeighborhoodRequest::new("about:project", 3)
.with_scopes(["about:project:dimension:conversation:alpha"]);
assert!(request.admits("about:project:dimension:conversation:alpha"));
assert!(!request.admits("about:project:dimension:conversation:beta"));
}
#[test]
fn blank_scopes_are_not_a_narrowing() {
let request = NeighborhoodRequest::new("about:project", 3).with_scopes(["", " "]);
assert!(request.scopes().is_empty());
assert!(request.admits("about:project:dimension:conversation"));
}
}