use crate::export_payload::{BinaryExportPayload, ExportPayload, unix_now_secs};
use crate::framework::ChaoticSemanticFramework;
use csm_core_lib::error::Result;
#[cfg(not(target_arch = "wasm32"))]
use std::path::Path;
use std::sync::Arc;
use tokio::sync::RwLock;
impl ChaoticSemanticFramework {
pub async fn set_namespace(&self, ns: impl Into<String>) -> Result<()> {
let ns = ns.into();
Self::validate_namespace(&ns)?;
*self.namespace.write().await = ns;
Ok(())
}
pub async fn list_namespaces(&self) -> Result<Vec<String>> {
let mut namespaces: Vec<String> = {
let sing = self.singularity.read().await;
sing.namespaces.keys().cloned().collect()
};
if let Some(ref persistence) = self.persistence {
if let Ok(persisted) = persistence.list_namespaces().await {
for ns in persisted {
if !namespaces.contains(&ns) {
namespaces.push(ns);
}
}
}
}
Ok(namespaces)
}
pub async fn delete_namespace(&self, ns: &str) -> Result<usize> {
Self::validate_namespace(ns)?;
let count = {
let mut sing = self.singularity.write().await;
let count = sing.len(ns);
sing.namespaces.remove(ns);
count
};
if let Some(ref persistence) = self.persistence {
persistence.clear_namespace(ns).await?;
}
Ok(count)
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn export_namespace(&self, ns: &str, path: &Path) -> Result<()> {
Self::validate_namespace(ns)?;
let path_str =
path.to_str()
.ok_or_else(|| csm_core_lib::error::MemoryError::InvalidInput {
field: "path".to_string(),
reason: "Invalid path".to_string(),
})?;
self.ensure_namespace_loaded(ns).await?;
let temp_fw = self.clone_with_namespace(ns);
temp_fw.export_json(path_str).await
}
pub async fn export_namespace_to_bytes(&self, ns: &str) -> Result<Vec<u8>> {
Self::validate_namespace(ns)?;
self.ensure_namespace_loaded(ns).await?;
let payload = {
let sing = self.singularity.read().await;
ExportPayload {
version: env!("CARGO_PKG_VERSION").to_string(),
exported_at: unix_now_secs(),
concepts: sing.all_concepts(ns),
associations: sing.all_associations(ns),
}
};
let binary_payload = BinaryExportPayload::from(payload);
let data = bincode::serialize(&binary_payload).map_err(|e| {
csm_core_lib::error::MemoryError::Persistence(format!("Serialization error: {e}"))
})?;
Ok(data)
}
async fn ensure_namespace_loaded(&self, ns: &str) -> Result<()> {
{
let sing = self.singularity.read().await;
if sing.namespaces.contains_key(ns) {
return Ok(());
}
}
if let Some(ref persistence) = self.persistence {
let concepts = persistence.load_all_concepts(ns).await?;
let mut sing = self.singularity.write().await;
if !sing.namespaces.contains_key(ns) {
for concept in concepts {
sing.inject(ns, concept)?;
}
}
}
Ok(())
}
fn clone_with_namespace(&self, ns: &str) -> Self {
Self {
singularity: self.singularity.clone(),
persistence: self.persistence.clone(),
reservoir: self.reservoir.clone(),
config: self.config.clone(),
metrics: self.metrics.clone(),
event_sender: self.event_sender.clone(),
emitters: self.emitters.clone(),
namespace: Arc::new(RwLock::new(ns.to_string())),
embedding_provider: self.embedding_provider.clone(),
projection: self.projection.clone(),
cleanup_handle: self.cleanup_handle.clone(),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use super::*;
use crate::export_payload::BinaryExportPayload;
use csm_core_lib::hyperdim::HVec10240;
async fn empty_framework() -> ChaoticSemanticFramework {
ChaoticSemanticFramework::builder()
.without_persistence()
.build()
.await
.expect("framework build should succeed")
}
#[tokio::test]
async fn test_set_namespace_updates_state() {
let fw = empty_framework().await;
assert_eq!(fw.namespace().await, "_default");
fw.set_namespace("new-ns").await.unwrap();
assert_eq!(fw.namespace().await, "new-ns");
assert!(fw.set_namespace("").await.is_err());
assert_eq!(fw.namespace().await, "new-ns");
}
#[tokio::test]
async fn test_export_namespace_to_bytes_serializes_concepts() {
let fw = empty_framework().await;
fw.set_namespace("test-ns").await.unwrap();
let vector = HVec10240::random();
fw.inject_concept("c1", vector).await.unwrap();
fw.inject_concept("c2", HVec10240::random()).await.unwrap();
fw.associate("c1", "c2", 0.5_f32).await.unwrap();
let bytes = fw.export_namespace_to_bytes("test-ns").await.unwrap();
assert!(!bytes.is_empty(), "export bytes should not be empty");
let bin_payload: BinaryExportPayload =
bincode::deserialize(&bytes).expect("should deserialize bincode payload");
assert_eq!(bin_payload.concepts.len(), 2, "should have 2 concepts");
assert_eq!(
bin_payload.associations.len(),
1,
"should have 1 association"
);
assert_eq!(
bin_payload.associations[0],
("c1".to_string(), "c2".to_string(), 0.5_f32)
);
let c1 = bin_payload
.concepts
.iter()
.find(|c| c.id == "c1")
.expect("c1 should exist in export");
let restored = HVec10240::from_bytes(&c1.vector_bytes).unwrap();
assert_eq!(
restored.to_bytes(),
vector.to_bytes(),
"vector should survive roundtrip"
);
}
#[tokio::test]
async fn test_export_namespace_to_bytes_empty_namespace() {
let fw = empty_framework().await;
let bytes = fw.export_namespace_to_bytes("empty-ns").await.unwrap();
let bin_payload: BinaryExportPayload =
bincode::deserialize(&bytes).expect("should deserialize bincode payload");
assert!(
bin_payload.concepts.is_empty(),
"empty namespace should have no concepts"
);
assert!(
bin_payload.associations.is_empty(),
"empty namespace should have no associations"
);
}
#[tokio::test]
async fn test_export_namespace_to_bytes_namespace_not_found() {
let fw = empty_framework().await;
let bytes = fw.export_namespace_to_bytes("nonexistent").await.unwrap();
let bin_payload: BinaryExportPayload =
bincode::deserialize(&bytes).expect("should deserialize bincode payload");
assert!(bin_payload.concepts.is_empty());
}
#[tokio::test]
async fn test_delete_namespace_returns_correct_count() {
let fw = empty_framework().await;
fw.set_namespace("delete-test").await.unwrap();
fw.inject_concept("c1", HVec10240::random()).await.unwrap();
fw.inject_concept("c2", HVec10240::random()).await.unwrap();
let count = fw.delete_namespace("delete-test").await.unwrap();
assert_eq!(
count, 2,
"delete_namespace should return the number of deleted concepts"
);
let namespaces = fw.list_namespaces().await.unwrap();
assert!(
!namespaces.contains(&"delete-test".to_string()),
"namespace should be removed from list"
);
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::test]
async fn test_export_namespace_writes_file() {
let fw = empty_framework().await;
fw.set_namespace("export-test").await.unwrap();
fw.inject_concept("c1", HVec10240::random()).await.unwrap();
let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path().join("export.json");
fw.export_namespace("export-test", &path).await.unwrap();
assert!(path.exists(), "export file should exist");
let content = std::fs::read_to_string(&path).unwrap();
assert!(
content.contains("c1"),
"export file should contain concept id"
);
assert!(content.len() > 100, "export file should not be empty");
}
#[tokio::test]
async fn test_export_namespace_to_bytes_isolates_namespaces() {
let fw = empty_framework().await;
let vector_a = HVec10240::random();
let vector_b = HVec10240::random();
fw.set_namespace("ns-a").await.unwrap();
fw.inject_concept("a1", vector_a).await.unwrap();
fw.set_namespace("ns-b").await.unwrap();
fw.inject_concept("b1", vector_b).await.unwrap();
let bytes = fw.export_namespace_to_bytes("ns-a").await.unwrap();
let bin_payload: BinaryExportPayload =
bincode::deserialize(&bytes).expect("should deserialize");
assert_eq!(bin_payload.concepts.len(), 1, "ns-a should have 1 concept");
assert_eq!(
bin_payload.concepts[0].id, "a1",
"ns-a should contain a1 only"
);
}
#[tokio::test]
async fn test_delete_namespace_logic() {
let fw = empty_framework().await;
fw.set_namespace("ns-delete").await.unwrap();
fw.inject_concept("c1", HVec10240::random()).await.unwrap();
fw.inject_concept("c2", HVec10240::random()).await.unwrap();
let count = fw.delete_namespace("ns-delete").await.unwrap();
assert_eq!(count, 2, "should return correct deleted count");
let namespaces = fw.list_namespaces().await.unwrap();
assert!(
!namespaces.contains(&"ns-delete".to_string()),
"namespace should be removed"
);
}
#[cfg(not(target_arch = "wasm32"))]
#[tokio::test]
async fn test_export_namespace_file() {
let export_path = std::path::PathBuf::from("/tmp/export_ns_test.json");
if export_path.exists() {
let _ = std::fs::remove_file(&export_path);
}
let fw = empty_framework().await;
fw.set_namespace("ns-export").await.unwrap();
fw.inject_concept("c1", HVec10240::random()).await.unwrap();
fw.export_namespace("ns-export", &export_path)
.await
.unwrap();
assert!(export_path.exists(), "export file should exist");
let content = std::fs::read_to_string(&export_path).unwrap();
assert!(
content.contains("\"id\": \"c1\""),
"export should contain concept"
);
let _ = std::fs::remove_file(&export_path);
}
#[tokio::test]
async fn test_validate_namespace_integration() {
let fw = empty_framework().await;
assert!(fw.set_namespace("").await.is_err());
assert!(fw.set_namespace("a".repeat(129)).await.is_err());
assert!(fw.set_namespace("ns\0").await.is_err());
assert!(fw.delete_namespace("").await.is_err());
#[cfg(not(target_arch = "wasm32"))]
{
let path = std::path::Path::new("test.json");
assert!(fw.export_namespace("", path).await.is_err());
}
assert!(fw.export_namespace_to_bytes("").await.is_err());
}
}