mockforge_vbr/
entities.rs1use 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#[derive(Debug, Clone)]
15pub struct Entity {
16 pub name: String,
18
19 pub schema: VbrSchemaDefinition,
21
22 pub table_name: String,
24
25 pub state_machine: Option<StateMachine>,
30}
31
32impl Entity {
33 pub fn new(name: String, schema: VbrSchemaDefinition) -> Self {
35 let table_name = name.to_lowercase() + "s"; Self {
37 name,
38 schema,
39 table_name,
40 state_machine: None,
41 }
42 }
43
44 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 pub fn set_state_machine(&mut self, state_machine: StateMachine) {
61 self.state_machine = Some(state_machine);
62 }
63
64 pub fn state_machine(&self) -> Option<&StateMachine> {
66 self.state_machine.as_ref()
67 }
68
69 pub fn has_state_machine(&self) -> bool {
71 self.state_machine.is_some()
72 }
73
74 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 let field_name = if let Some(name) = state_field_name {
92 name
93 } else {
94 if let Some(ref sm) = self.state_machine {
95 "status"
99 } else {
100 "status"
101 }
102 };
103
104 let field_exists = self.schema.base.fields.iter().any(|f| f.name == field_name);
106
107 if !field_exists {
108 warn!(
111 "State field '{}' not found in entity schema, attempting update anyway",
112 field_name
113 );
114 }
115
116 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 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 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 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 Ok(state_machine.can_transition(¤t_state, to_state))
188 }
189
190 pub fn name(&self) -> &str {
192 &self.name
193 }
194
195 pub fn table_name(&self) -> &str {
197 &self.table_name
198 }
199}
200
201#[derive(Clone)]
203pub struct EntityRegistry {
204 entities: HashMap<String, Entity>,
206}
207
208impl EntityRegistry {
209 pub fn new() -> Self {
211 Self {
212 entities: HashMap::new(),
213 }
214 }
215
216 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 pub fn get(&self, name: &str) -> Option<&Entity> {
228 self.entities.get(name)
229 }
230
231 pub fn list(&self) -> Vec<String> {
233 self.entities.keys().cloned().collect()
234 }
235
236 pub fn exists(&self, name: &str) -> bool {
238 self.entities.contains_key(name)
239 }
240
241 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}