mockforge_vbr/
lib.rs

1//! # MockForge Virtual Backend Reality (VBR) Engine
2//!
3//! The VBR engine creates stateful mock servers with persistent virtual databases,
4//! auto-generated CRUD APIs, relationship constraints, session management, and
5//! time-based data evolution.
6//!
7//! ## Overview
8//!
9//! VBR acts like a mini real backend with:
10//! - Persistent virtual database (SQLite, JSON, in-memory options)
11//! - CRUD APIs auto-generated from entity schemas
12//! - Relationship modeling and constraint enforcement
13//! - User session & auth emulation
14//! - Time-based data evolution (data aging, expiring sessions)
15//!
16//! ## Example Usage
17//!
18//! ```no_run
19//! use mockforge_vbr::{VbrEngine, VbrConfig};
20//!
21//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
22//! let config = VbrConfig::default()
23//!     .with_storage_backend(mockforge_vbr::StorageBackend::Sqlite {
24//!         path: "./data/vbr.db".into(),
25//!     });
26//!
27//! let engine = VbrEngine::new(config).await?;
28//! // Define entities and generate living API
29//! # Ok(())
30//! # }
31//! ```
32
33// Re-export error types from mockforge-core
34pub use mockforge_core::{Error, Result};
35
36use std::collections::HashMap;
37use std::sync::Arc;
38
39// Core modules
40pub mod config;
41pub mod database;
42pub mod entities;
43pub mod schema;
44
45// Database and migration modules
46pub mod constraints;
47pub mod migration;
48
49// API generation modules
50pub mod api_generator;
51pub mod handlers;
52
53// Session and auth modules
54pub mod auth;
55pub mod session;
56
57// Time-based features
58pub mod aging;
59pub mod mutation_rules;
60pub mod scheduler;
61
62// Integration module
63pub mod integration;
64
65// OpenAPI integration
66pub mod openapi;
67
68// Data seeding
69pub mod seeding;
70
71// ID generation
72pub mod id_generation;
73
74// Snapshots
75pub mod snapshots;
76
77// Re-export commonly used types
78pub use config::{StorageBackend, VbrConfig};
79pub use database::VirtualDatabase;
80pub use entities::{Entity, EntityRegistry};
81pub use mutation_rules::{
82    ComparisonOperator, MutationOperation, MutationRule, MutationRuleManager, MutationTrigger,
83};
84pub use schema::{ManyToManyDefinition, VbrSchemaDefinition};
85pub use snapshots::{SnapshotMetadata, TimeTravelSnapshotState};
86
87/// Main VBR engine
88pub struct VbrEngine {
89    /// Configuration
90    config: VbrConfig,
91    /// Virtual database instance (stored in Arc for sharing)
92    database: Arc<dyn VirtualDatabase + Send + Sync>,
93    /// Entity registry
94    registry: EntityRegistry,
95}
96
97impl VbrEngine {
98    /// Create a new VBR engine with the given configuration
99    pub async fn new(config: VbrConfig) -> Result<Self> {
100        // Initialize virtual database (already in Arc)
101        let database = database::create_database(&config.storage).await?;
102
103        // Initialize entity registry
104        let registry = EntityRegistry::new();
105
106        Ok(Self {
107            config,
108            database,
109            registry,
110        })
111    }
112
113    /// Get the configuration
114    pub fn config(&self) -> &VbrConfig {
115        &self.config
116    }
117
118    /// Get the virtual database as Arc for sharing
119    pub fn database_arc(&self) -> Arc<dyn VirtualDatabase + Send + Sync> {
120        Arc::clone(&self.database)
121    }
122
123    /// Get a reference to the virtual database
124    pub fn database(&self) -> &dyn VirtualDatabase {
125        self.database.as_ref()
126    }
127
128    /// Get the entity registry
129    pub fn registry(&self) -> &EntityRegistry {
130        &self.registry
131    }
132
133    /// Get mutable access to the entity registry
134    pub fn registry_mut(&mut self) -> &mut EntityRegistry {
135        &mut self.registry
136    }
137
138    /// Create VBR engine from an OpenAPI specification
139    ///
140    /// This method automatically:
141    /// - Parses the OpenAPI 3.x specification
142    /// - Extracts entity schemas from `components/schemas`
143    /// - Auto-detects primary keys and foreign keys
144    /// - Registers all entities in the engine
145    /// - Creates database tables for all entities
146    ///
147    /// # Arguments
148    /// * `config` - VBR configuration
149    /// * `openapi_content` - OpenAPI specification content (JSON or YAML)
150    ///
151    /// # Returns
152    /// VBR engine with entities registered and database initialized
153    pub async fn from_openapi(
154        config: VbrConfig,
155        openapi_content: &str,
156    ) -> Result<(Self, openapi::OpenApiConversionResult)> {
157        // Parse OpenAPI content (JSON or YAML)
158        let json_value: serde_json::Value = if openapi_content.trim_start().starts_with('{') {
159            serde_json::from_str(openapi_content)
160                .map_err(|e| Error::generic(format!("Failed to parse OpenAPI JSON: {}", e)))?
161        } else {
162            serde_yaml::from_str(openapi_content)
163                .map_err(|e| Error::generic(format!("Failed to parse OpenAPI YAML: {}", e)))?
164        };
165
166        // Load OpenAPI spec
167        let spec = mockforge_core::openapi::OpenApiSpec::from_json(json_value)
168            .map_err(|e| Error::generic(format!("Failed to load OpenAPI spec: {}", e)))?;
169
170        // Validate spec
171        spec.validate()
172            .map_err(|e| Error::generic(format!("Invalid OpenAPI specification: {}", e)))?;
173
174        // Convert OpenAPI to VBR entities
175        let conversion_result = openapi::convert_openapi_to_vbr(&spec)?;
176
177        // Create engine
178        let mut engine = Self::new(config).await?;
179
180        // Register entities and create database tables
181        for (entity_name, vbr_schema) in &conversion_result.entities {
182            let entity = entities::Entity::new(entity_name.clone(), vbr_schema.clone());
183            engine.registry_mut().register(entity.clone())?;
184
185            // Create database table for this entity
186            migration::create_table_for_entity(engine.database.as_ref(), &entity).await?;
187        }
188
189        Ok((engine, conversion_result))
190    }
191
192    /// Load VBR engine from an OpenAPI file
193    ///
194    /// # Arguments
195    /// * `config` - VBR configuration
196    /// * `file_path` - Path to OpenAPI specification file (JSON or YAML)
197    ///
198    /// # Returns
199    /// VBR engine with entities registered and database initialized
200    pub async fn from_openapi_file<P: AsRef<std::path::Path>>(
201        config: VbrConfig,
202        file_path: P,
203    ) -> Result<(Self, openapi::OpenApiConversionResult)> {
204        let content = tokio::fs::read_to_string(file_path.as_ref())
205            .await
206            .map_err(|e| Error::generic(format!("Failed to read OpenAPI file: {}", e)))?;
207
208        Self::from_openapi(config, &content).await
209    }
210
211    /// Seed entity with data
212    ///
213    /// # Arguments
214    /// * `entity_name` - Name of the entity to seed
215    /// * `records` - Records to insert
216    pub async fn seed_entity(
217        &self,
218        entity_name: &str,
219        records: &[HashMap<String, serde_json::Value>],
220    ) -> Result<usize> {
221        seeding::seed_entity(self.database.as_ref(), &self.registry, entity_name, records).await
222    }
223
224    /// Seed all entities with data (respects dependencies)
225    ///
226    /// # Arguments
227    /// * `seed_data` - Seed data organized by entity name
228    pub async fn seed_all(&self, seed_data: &seeding::SeedData) -> Result<HashMap<String, usize>> {
229        seeding::seed_all(self.database.as_ref(), &self.registry, seed_data).await
230    }
231
232    /// Load and seed from a file
233    ///
234    /// # Arguments
235    /// * `file_path` - Path to seed file (JSON or YAML)
236    pub async fn seed_from_file<P: AsRef<std::path::Path>>(
237        &self,
238        file_path: P,
239    ) -> Result<HashMap<String, usize>> {
240        let seed_data = seeding::load_seed_file(file_path).await?;
241        self.seed_all(&seed_data).await
242    }
243
244    /// Clear all data from an entity
245    ///
246    /// # Arguments
247    /// * `entity_name` - Name of the entity to clear
248    pub async fn clear_entity(&self, entity_name: &str) -> Result<()> {
249        seeding::clear_entity(self.database.as_ref(), &self.registry, entity_name).await
250    }
251
252    /// Clear all data from all entities
253    pub async fn clear_all(&self) -> Result<()> {
254        seeding::clear_all(self.database.as_ref(), &self.registry).await
255    }
256
257    /// Create a snapshot of the current database state
258    ///
259    /// # Arguments
260    /// * `name` - Name for the snapshot
261    /// * `description` - Optional description
262    /// * `snapshots_dir` - Directory to store snapshots
263    pub async fn create_snapshot<P: AsRef<std::path::Path>>(
264        &self,
265        name: &str,
266        description: Option<String>,
267        snapshots_dir: P,
268    ) -> Result<snapshots::SnapshotMetadata> {
269        let manager = snapshots::SnapshotManager::new(snapshots_dir);
270        manager
271            .create_snapshot(name, description, self.database.as_ref(), &self.registry)
272            .await
273    }
274
275    /// Create a snapshot with time travel state
276    ///
277    /// # Arguments
278    /// * `name` - Name for the snapshot
279    /// * `description` - Optional description
280    /// * `snapshots_dir` - Directory to store snapshots
281    /// * `include_time_travel` - Whether to include time travel state
282    /// * `time_travel_state` - Optional time travel state to include
283    pub async fn create_snapshot_with_time_travel<P: AsRef<std::path::Path>>(
284        &self,
285        name: &str,
286        description: Option<String>,
287        snapshots_dir: P,
288        include_time_travel: bool,
289        time_travel_state: Option<snapshots::TimeTravelSnapshotState>,
290    ) -> Result<snapshots::SnapshotMetadata> {
291        let manager = snapshots::SnapshotManager::new(snapshots_dir);
292        manager
293            .create_snapshot_with_time_travel(
294                name,
295                description,
296                self.database.as_ref(),
297                &self.registry,
298                include_time_travel,
299                time_travel_state,
300            )
301            .await
302    }
303
304    /// Restore a snapshot
305    ///
306    /// # Arguments
307    /// * `name` - Name of the snapshot to restore
308    /// * `snapshots_dir` - Directory where snapshots are stored
309    pub async fn restore_snapshot<P: AsRef<std::path::Path>>(
310        &self,
311        name: &str,
312        snapshots_dir: P,
313    ) -> Result<()> {
314        let manager = snapshots::SnapshotManager::new(snapshots_dir);
315        manager.restore_snapshot(name, self.database.as_ref(), &self.registry).await
316    }
317
318    /// Restore a snapshot with time travel state
319    ///
320    /// # Arguments
321    /// * `name` - Name of the snapshot to restore
322    /// * `snapshots_dir` - Directory where snapshots are stored
323    /// * `restore_time_travel` - Whether to restore time travel state
324    /// * `time_travel_restore_callback` - Optional callback to restore time travel state
325    pub async fn restore_snapshot_with_time_travel<P, F>(
326        &self,
327        name: &str,
328        snapshots_dir: P,
329        restore_time_travel: bool,
330        time_travel_restore_callback: Option<F>,
331    ) -> Result<()>
332    where
333        P: AsRef<std::path::Path>,
334        F: FnOnce(snapshots::TimeTravelSnapshotState) -> Result<()>,
335    {
336        let manager = snapshots::SnapshotManager::new(snapshots_dir);
337        manager
338            .restore_snapshot_with_time_travel(
339                name,
340                self.database.as_ref(),
341                &self.registry,
342                restore_time_travel,
343                time_travel_restore_callback,
344            )
345            .await
346    }
347
348    /// List all snapshots
349    ///
350    /// # Arguments
351    /// * `snapshots_dir` - Directory where snapshots are stored
352    pub async fn list_snapshots<P: AsRef<std::path::Path>>(
353        snapshots_dir: P,
354    ) -> Result<Vec<snapshots::SnapshotMetadata>> {
355        let manager = snapshots::SnapshotManager::new(snapshots_dir);
356        manager.list_snapshots().await
357    }
358
359    /// Delete a snapshot
360    ///
361    /// # Arguments
362    /// * `name` - Name of the snapshot to delete
363    /// * `snapshots_dir` - Directory where snapshots are stored
364    pub async fn delete_snapshot<P: AsRef<std::path::Path>>(
365        name: &str,
366        snapshots_dir: P,
367    ) -> Result<()> {
368        let manager = snapshots::SnapshotManager::new(snapshots_dir);
369        manager.delete_snapshot(name).await
370    }
371
372    /// Reset database to empty state
373    pub async fn reset(&self) -> Result<()> {
374        snapshots::reset_database(self.database.as_ref(), &self.registry).await
375    }
376
377    /// Export the complete database state as JSON
378    ///
379    /// This exports all entity data in a format suitable for snapshots.
380    pub async fn export_state(&self) -> Result<serde_json::Value> {
381        let mut state = serde_json::Map::new();
382        let mut entities = serde_json::Map::new();
383
384        for entity_name in self.registry.list() {
385            if let Some(entity) = self.registry.get(&entity_name) {
386                let table_name = entity.table_name();
387                let query = format!("SELECT * FROM {}", table_name);
388                let records = self.database.query(&query, &[]).await?;
389                entities.insert(
390                    entity_name.clone(),
391                    serde_json::Value::Array(
392                        records.into_iter().map(|r| serde_json::json!(r)).collect(),
393                    ),
394                );
395            }
396        }
397
398        state.insert("entities".to_string(), serde_json::Value::Object(entities));
399        state.insert(
400            "storage_backend".to_string(),
401            serde_json::Value::String(self.database.connection_info()),
402        );
403        state.insert(
404            "exported_at".to_string(),
405            serde_json::Value::String(chrono::Utc::now().to_rfc3339()),
406        );
407
408        Ok(serde_json::Value::Object(state))
409    }
410
411    /// Import database state from JSON
412    ///
413    /// This restores entity data from a previous snapshot export.
414    pub async fn import_state(&self, state: serde_json::Value) -> Result<()> {
415        // First, clear existing data
416        self.reset().await?;
417
418        // Extract entities from state
419        let entities = state
420            .get("entities")
421            .and_then(|e| e.as_object())
422            .ok_or_else(|| Error::generic("Invalid state format: missing entities"))?;
423
424        // Import each entity
425        for (entity_name, records_value) in entities {
426            let records = records_value.as_array().ok_or_else(|| {
427                Error::generic(format!("Invalid records for entity {}", entity_name))
428            })?;
429
430            if let Some(entity) = self.registry.get(entity_name) {
431                let table_name = entity.table_name();
432
433                for record in records {
434                    let record_obj = record
435                        .as_object()
436                        .ok_or_else(|| Error::generic("Invalid record format"))?;
437
438                    let fields: Vec<String> = record_obj.keys().cloned().collect();
439                    let placeholders: Vec<String> =
440                        (0..fields.len()).map(|_| "?".to_string()).collect();
441                    let values: Vec<serde_json::Value> = fields
442                        .iter()
443                        .map(|f| record_obj.get(f).cloned().unwrap_or(serde_json::Value::Null))
444                        .collect();
445
446                    let query = format!(
447                        "INSERT INTO {} ({}) VALUES ({})",
448                        table_name,
449                        fields.join(", "),
450                        placeholders.join(", ")
451                    );
452
453                    self.database.execute(&query, &values).await?;
454                }
455            }
456        }
457
458        Ok(())
459    }
460
461    /// Get a summary of the current state
462    pub async fn state_summary(&self) -> String {
463        let mut total_records = 0usize;
464        let entity_count = self.registry.list().len();
465
466        for entity_name in self.registry.list() {
467            if let Some(entity) = self.registry.get(&entity_name) {
468                let table_name = entity.table_name();
469                let count_query = format!("SELECT COUNT(*) as count FROM {}", table_name);
470                if let Ok(results) = self.database.query(&count_query, &[]).await {
471                    if let Some(count) =
472                        results.first().and_then(|r| r.get("count")).and_then(|v| v.as_u64())
473                    {
474                        total_records += count as usize;
475                    }
476                }
477            }
478        }
479
480        format!("{} entities, {} records", entity_count, total_records)
481    }
482}
483
484// Implement ProtocolStateExporter trait for VbrEngine
485use async_trait::async_trait;
486use mockforge_core::snapshots::ProtocolStateExporter;
487
488#[async_trait]
489impl ProtocolStateExporter for VbrEngine {
490    fn protocol_name(&self) -> &str {
491        "vbr"
492    }
493
494    async fn export_state(&self) -> Result<serde_json::Value> {
495        VbrEngine::export_state(self).await
496    }
497
498    async fn import_state(&self, state: serde_json::Value) -> Result<()> {
499        VbrEngine::import_state(self, state).await
500    }
501
502    async fn state_summary(&self) -> String {
503        VbrEngine::state_summary(self).await
504    }
505}
506
507#[cfg(test)]
508mod tests {
509    use super::*;
510
511    #[tokio::test]
512    async fn test_vbr_engine_creation() {
513        // Use Memory backend for tests to avoid file system issues
514        let config = VbrConfig::default().with_storage_backend(StorageBackend::Memory);
515        let engine = VbrEngine::new(config).await;
516        assert!(engine.is_ok());
517    }
518}