/// In-memory registry of named projection schemas. /// /// The registry is the store of all registered projections. In a running server, /// one registry instance is shared (behind a Mutex or RwLock). Projections are /// looked up by name to execute queries. use std::collections::HashMap; use crate::error::{ProjectionError, ProjectionResult}; use crate::schema::ProjectionSchema; /// Holds all registered `ProjectionSchema`s, keyed by name. #[derive(Default)] pub struct ProjectionRegistry { schemas: HashMap, } impl ProjectionRegistry { pub fn new() -> Self { Self { schemas: HashMap::new(), } } /// Register a new schema. Fails if one with the same name already exists. pub fn register(&mut self, schema: ProjectionSchema) -> ProjectionResult<()> { if self.schemas.contains_key(&schema.name) { return Err(ProjectionError::AlreadyExists(schema.name.clone())); } if schema.name.is_empty() { return Err(ProjectionError::InvalidSchema("name must not be empty".into())); } self.schemas.insert(schema.name.clone(), schema); Ok(()) } /// Replace an existing schema (upsert). Creates if not present. pub fn upsert(&mut self, schema: ProjectionSchema) -> ProjectionResult<()> { if schema.name.is_empty() { return Err(ProjectionError::InvalidSchema("name must not be empty".into())); } self.schemas.insert(schema.name.clone(), schema); Ok(()) } /// Retrieve a schema by name. pub fn get(&self, name: &str) -> ProjectionResult<&ProjectionSchema> { self.schemas .get(name) .ok_or_else(|| ProjectionError::NotFound(name.to_string())) } /// List all schema names. pub fn list(&self) -> Vec<&ProjectionSchema> { let mut schemas: Vec<&ProjectionSchema> = self.schemas.values().collect(); schemas.sort_by(|a, b| a.name.cmp(&b.name)); schemas } /// Remove a schema by name. Returns true if it existed. pub fn remove(&mut self, name: &str) -> bool { self.schemas.remove(name).is_some() } /// Number of registered schemas. pub fn len(&self) -> usize { self.schemas.len() } /// True if no schemas are registered. pub fn is_empty(&self) -> bool { self.schemas.is_empty() } } #[cfg(test)] mod tests { use super::*; use crate::schema::{NodeFilter, ProjectionType}; fn make_schema(name: &str) -> ProjectionSchema { ProjectionSchema { name: name.to_string(), description: None, projection_type: ProjectionType::Relational, node_filter: NodeFilter::All, field_mappings: vec![], } } #[test] fn test_register_and_get() { let mut reg = ProjectionRegistry::new(); reg.register(make_schema("users")).unwrap(); let s = reg.get("users").unwrap(); assert_eq!(s.name, "users"); } #[test] fn test_register_duplicate_fails() { let mut reg = ProjectionRegistry::new(); reg.register(make_schema("events")).unwrap(); assert!(reg.register(make_schema("events")).is_err()); } #[test] fn test_upsert_replaces() { let mut reg = ProjectionRegistry::new(); reg.register(make_schema("s1")).unwrap(); let mut updated = make_schema("s1"); updated.description = Some("updated".into()); reg.upsert(updated).unwrap(); assert_eq!(reg.get("s1").unwrap().description.as_deref(), Some("updated")); } #[test] fn test_list_sorted() { let mut reg = ProjectionRegistry::new(); reg.register(make_schema("zoo")).unwrap(); reg.register(make_schema("alpha")).unwrap(); reg.register(make_schema("mango")).unwrap(); let names: Vec<&str> = reg.list().iter().map(|s| s.name.as_str()).collect(); assert_eq!(names, vec!["alpha", "mango", "zoo"]); } #[test] fn test_remove() { let mut reg = ProjectionRegistry::new(); reg.register(make_schema("temp")).unwrap(); assert!(reg.remove("temp")); assert!(!reg.remove("temp")); assert!(reg.get("temp").is_err()); } }