Major Changes:
- Moved Rhai support from rhai_support/ to rhai/ module
- Implemented context-based execution with signatory access control
- Added TypeRegistry for dynamic type registration and object creation
- Refactored engine to use context (Vec<String>) instead of instance
- Removed old runner binary (moved to runner_rust crate)
Rhai Module:
- engine.rs: Core Rhai engine with context-based get_context()
- functions.rs: Rhai function bindings (create_note, create_event, etc.)
- mod.rs: Module exports and organization
Store Improvements:
- TypeRegistry for registering object types and creators
- Generic store uses type registry for dynamic object creation
- Improved error handling and type safety
Documentation:
- RHAI_REFACTOR_COMPLETE.md: Refactoring details
- SIGNATORY_ACCESS_CONTROL.md: Context-based access control
- TYPE_REGISTRY_DESIGN.md: Type registry architecture
- REFACTORING_COMPLETE.md: Overall refactoring summary
- TESTS_COMPLETE.md: Testing documentation
Build Status: ✅ Compiles successfully with minor warnings
159 lines
4.8 KiB
Rust
159 lines
4.8 KiB
Rust
use crate::error::Result;
|
|
use crate::index::FieldIndex;
|
|
use crate::store::{HeroDbClient, Object, TypeRegistry};
|
|
use std::sync::Arc;
|
|
|
|
/// Generic storage layer for OSIRIS objects
|
|
#[derive(Debug)]
|
|
pub struct GenericStore {
|
|
client: HeroDbClient,
|
|
index: FieldIndex,
|
|
type_registry: Option<Arc<TypeRegistry>>,
|
|
}
|
|
|
|
impl GenericStore {
|
|
/// Create a new generic store
|
|
pub fn new(client: HeroDbClient) -> Self {
|
|
let index = FieldIndex::new(client.clone());
|
|
Self {
|
|
client,
|
|
index,
|
|
type_registry: None,
|
|
}
|
|
}
|
|
|
|
/// Create a new generic store with a type registry
|
|
pub fn with_registry(client: HeroDbClient, registry: Arc<TypeRegistry>) -> Self {
|
|
let index = FieldIndex::new(client.clone());
|
|
Self {
|
|
client,
|
|
index,
|
|
type_registry: Some(registry),
|
|
}
|
|
}
|
|
|
|
/// Set the type registry
|
|
pub fn set_registry(&mut self, registry: Arc<TypeRegistry>) {
|
|
self.type_registry = Some(registry);
|
|
}
|
|
|
|
/// Store an object
|
|
pub async fn put<T: Object>(&self, obj: &T) -> Result<()> {
|
|
// Serialize object to JSON
|
|
let json = obj.to_json()?;
|
|
let key = format!("obj:{}:{}", obj.namespace(), obj.id());
|
|
|
|
// Store in HeroDB
|
|
self.client.set(&key, &json).await?;
|
|
|
|
// Index the object
|
|
self.index_object(obj).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get an object by ID
|
|
pub async fn get<T: Object>(&self, ns: &str, id: &str) -> Result<T> {
|
|
let key = format!("obj:{}:{}", ns, id);
|
|
let json = self.client.get(&key).await?
|
|
.ok_or_else(|| crate::error::Error::NotFound(format!("Object {}:{}", ns, id)))?;
|
|
|
|
T::from_json(&json)
|
|
}
|
|
|
|
/// Get raw JSON data by ID (for generic access without type)
|
|
pub async fn get_raw(&self, ns: &str, id: &str) -> Result<String> {
|
|
let key = format!("obj:{}:{}", ns, id);
|
|
self.client.get(&key).await?
|
|
.ok_or_else(|| crate::error::Error::NotFound(format!("Object {}:{}", ns, id)))
|
|
}
|
|
|
|
/// Get the type registry if configured
|
|
pub fn type_registry(&self) -> Option<Arc<TypeRegistry>> {
|
|
self.type_registry.clone()
|
|
}
|
|
|
|
/// Delete an object
|
|
pub async fn delete<T: Object>(&self, obj: &T) -> Result<bool> {
|
|
let key = format!("obj:{}:{}", obj.namespace(), obj.id());
|
|
|
|
// Deindex first
|
|
self.deindex_object(obj).await?;
|
|
|
|
// Delete from HeroDB
|
|
self.client.del(&key).await
|
|
}
|
|
|
|
/// Check if an object exists
|
|
pub async fn exists(&self, ns: &str, id: &str) -> Result<bool> {
|
|
let key = format!("obj:{}:{}", ns, id);
|
|
self.client.exists(&key).await
|
|
}
|
|
|
|
/// Index an object
|
|
async fn index_object<T: Object>(&self, obj: &T) -> Result<()> {
|
|
let index_keys = obj.index_keys();
|
|
|
|
for key in index_keys {
|
|
let field_key = format!("idx:{}:{}:{}", obj.namespace(), key.name, key.value);
|
|
self.client.sadd(&field_key, obj.id()).await?;
|
|
}
|
|
|
|
// Add to scan index for full-text search
|
|
let scan_key = format!("scan:{}", obj.namespace());
|
|
self.client.sadd(&scan_key, obj.id()).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Deindex an object
|
|
async fn deindex_object<T: Object>(&self, obj: &T) -> Result<()> {
|
|
let index_keys = obj.index_keys();
|
|
|
|
for key in index_keys {
|
|
let field_key = format!("idx:{}:{}:{}", obj.namespace(), key.name, key.value);
|
|
self.client.srem(&field_key, obj.id()).await?;
|
|
}
|
|
|
|
// Remove from scan index
|
|
let scan_key = format!("scan:{}", obj.namespace());
|
|
self.client.srem(&scan_key, obj.id()).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get all IDs matching an index key
|
|
pub async fn get_ids_by_index(&self, ns: &str, field: &str, value: &str) -> Result<Vec<String>> {
|
|
let field_key = format!("idx:{}:{}:{}", ns, field, value);
|
|
self.client.smembers(&field_key).await
|
|
}
|
|
|
|
/// Get all IDs in a namespace
|
|
pub async fn get_all_ids(&self, ns: &str) -> Result<Vec<String>> {
|
|
let scan_key = format!("scan:{}", ns);
|
|
self.client.smembers(&scan_key).await
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::objects::Note;
|
|
|
|
#[tokio::test]
|
|
#[ignore]
|
|
async fn test_generic_store() {
|
|
let client = HeroDbClient::new("redis://localhost:6379", 1).unwrap();
|
|
let store = GenericStore::new(client);
|
|
|
|
let note = Note::new("test".to_string())
|
|
.set_title("Test Note")
|
|
.set_content("This is a test");
|
|
|
|
store.put(¬e).await.unwrap();
|
|
|
|
let retrieved: Note = store.get("test", note.id()).await.unwrap();
|
|
assert_eq!(retrieved.title, note.title);
|
|
}
|
|
}
|