Skip to main content

mockforge_vbr/
entities.rs

1//! Entity management and registry
2//!
3//! This module provides entity definition, management, and registry functionality
4//! for tracking all entities in the VBR engine.
5
6use crate::database::VirtualDatabase;
7use crate::schema::VbrSchemaDefinition;
8use crate::{Error, Result};
9use mockforge_core::intelligent_behavior::rules::StateMachine;
10use std::collections::HashMap;
11use tracing::warn;
12
13/// Entity definition
14#[derive(Debug, Clone)]
15pub struct Entity {
16    /// Entity name
17    pub name: String,
18
19    /// Schema definition
20    pub schema: VbrSchemaDefinition,
21
22    /// Table name (derived from entity name)
23    pub table_name: String,
24
25    /// Optional state machine for this entity
26    ///
27    /// If set, the entity can participate in state machine transitions.
28    /// The state machine defines valid state transitions and lifecycle management.
29    pub state_machine: Option<StateMachine>,
30}
31
32impl Entity {
33    /// Create a new entity
34    pub fn new(name: String, schema: VbrSchemaDefinition) -> Self {
35        let table_name = name.to_lowercase() + "s"; // Simple pluralization
36        Self {
37            name,
38            schema,
39            table_name,
40            state_machine: None,
41        }
42    }
43
44    /// Create a new entity with a state machine
45    pub fn with_state_machine(
46        name: String,
47        schema: VbrSchemaDefinition,
48        state_machine: StateMachine,
49    ) -> Self {
50        let table_name = name.to_lowercase() + "s";
51        Self {
52            name,
53            schema,
54            table_name,
55            state_machine: Some(state_machine),
56        }
57    }
58
59    /// Set the state machine for this entity
60    pub fn set_state_machine(&mut self, state_machine: StateMachine) {
61        self.state_machine = Some(state_machine);
62    }
63
64    /// Get the state machine for this entity
65    pub fn state_machine(&self) -> Option<&StateMachine> {
66        self.state_machine.as_ref()
67    }
68
69    /// Check if this entity has a state machine
70    pub fn has_state_machine(&self) -> bool {
71        self.state_machine.is_some()
72    }
73
74    /// Apply a state transition to an entity record
75    ///
76    /// Updates the entity's state field in the database based on the state machine transition.
77    /// The state field name is typically derived from the state machine's resource_type
78    /// (e.g., "status" for "Order" resource type).
79    ///
80    /// This method should be called after validating that the transition is allowed
81    /// by the state machine.
82    pub async fn apply_state_transition(
83        &self,
84        database: &dyn VirtualDatabase,
85        record_id: &str,
86        new_state: &str,
87        state_field_name: Option<&str>,
88    ) -> Result<()> {
89        // Determine state field name:
90        // 1. Use the explicitly passed name if provided
91        // 2. Look for a matching field in the schema (e.g., "order_status" for resource_type "Order")
92        // 3. Fall back to "status"
93        let derived_field;
94        let field_name = if let Some(name) = state_field_name {
95            name
96        } else if let Some(ref sm) = self.state_machine {
97            // Derive candidate field name: "Order" -> "order_status"
98            let candidate = format!("{}_status", sm.resource_type.to_ascii_lowercase());
99            // Check if the derived field exists in the schema
100            if self.schema.base.fields.iter().any(|f| f.name == candidate) {
101                derived_field = candidate;
102                &derived_field
103            } else {
104                "status"
105            }
106        } else {
107            "status"
108        };
109
110        // Check if the field exists in the schema
111        let field_exists = self.schema.base.fields.iter().any(|f| f.name == field_name);
112
113        if !field_exists {
114            // If field doesn't exist, we'll still try to update it
115            // (it might be a dynamic field or added later)
116            warn!(
117                "State field '{}' not found in entity schema, attempting update anyway",
118                field_name
119            );
120        }
121
122        // Update the state field in the database
123        let query = format!("UPDATE {} SET {} = ? WHERE id = ?", self.table_name, field_name);
124
125        database
126            .execute(
127                &query,
128                &[
129                    serde_json::Value::String(new_state.to_string()),
130                    serde_json::Value::String(record_id.to_string()),
131                ],
132            )
133            .await
134            .map_err(|e| Error::generic(format!("Failed to update entity state: {}", e)))?;
135
136        Ok(())
137    }
138
139    /// Get the current state of an entity record
140    ///
141    /// Reads the state field from the database for a specific record.
142    pub async fn get_current_state(
143        &self,
144        database: &dyn VirtualDatabase,
145        record_id: &str,
146        state_field_name: Option<&str>,
147    ) -> Result<Option<String>> {
148        let field_name = state_field_name.unwrap_or("status");
149
150        let query = format!("SELECT {} FROM {} WHERE id = ?", field_name, self.table_name);
151
152        let results = database
153            .query(&query, &[serde_json::Value::String(record_id.to_string())])
154            .await
155            .map_err(|e| Error::generic(format!("Failed to query entity state: {}", e)))?;
156
157        if let Some(row) = results.first() {
158            if let Some(value) = row.get(field_name) {
159                if let Some(state) = value.as_str() {
160                    return Ok(Some(state.to_string()));
161                }
162            }
163        }
164
165        Ok(None)
166    }
167
168    /// Check if a state transition is allowed for an entity record
169    ///
170    /// Validates that the transition from the current state to the new state
171    /// is allowed by the entity's state machine.
172    pub async fn can_transition(
173        &self,
174        database: &dyn VirtualDatabase,
175        record_id: &str,
176        to_state: &str,
177        state_field_name: Option<&str>,
178    ) -> Result<bool> {
179        let state_machine = self
180            .state_machine
181            .as_ref()
182            .ok_or_else(|| Error::generic("Entity does not have a state machine configured"))?;
183
184        // Get current state
185        let current_state = self
186            .get_current_state(database, record_id, state_field_name)
187            .await?
188            .ok_or_else(|| {
189                Error::generic(format!("Record '{}' not found or has no state", record_id))
190            })?;
191
192        // Check if transition is allowed
193        Ok(state_machine.can_transition(&current_state, to_state))
194    }
195
196    /// Get the entity name
197    pub fn name(&self) -> &str {
198        &self.name
199    }
200
201    /// Get the table name
202    pub fn table_name(&self) -> &str {
203        &self.table_name
204    }
205}
206
207/// Entity registry for managing all entities
208#[derive(Clone)]
209pub struct EntityRegistry {
210    /// Registered entities by name
211    entities: HashMap<String, Entity>,
212}
213
214impl EntityRegistry {
215    /// Create a new entity registry
216    pub fn new() -> Self {
217        Self {
218            entities: HashMap::new(),
219        }
220    }
221
222    /// Register an entity
223    pub fn register(&mut self, entity: Entity) -> Result<()> {
224        let name = entity.name.clone();
225        if self.entities.contains_key(&name) {
226            return Err(Error::generic(format!("Entity '{}' already registered", name)));
227        }
228        self.entities.insert(name, entity);
229        Ok(())
230    }
231
232    /// Get an entity by name
233    pub fn get(&self, name: &str) -> Option<&Entity> {
234        self.entities.get(name)
235    }
236
237    /// Get all entity names
238    pub fn list(&self) -> Vec<String> {
239        self.entities.keys().cloned().collect()
240    }
241
242    /// Check if an entity exists
243    pub fn exists(&self, name: &str) -> bool {
244        self.entities.contains_key(name)
245    }
246
247    /// Remove an entity
248    pub fn remove(&mut self, name: &str) -> Result<()> {
249        self.entities
250            .remove(name)
251            .ok_or_else(|| Error::generic(format!("Entity '{}' not found", name)))?;
252        Ok(())
253    }
254}
255
256impl Default for EntityRegistry {
257    fn default() -> Self {
258        Self::new()
259    }
260}
261
262#[cfg(test)]
263mod tests {
264    use super::*;
265    use mockforge_data::SchemaDefinition;
266
267    fn create_test_schema(name: &str) -> VbrSchemaDefinition {
268        let base_schema = SchemaDefinition::new(name.to_string());
269        VbrSchemaDefinition::new(base_schema)
270    }
271
272    // Entity tests
273    #[test]
274    fn test_entity_creation() {
275        let base_schema = SchemaDefinition::new("User".to_string());
276        let vbr_schema = VbrSchemaDefinition::new(base_schema);
277        let entity = Entity::new("User".to_string(), vbr_schema);
278
279        assert_eq!(entity.name(), "User");
280        assert_eq!(entity.table_name(), "users");
281    }
282
283    #[test]
284    fn test_entity_table_name_pluralization() {
285        let entity = Entity::new("Order".to_string(), create_test_schema("Order"));
286        assert_eq!(entity.table_name(), "orders");
287
288        let entity2 = Entity::new("Product".to_string(), create_test_schema("Product"));
289        assert_eq!(entity2.table_name(), "products");
290    }
291
292    #[test]
293    fn test_entity_table_name_lowercase() {
294        let entity = Entity::new("UserProfile".to_string(), create_test_schema("UserProfile"));
295        assert_eq!(entity.table_name(), "userprofiles");
296    }
297
298    #[test]
299    fn test_entity_no_state_machine() {
300        let entity = Entity::new("Item".to_string(), create_test_schema("Item"));
301        assert!(!entity.has_state_machine());
302        assert!(entity.state_machine().is_none());
303    }
304
305    #[test]
306    fn test_entity_clone() {
307        let entity = Entity::new("Test".to_string(), create_test_schema("Test"));
308        let cloned = entity.clone();
309        assert_eq!(entity.name(), cloned.name());
310        assert_eq!(entity.table_name(), cloned.table_name());
311    }
312
313    #[test]
314    fn test_entity_debug() {
315        let entity = Entity::new("DebugEntity".to_string(), create_test_schema("DebugEntity"));
316        let debug = format!("{:?}", entity);
317        assert!(debug.contains("Entity"));
318        assert!(debug.contains("DebugEntity"));
319    }
320
321    #[test]
322    fn test_entity_name_getter() {
323        let entity = Entity::new("Customer".to_string(), create_test_schema("Customer"));
324        assert_eq!(entity.name(), "Customer");
325        assert_eq!(entity.name, "Customer");
326    }
327
328    // EntityRegistry tests
329    #[test]
330    fn test_entity_registry() {
331        let mut registry = EntityRegistry::new();
332
333        let base_schema = SchemaDefinition::new("User".to_string());
334        let vbr_schema = VbrSchemaDefinition::new(base_schema);
335        let entity = Entity::new("User".to_string(), vbr_schema);
336
337        assert!(registry.register(entity).is_ok());
338        assert!(registry.exists("User"));
339        assert!(registry.get("User").is_some());
340    }
341
342    #[test]
343    fn test_entity_registry_duplicate() {
344        let mut registry = EntityRegistry::new();
345
346        let base_schema1 = SchemaDefinition::new("User".to_string());
347        let vbr_schema1 = VbrSchemaDefinition::new(base_schema1);
348        let entity1 = Entity::new("User".to_string(), vbr_schema1);
349
350        let base_schema2 = SchemaDefinition::new("User".to_string());
351        let vbr_schema2 = VbrSchemaDefinition::new(base_schema2);
352        let entity2 = Entity::new("User".to_string(), vbr_schema2);
353
354        assert!(registry.register(entity1).is_ok());
355        assert!(registry.register(entity2).is_err());
356    }
357
358    #[test]
359    fn test_entity_registry_default() {
360        let registry = EntityRegistry::default();
361        assert!(registry.list().is_empty());
362    }
363
364    #[test]
365    fn test_entity_registry_new() {
366        let registry = EntityRegistry::new();
367        assert!(registry.list().is_empty());
368        assert!(!registry.exists("Anything"));
369    }
370
371    #[test]
372    fn test_entity_registry_get_nonexistent() {
373        let registry = EntityRegistry::new();
374        assert!(registry.get("NonExistent").is_none());
375    }
376
377    #[test]
378    fn test_entity_registry_list() {
379        let mut registry = EntityRegistry::new();
380
381        registry
382            .register(Entity::new("User".to_string(), create_test_schema("User")))
383            .unwrap();
384        registry
385            .register(Entity::new("Order".to_string(), create_test_schema("Order")))
386            .unwrap();
387        registry
388            .register(Entity::new("Product".to_string(), create_test_schema("Product")))
389            .unwrap();
390
391        let list = registry.list();
392        assert_eq!(list.len(), 3);
393        assert!(list.contains(&"User".to_string()));
394        assert!(list.contains(&"Order".to_string()));
395        assert!(list.contains(&"Product".to_string()));
396    }
397
398    #[test]
399    fn test_entity_registry_exists() {
400        let mut registry = EntityRegistry::new();
401        registry
402            .register(Entity::new("User".to_string(), create_test_schema("User")))
403            .unwrap();
404
405        assert!(registry.exists("User"));
406        assert!(!registry.exists("Order"));
407        assert!(!registry.exists("user")); // Case sensitive
408    }
409
410    #[test]
411    fn test_entity_registry_remove() {
412        let mut registry = EntityRegistry::new();
413        registry
414            .register(Entity::new("User".to_string(), create_test_schema("User")))
415            .unwrap();
416
417        assert!(registry.exists("User"));
418        assert!(registry.remove("User").is_ok());
419        assert!(!registry.exists("User"));
420    }
421
422    #[test]
423    fn test_entity_registry_remove_nonexistent() {
424        let mut registry = EntityRegistry::new();
425        let result = registry.remove("NonExistent");
426        assert!(result.is_err());
427    }
428
429    #[test]
430    fn test_entity_registry_clone() {
431        let mut registry = EntityRegistry::new();
432        registry
433            .register(Entity::new("User".to_string(), create_test_schema("User")))
434            .unwrap();
435
436        let cloned = registry.clone();
437        assert!(cloned.exists("User"));
438        assert_eq!(cloned.list().len(), 1);
439    }
440
441    #[test]
442    fn test_entity_registry_multiple_operations() {
443        let mut registry = EntityRegistry::new();
444
445        // Register multiple entities
446        registry
447            .register(Entity::new("A".to_string(), create_test_schema("A")))
448            .unwrap();
449        registry
450            .register(Entity::new("B".to_string(), create_test_schema("B")))
451            .unwrap();
452
453        // Verify
454        assert_eq!(registry.list().len(), 2);
455        assert!(registry.exists("A"));
456        assert!(registry.exists("B"));
457
458        // Remove one
459        registry.remove("A").unwrap();
460        assert_eq!(registry.list().len(), 1);
461        assert!(!registry.exists("A"));
462        assert!(registry.exists("B"));
463
464        // Add another
465        registry
466            .register(Entity::new("C".to_string(), create_test_schema("C")))
467            .unwrap();
468        assert_eq!(registry.list().len(), 2);
469    }
470
471    #[test]
472    fn test_entity_registry_get_returns_reference() {
473        let mut registry = EntityRegistry::new();
474        registry
475            .register(Entity::new("User".to_string(), create_test_schema("User")))
476            .unwrap();
477
478        let entity = registry.get("User").unwrap();
479        assert_eq!(entity.name(), "User");
480        assert_eq!(entity.table_name(), "users");
481    }
482}