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
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[tokio::test]
383    async fn test_vbr_engine_creation() {
384        // Use Memory backend for tests to avoid file system issues
385        let config = VbrConfig::default().with_storage_backend(StorageBackend::Memory);
386        let engine = VbrEngine::new(config).await;
387        assert!(engine.is_ok());
388    }
389}