use crate::error::{MemoryError, Result};
use crate::hyperdim::HVec10240;
use crate::singularity::Singularity;
use tracing::instrument;
impl Singularity {
#[instrument(skip(self, ns), fields(ids_count = ids.len()))]
pub fn bundle_concepts_strict(&self, ns: &str, ids: &[String]) -> Result<HVec10240> {
let ns_state = self
.get_namespace(ns)
.ok_or_else(|| MemoryError::NotFound {
entity: "Namespace".to_string(),
id: ns.to_string(),
})?;
let mut vectors = Vec::with_capacity(ids.len());
for id in ids {
match ns_state.concepts.get(id) {
Some(concept) => vectors.push(concept.vector),
None => {
return Err(MemoryError::NotFound {
entity: "Concept".to_string(),
id: id.clone(),
});
}
}
}
if vectors.is_empty() {
return Err(MemoryError::InvalidInput {
field: "ids".to_string(),
reason: "Empty concept list for bundling".to_string(),
});
}
HVec10240::bundle(&vectors)
}
pub fn update_metadata(
&mut self,
ns: &str,
id: &str,
metadata: std::collections::HashMap<String, serde_json::Value>,
) -> Result<()> {
let ns_state = self.get_namespace_mut(ns);
if let Some(concept) = ns_state.concepts.get_mut(id) {
concept.metadata = metadata;
concept.modified_at = crate::singularity::unix_now_secs();
self.invalidate_cache(ns);
Ok(())
} else {
Err(MemoryError::NotFound {
entity: "Concept".to_string(),
id: id.to_string(),
})
}
}
pub fn clear_associations(&mut self, ns: &str, id: &str) -> Result<()> {
let ns_state = self.get_namespace_mut(ns);
if let Some(neighbors) = ns_state.associations.get_mut(id) {
neighbors.clear();
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::MemoryError;
use crate::singularity::{ConceptBuilder, Singularity, SingularityConfig};
use std::collections::HashMap;
const NS: &str = "_default";
#[test]
fn test_bundle_concepts_strict_success() -> crate::error::Result<()> {
let mut singularity = Singularity::with_config(SingularityConfig::default());
let vec1 = HVec10240::random();
let vec2 = HVec10240::random();
let c1 = ConceptBuilder::new("c1").with_vector(vec1).build().unwrap();
let c2 = ConceptBuilder::new("c2").with_vector(vec2).build().unwrap();
singularity.inject(NS, c1)?;
singularity.inject(NS, c2)?;
let result = singularity.bundle_concepts_strict(NS, &["c1".to_string(), "c2".to_string()]);
assert!(result.is_ok());
Ok(())
}
#[test]
fn test_bundle_concepts_strict_missing_id() -> crate::error::Result<()> {
let mut singularity = Singularity::with_config(SingularityConfig::default());
let vec1 = HVec10240::random();
let c1 = ConceptBuilder::new("c1").with_vector(vec1).build()?;
singularity.inject(NS, c1)?;
let result =
singularity.bundle_concepts_strict(NS, &["c1".to_string(), "missing_id".to_string()]);
match result {
Err(MemoryError::NotFound { entity, id }) => {
assert_eq!(entity, "Concept");
assert_eq!(id, "missing_id");
}
_ => panic!("Expected NotFound error, got {result:?}"),
}
Ok(())
}
#[test]
fn test_update_metadata_not_found() {
let mut sing = Singularity::new(SingularityConfig::default());
let metadata = HashMap::new();
let result = sing.update_metadata(NS, "non-existent-id", metadata);
match result {
Err(MemoryError::NotFound { entity, id }) => {
assert_eq!(entity, "Concept");
assert_eq!(id, "non-existent-id");
}
_ => panic!("Expected MemoryError::NotFound, got {result:?}"),
}
}
#[test]
fn test_update_metadata_success() -> crate::error::Result<()> {
let mut sing = Singularity::new(SingularityConfig::default());
let concept = ConceptBuilder::new("test-id")
.with_metadata("original", serde_json::Value::Bool(true))
.build()
.expect("Failed to build concept");
sing.inject(NS, concept)?;
let mut new_metadata = HashMap::new();
new_metadata.insert("updated".to_string(), serde_json::Value::Bool(true));
let time_before = crate::singularity::unix_now_secs();
let result = sing.update_metadata(NS, "test-id", new_metadata.clone());
assert!(result.is_ok());
let updated_concept = sing
.get(NS, "test-id")
.ok_or_else(|| MemoryError::NotFound {
entity: "Concept".to_string(),
id: "test-id".to_string(),
})?;
assert_eq!(updated_concept.metadata, new_metadata);
assert!(updated_concept.modified_at >= time_before);
Ok(())
}
}