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        // Default to "status" if not specified, or use resource_type-based naming
91        let field_name = if let Some(name) = state_field_name {
92            name
93        } else {
94            if let Some(ref sm) = self.state_machine {
95                // Use resource_type to derive field name (e.g., "Order" -> "order_status")
96                // We'll use a static string for now - in production, this would need to be
97                // stored or passed differently
98                "status"
99            } else {
100                "status"
101            }
102        };
103
104        // Check if the field exists in the schema
105        let field_exists = self.schema.base.fields.iter().any(|f| f.name == field_name);
106
107        if !field_exists {
108            // If field doesn't exist, we'll still try to update it
109            // (it might be a dynamic field or added later)
110            warn!(
111                "State field '{}' not found in entity schema, attempting update anyway",
112                field_name
113            );
114        }
115
116        // Update the state field in the database
117        let query = format!("UPDATE {} SET {} = ? WHERE id = ?", self.table_name, field_name);
118
119        database
120            .execute(
121                &query,
122                &[
123                    serde_json::Value::String(new_state.to_string()),
124                    serde_json::Value::String(record_id.to_string()),
125                ],
126            )
127            .await
128            .map_err(|e| Error::generic(format!("Failed to update entity state: {}", e)))?;
129
130        Ok(())
131    }
132
133    /// Get the current state of an entity record
134    ///
135    /// Reads the state field from the database for a specific record.
136    pub async fn get_current_state(
137        &self,
138        database: &dyn VirtualDatabase,
139        record_id: &str,
140        state_field_name: Option<&str>,
141    ) -> Result<Option<String>> {
142        let field_name = state_field_name.unwrap_or("status");
143
144        let query = format!("SELECT {} FROM {} WHERE id = ?", field_name, self.table_name);
145
146        let results = database
147            .query(&query, &[serde_json::Value::String(record_id.to_string())])
148            .await
149            .map_err(|e| Error::generic(format!("Failed to query entity state: {}", e)))?;
150
151        if let Some(row) = results.first() {
152            if let Some(value) = row.get(field_name) {
153                if let Some(state) = value.as_str() {
154                    return Ok(Some(state.to_string()));
155                }
156            }
157        }
158
159        Ok(None)
160    }
161
162    /// Check if a state transition is allowed for an entity record
163    ///
164    /// Validates that the transition from the current state to the new state
165    /// is allowed by the entity's state machine.
166    pub async fn can_transition(
167        &self,
168        database: &dyn VirtualDatabase,
169        record_id: &str,
170        to_state: &str,
171        state_field_name: Option<&str>,
172    ) -> Result<bool> {
173        let state_machine = self
174            .state_machine
175            .as_ref()
176            .ok_or_else(|| Error::generic("Entity does not have a state machine configured"))?;
177
178        // Get current state
179        let current_state = self
180            .get_current_state(database, record_id, state_field_name)
181            .await?
182            .ok_or_else(|| {
183                Error::generic(format!("Record '{}' not found or has no state", record_id))
184            })?;
185
186        // Check if transition is allowed
187        Ok(state_machine.can_transition(&current_state, to_state))
188    }
189
190    /// Get the entity name
191    pub fn name(&self) -> &str {
192        &self.name
193    }
194
195    /// Get the table name
196    pub fn table_name(&self) -> &str {
197        &self.table_name
198    }
199}
200
201/// Entity registry for managing all entities
202#[derive(Clone)]
203pub struct EntityRegistry {
204    /// Registered entities by name
205    entities: HashMap<String, Entity>,
206}
207
208impl EntityRegistry {
209    /// Create a new entity registry
210    pub fn new() -> Self {
211        Self {
212            entities: HashMap::new(),
213        }
214    }
215
216    /// Register an entity
217    pub fn register(&mut self, entity: Entity) -> Result<()> {
218        let name = entity.name.clone();
219        if self.entities.contains_key(&name) {
220            return Err(Error::generic(format!("Entity '{}' already registered", name)));
221        }
222        self.entities.insert(name, entity);
223        Ok(())
224    }
225
226    /// Get an entity by name
227    pub fn get(&self, name: &str) -> Option<&Entity> {
228        self.entities.get(name)
229    }
230
231    /// Get all entity names
232    pub fn list(&self) -> Vec<String> {
233        self.entities.keys().cloned().collect()
234    }
235
236    /// Check if an entity exists
237    pub fn exists(&self, name: &str) -> bool {
238        self.entities.contains_key(name)
239    }
240
241    /// Remove an entity
242    pub fn remove(&mut self, name: &str) -> Result<()> {
243        self.entities
244            .remove(name)
245            .ok_or_else(|| Error::generic(format!("Entity '{}' not found", name)))?;
246        Ok(())
247    }
248}
249
250impl Default for EntityRegistry {
251    fn default() -> Self {
252        Self::new()
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259    use mockforge_data::SchemaDefinition;
260
261    #[test]
262    fn test_entity_creation() {
263        let base_schema = SchemaDefinition::new("User".to_string());
264        let vbr_schema = VbrSchemaDefinition::new(base_schema);
265        let entity = Entity::new("User".to_string(), vbr_schema);
266
267        assert_eq!(entity.name(), "User");
268        assert_eq!(entity.table_name(), "users");
269    }
270
271    #[test]
272    fn test_entity_registry() {
273        let mut registry = EntityRegistry::new();
274
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!(registry.register(entity).is_ok());
280        assert!(registry.exists("User"));
281        assert!(registry.get("User").is_some());
282    }
283
284    #[test]
285    fn test_entity_registry_duplicate() {
286        let mut registry = EntityRegistry::new();
287
288        let base_schema1 = SchemaDefinition::new("User".to_string());
289        let vbr_schema1 = VbrSchemaDefinition::new(base_schema1);
290        let entity1 = Entity::new("User".to_string(), vbr_schema1);
291
292        let base_schema2 = SchemaDefinition::new("User".to_string());
293        let vbr_schema2 = VbrSchemaDefinition::new(base_schema2);
294        let entity2 = Entity::new("User".to_string(), vbr_schema2);
295
296        assert!(registry.register(entity1).is_ok());
297        assert!(registry.register(entity2).is_err());
298    }
299}