mockforge_vbr/
schema.rs

1//! VBR schema extensions
2//!
3//! This module extends the SchemaDefinition from mockforge-data with VBR-specific
4//! metadata including primary keys, foreign keys, indexes, unique constraints,
5//! and auto-generation rules.
6
7use mockforge_data::SchemaDefinition;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11
12/// VBR-specific schema metadata that extends SchemaDefinition
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct VbrSchemaDefinition {
15    /// Base schema definition from mockforge-data
16    #[serde(flatten)]
17    pub base: SchemaDefinition,
18
19    /// Primary key field name(s)
20    pub primary_key: Vec<String>,
21
22    /// Foreign key relationships
23    pub foreign_keys: Vec<ForeignKeyDefinition>,
24
25    /// Index definitions
26    pub indexes: Vec<IndexDefinition>,
27
28    /// Unique constraints
29    pub unique_constraints: Vec<UniqueConstraint>,
30
31    /// Auto-generation rules for fields
32    pub auto_generation: HashMap<String, AutoGenerationRule>,
33
34    /// Many-to-many relationships
35    pub many_to_many: Vec<ManyToManyDefinition>,
36}
37
38/// Foreign key relationship definition
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ForeignKeyDefinition {
41    /// Field name in this entity
42    pub field: String,
43
44    /// Target entity name
45    pub target_entity: String,
46
47    /// Target field name (usually "id")
48    pub target_field: String,
49
50    /// Cascade action on delete
51    #[serde(default)]
52    pub on_delete: CascadeAction,
53
54    /// Cascade action on update
55    #[serde(default)]
56    pub on_update: CascadeAction,
57}
58
59/// Many-to-many relationship definition
60///
61/// Represents a many-to-many relationship between two entities using a junction table.
62/// For example, Users and Roles with a user_roles junction table.
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ManyToManyDefinition {
65    /// First entity name
66    pub entity_a: String,
67
68    /// Second entity name
69    pub entity_b: String,
70
71    /// Junction table name (auto-generated if not provided)
72    /// Format: "{entity_a}_{entity_b}" or "{entity_b}_{entity_a}" (alphabetically sorted)
73    pub junction_table: Option<String>,
74
75    /// Foreign key field name in junction table pointing to entity_a
76    pub entity_a_field: String,
77
78    /// Foreign key field name in junction table pointing to entity_b
79    pub entity_b_field: String,
80
81    /// Cascade action on delete for entity_a
82    #[serde(default)]
83    pub on_delete_a: CascadeAction,
84
85    /// Cascade action on delete for entity_b
86    #[serde(default)]
87    pub on_delete_b: CascadeAction,
88}
89
90impl ManyToManyDefinition {
91    /// Create a new many-to-many relationship definition
92    pub fn new(entity_a: String, entity_b: String) -> Self {
93        // Auto-generate junction table name (alphabetically sorted)
94        let (field_a, field_b) = if entity_a.to_lowercase() < entity_b.to_lowercase() {
95            (
96                format!("{}_id", entity_a.to_lowercase()),
97                format!("{}_id", entity_b.to_lowercase()),
98            )
99        } else {
100            (
101                format!("{}_id", entity_b.to_lowercase()),
102                format!("{}_id", entity_a.to_lowercase()),
103            )
104        };
105
106        let junction_table = if entity_a.to_lowercase() < entity_b.to_lowercase() {
107            Some(format!("{}_{}", entity_a.to_lowercase(), entity_b.to_lowercase()))
108        } else {
109            Some(format!("{}_{}", entity_b.to_lowercase(), entity_a.to_lowercase()))
110        };
111
112        Self {
113            entity_a: entity_a.clone(),
114            entity_b: entity_b.clone(),
115            junction_table,
116            entity_a_field: format!("{}_id", entity_a.to_lowercase()),
117            entity_b_field: format!("{}_id", entity_b.to_lowercase()),
118            on_delete_a: CascadeAction::Cascade,
119            on_delete_b: CascadeAction::Cascade,
120        }
121    }
122
123    /// Set the junction table name
124    pub fn with_junction_table(mut self, table_name: String) -> Self {
125        self.junction_table = Some(table_name);
126        self
127    }
128
129    /// Set the foreign key field names
130    pub fn with_fields(mut self, entity_a_field: String, entity_b_field: String) -> Self {
131        self.entity_a_field = entity_a_field;
132        self.entity_b_field = entity_b_field;
133        self
134    }
135
136    /// Set cascade actions
137    pub fn with_cascade_actions(
138        mut self,
139        on_delete_a: CascadeAction,
140        on_delete_b: CascadeAction,
141    ) -> Self {
142        self.on_delete_a = on_delete_a;
143        self.on_delete_b = on_delete_b;
144        self
145    }
146}
147
148/// Cascade action for foreign keys
149#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
150#[serde(rename_all = "UPPERCASE")]
151pub enum CascadeAction {
152    /// No action
153    #[default]
154    NoAction,
155    /// Cascade (delete/update related records)
156    Cascade,
157    /// Set null
158    SetNull,
159    /// Set default
160    SetDefault,
161    /// Restrict (prevent if related records exist)
162    Restrict,
163}
164
165/// Index definition
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct IndexDefinition {
168    /// Index name
169    pub name: String,
170
171    /// Fields included in the index
172    pub fields: Vec<String>,
173
174    /// Whether the index is unique
175    #[serde(default)]
176    pub unique: bool,
177}
178
179/// Unique constraint definition
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct UniqueConstraint {
182    /// Constraint name
183    pub name: String,
184
185    /// Fields that must be unique together
186    pub fields: Vec<String>,
187}
188
189/// Auto-generation rule for fields
190#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(tag = "type", rename_all = "snake_case")]
192pub enum AutoGenerationRule {
193    /// Auto-incrementing integer
194    AutoIncrement,
195    /// UUID generation
196    Uuid,
197    /// Current timestamp
198    Timestamp,
199    /// Current date
200    Date,
201    /// Custom function/expression
202    Custom(String),
203    /// Pattern-based ID generation
204    ///
205    /// Supports template variables:
206    /// - `{increment}` or `{increment:06}` - Auto-incrementing number with padding
207    /// - `{timestamp}` - Unix timestamp
208    /// - `{random}` - Random alphanumeric string
209    /// - `{uuid}` - UUID v4
210    ///
211    /// Examples:
212    /// - "USR-{increment:06}" -> "USR-000001"
213    /// - "ORD-{timestamp}" -> "ORD-1704067200"
214    Pattern(String),
215    /// Realistic-looking ID generation (Stripe-style)
216    ///
217    /// Generates IDs in the format: `{prefix}_{random_alphanumeric}`
218    ///
219    /// # Arguments
220    /// * `prefix` - Prefix for the ID (e.g., "cus", "ord")
221    /// * `length` - Total length of the random part (excluding prefix and underscore)
222    Realistic {
223        /// Prefix for the ID
224        prefix: String,
225        /// Length of the random alphanumeric part
226        length: usize,
227    },
228}
229
230impl VbrSchemaDefinition {
231    /// Create a new VBR schema definition from a base schema
232    pub fn new(base: SchemaDefinition) -> Self {
233        Self {
234            base,
235            primary_key: vec!["id".to_string()], // Default primary key
236            foreign_keys: Vec::new(),
237            indexes: Vec::new(),
238            unique_constraints: Vec::new(),
239            auto_generation: HashMap::new(),
240            many_to_many: Vec::new(),
241        }
242    }
243
244    /// Set the primary key field(s)
245    pub fn with_primary_key(mut self, fields: Vec<String>) -> Self {
246        self.primary_key = fields;
247        self
248    }
249
250    /// Add a foreign key relationship
251    pub fn with_foreign_key(mut self, fk: ForeignKeyDefinition) -> Self {
252        self.foreign_keys.push(fk);
253        self
254    }
255
256    /// Add an index
257    pub fn with_index(mut self, index: IndexDefinition) -> Self {
258        self.indexes.push(index);
259        self
260    }
261
262    /// Add a unique constraint
263    pub fn with_unique_constraint(mut self, constraint: UniqueConstraint) -> Self {
264        self.unique_constraints.push(constraint);
265        self
266    }
267
268    /// Set auto-generation rule for a field
269    pub fn with_auto_generation(mut self, field: String, rule: AutoGenerationRule) -> Self {
270        self.auto_generation.insert(field, rule);
271        self
272    }
273
274    /// Add a many-to-many relationship
275    pub fn with_many_to_many(mut self, m2m: ManyToManyDefinition) -> Self {
276        self.many_to_many.push(m2m);
277        self
278    }
279}