1use mockforge_data::SchemaDefinition;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct VbrSchemaDefinition {
14 #[serde(flatten)]
16 pub base: SchemaDefinition,
17
18 pub primary_key: Vec<String>,
20
21 pub foreign_keys: Vec<ForeignKeyDefinition>,
23
24 pub indexes: Vec<IndexDefinition>,
26
27 pub unique_constraints: Vec<UniqueConstraint>,
29
30 pub auto_generation: HashMap<String, AutoGenerationRule>,
32
33 pub many_to_many: Vec<ManyToManyDefinition>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct ForeignKeyDefinition {
40 pub field: String,
42
43 pub target_entity: String,
45
46 pub target_field: String,
48
49 #[serde(default)]
51 pub on_delete: CascadeAction,
52
53 #[serde(default)]
55 pub on_update: CascadeAction,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ManyToManyDefinition {
64 pub entity_a: String,
66
67 pub entity_b: String,
69
70 pub junction_table: Option<String>,
73
74 pub entity_a_field: String,
76
77 pub entity_b_field: String,
79
80 #[serde(default)]
82 pub on_delete_a: CascadeAction,
83
84 #[serde(default)]
86 pub on_delete_b: CascadeAction,
87}
88
89impl ManyToManyDefinition {
90 pub fn new(entity_a: String, entity_b: String) -> Self {
92 let (_field_a, _field_b) = if entity_a.to_lowercase() < entity_b.to_lowercase() {
94 (
95 format!("{}_id", entity_a.to_lowercase()),
96 format!("{}_id", entity_b.to_lowercase()),
97 )
98 } else {
99 (
100 format!("{}_id", entity_b.to_lowercase()),
101 format!("{}_id", entity_a.to_lowercase()),
102 )
103 };
104
105 let junction_table = if entity_a.to_lowercase() < entity_b.to_lowercase() {
106 Some(format!("{}_{}", entity_a.to_lowercase(), entity_b.to_lowercase()))
107 } else {
108 Some(format!("{}_{}", entity_b.to_lowercase(), entity_a.to_lowercase()))
109 };
110
111 Self {
112 entity_a: entity_a.clone(),
113 entity_b: entity_b.clone(),
114 junction_table,
115 entity_a_field: format!("{}_id", entity_a.to_lowercase()),
116 entity_b_field: format!("{}_id", entity_b.to_lowercase()),
117 on_delete_a: CascadeAction::Cascade,
118 on_delete_b: CascadeAction::Cascade,
119 }
120 }
121
122 pub fn with_junction_table(mut self, table_name: String) -> Self {
124 self.junction_table = Some(table_name);
125 self
126 }
127
128 pub fn with_fields(mut self, entity_a_field: String, entity_b_field: String) -> Self {
130 self.entity_a_field = entity_a_field;
131 self.entity_b_field = entity_b_field;
132 self
133 }
134
135 pub fn with_cascade_actions(
137 mut self,
138 on_delete_a: CascadeAction,
139 on_delete_b: CascadeAction,
140 ) -> Self {
141 self.on_delete_a = on_delete_a;
142 self.on_delete_b = on_delete_b;
143 self
144 }
145}
146
147#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
149#[serde(rename_all = "UPPERCASE")]
150pub enum CascadeAction {
151 #[default]
153 NoAction,
154 Cascade,
156 SetNull,
158 SetDefault,
160 Restrict,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct IndexDefinition {
167 pub name: String,
169
170 pub fields: Vec<String>,
172
173 #[serde(default)]
175 pub unique: bool,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct UniqueConstraint {
181 pub name: String,
183
184 pub fields: Vec<String>,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
190#[serde(tag = "type", content = "value", rename_all = "snake_case")]
191pub enum AutoGenerationRule {
192 AutoIncrement,
194 Uuid,
196 Timestamp,
198 Date,
200 Custom(String),
202 Pattern(String),
214 Realistic {
222 prefix: String,
224 length: usize,
226 },
227}
228
229impl VbrSchemaDefinition {
230 pub fn new(base: SchemaDefinition) -> Self {
232 Self {
233 base,
234 primary_key: vec!["id".to_string()], foreign_keys: Vec::new(),
236 indexes: Vec::new(),
237 unique_constraints: Vec::new(),
238 auto_generation: HashMap::new(),
239 many_to_many: Vec::new(),
240 }
241 }
242
243 pub fn with_primary_key(mut self, fields: Vec<String>) -> Self {
245 self.primary_key = fields;
246 self
247 }
248
249 pub fn with_foreign_key(mut self, fk: ForeignKeyDefinition) -> Self {
251 self.foreign_keys.push(fk);
252 self
253 }
254
255 pub fn with_index(mut self, index: IndexDefinition) -> Self {
257 self.indexes.push(index);
258 self
259 }
260
261 pub fn with_unique_constraint(mut self, constraint: UniqueConstraint) -> Self {
263 self.unique_constraints.push(constraint);
264 self
265 }
266
267 pub fn with_auto_generation(mut self, field: String, rule: AutoGenerationRule) -> Self {
269 self.auto_generation.insert(field, rule);
270 self
271 }
272
273 pub fn with_many_to_many(mut self, m2m: ManyToManyDefinition) -> Self {
275 self.many_to_many.push(m2m);
276 self
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use mockforge_data::SchemaDefinition;
284
285 fn create_test_schema() -> VbrSchemaDefinition {
286 let base = SchemaDefinition::new("TestEntity".to_string());
287 VbrSchemaDefinition::new(base)
288 }
289
290 #[test]
292 fn test_cascade_action_default() {
293 let action = CascadeAction::default();
294 assert!(matches!(action, CascadeAction::NoAction));
295 }
296
297 #[test]
298 fn test_cascade_action_serialize() {
299 assert_eq!(serde_json::to_string(&CascadeAction::NoAction).unwrap(), "\"NOACTION\"");
300 assert_eq!(serde_json::to_string(&CascadeAction::Cascade).unwrap(), "\"CASCADE\"");
301 assert_eq!(serde_json::to_string(&CascadeAction::SetNull).unwrap(), "\"SETNULL\"");
302 assert_eq!(serde_json::to_string(&CascadeAction::SetDefault).unwrap(), "\"SETDEFAULT\"");
303 assert_eq!(serde_json::to_string(&CascadeAction::Restrict).unwrap(), "\"RESTRICT\"");
304 }
305
306 #[test]
307 fn test_cascade_action_deserialize() {
308 let action: CascadeAction = serde_json::from_str("\"CASCADE\"").unwrap();
309 assert!(matches!(action, CascadeAction::Cascade));
310 }
311
312 #[test]
313 fn test_cascade_action_clone() {
314 let action = CascadeAction::Cascade;
315 let cloned = action;
316 assert_eq!(action, cloned);
317 }
318
319 #[test]
320 fn test_cascade_action_debug() {
321 let action = CascadeAction::Restrict;
322 let debug = format!("{:?}", action);
323 assert!(debug.contains("Restrict"));
324 }
325
326 #[test]
328 fn test_foreign_key_definition_clone() {
329 let fk = ForeignKeyDefinition {
330 field: "user_id".to_string(),
331 target_entity: "User".to_string(),
332 target_field: "id".to_string(),
333 on_delete: CascadeAction::Cascade,
334 on_update: CascadeAction::NoAction,
335 };
336
337 let cloned = fk.clone();
338 assert_eq!(fk.field, cloned.field);
339 assert_eq!(fk.target_entity, cloned.target_entity);
340 }
341
342 #[test]
343 fn test_foreign_key_definition_debug() {
344 let fk = ForeignKeyDefinition {
345 field: "post_id".to_string(),
346 target_entity: "Post".to_string(),
347 target_field: "id".to_string(),
348 on_delete: CascadeAction::default(),
349 on_update: CascadeAction::default(),
350 };
351
352 let debug = format!("{:?}", fk);
353 assert!(debug.contains("ForeignKeyDefinition"));
354 assert!(debug.contains("post_id"));
355 }
356
357 #[test]
358 fn test_foreign_key_definition_serialize() {
359 let fk = ForeignKeyDefinition {
360 field: "author_id".to_string(),
361 target_entity: "Author".to_string(),
362 target_field: "id".to_string(),
363 on_delete: CascadeAction::Cascade,
364 on_update: CascadeAction::NoAction,
365 };
366
367 let json = serde_json::to_string(&fk).unwrap();
368 assert!(json.contains("author_id"));
369 assert!(json.contains("Author"));
370 }
371
372 #[test]
374 fn test_many_to_many_definition_new() {
375 let m2m = ManyToManyDefinition::new("User".to_string(), "Role".to_string());
376 assert_eq!(m2m.entity_a, "User");
377 assert_eq!(m2m.entity_b, "Role");
378 assert_eq!(m2m.junction_table, Some("role_user".to_string())); assert_eq!(m2m.entity_a_field, "user_id");
380 assert_eq!(m2m.entity_b_field, "role_id");
381 }
382
383 #[test]
384 fn test_many_to_many_definition_alphabetical_order() {
385 let m2m1 = ManyToManyDefinition::new("Apple".to_string(), "Banana".to_string());
387 assert_eq!(m2m1.junction_table, Some("apple_banana".to_string()));
388
389 let m2m2 = ManyToManyDefinition::new("Zebra".to_string(), "Apple".to_string());
391 assert_eq!(m2m2.junction_table, Some("apple_zebra".to_string()));
392 }
393
394 #[test]
395 fn test_many_to_many_definition_with_junction_table() {
396 let m2m = ManyToManyDefinition::new("User".to_string(), "Role".to_string())
397 .with_junction_table("custom_user_roles".to_string());
398 assert_eq!(m2m.junction_table, Some("custom_user_roles".to_string()));
399 }
400
401 #[test]
402 fn test_many_to_many_definition_with_fields() {
403 let m2m = ManyToManyDefinition::new("User".to_string(), "Role".to_string())
404 .with_fields("usr_id".to_string(), "role_identifier".to_string());
405 assert_eq!(m2m.entity_a_field, "usr_id");
406 assert_eq!(m2m.entity_b_field, "role_identifier");
407 }
408
409 #[test]
410 fn test_many_to_many_definition_with_cascade_actions() {
411 let m2m = ManyToManyDefinition::new("User".to_string(), "Role".to_string())
412 .with_cascade_actions(CascadeAction::Restrict, CascadeAction::SetNull);
413 assert!(matches!(m2m.on_delete_a, CascadeAction::Restrict));
414 assert!(matches!(m2m.on_delete_b, CascadeAction::SetNull));
415 }
416
417 #[test]
418 fn test_many_to_many_definition_clone() {
419 let m2m = ManyToManyDefinition::new("User".to_string(), "Group".to_string());
420 let cloned = m2m.clone();
421 assert_eq!(m2m.entity_a, cloned.entity_a);
422 assert_eq!(m2m.junction_table, cloned.junction_table);
423 }
424
425 #[test]
426 fn test_many_to_many_definition_debug() {
427 let m2m = ManyToManyDefinition::new("Tag".to_string(), "Post".to_string());
428 let debug = format!("{:?}", m2m);
429 assert!(debug.contains("ManyToManyDefinition"));
430 assert!(debug.contains("Tag"));
431 }
432
433 #[test]
435 fn test_index_definition_clone() {
436 let idx = IndexDefinition {
437 name: "idx_email".to_string(),
438 fields: vec!["email".to_string()],
439 unique: true,
440 };
441
442 let cloned = idx.clone();
443 assert_eq!(idx.name, cloned.name);
444 assert_eq!(idx.unique, cloned.unique);
445 }
446
447 #[test]
448 fn test_index_definition_debug() {
449 let idx = IndexDefinition {
450 name: "idx_composite".to_string(),
451 fields: vec!["first_name".to_string(), "last_name".to_string()],
452 unique: false,
453 };
454
455 let debug = format!("{:?}", idx);
456 assert!(debug.contains("IndexDefinition"));
457 assert!(debug.contains("idx_composite"));
458 }
459
460 #[test]
461 fn test_index_definition_serialize() {
462 let idx = IndexDefinition {
463 name: "idx_test".to_string(),
464 fields: vec!["field1".to_string()],
465 unique: true,
466 };
467
468 let json = serde_json::to_string(&idx).unwrap();
469 assert!(json.contains("idx_test"));
470 assert!(json.contains("\"unique\":true"));
471 }
472
473 #[test]
475 fn test_unique_constraint_clone() {
476 let constraint = UniqueConstraint {
477 name: "uq_email".to_string(),
478 fields: vec!["email".to_string()],
479 };
480
481 let cloned = constraint.clone();
482 assert_eq!(constraint.name, cloned.name);
483 assert_eq!(constraint.fields, cloned.fields);
484 }
485
486 #[test]
487 fn test_unique_constraint_debug() {
488 let constraint = UniqueConstraint {
489 name: "uq_composite".to_string(),
490 fields: vec!["a".to_string(), "b".to_string()],
491 };
492
493 let debug = format!("{:?}", constraint);
494 assert!(debug.contains("UniqueConstraint"));
495 }
496
497 #[test]
499 fn test_auto_generation_rule_clone() {
500 let rule = AutoGenerationRule::Uuid;
501 let cloned = rule.clone();
502 assert!(matches!(cloned, AutoGenerationRule::Uuid));
503 }
504
505 #[test]
506 fn test_auto_generation_rule_debug() {
507 let rule = AutoGenerationRule::Timestamp;
508 let debug = format!("{:?}", rule);
509 assert!(debug.contains("Timestamp"));
510 }
511
512 #[test]
513 fn test_auto_generation_rule_serialize_uuid() {
514 let rule = AutoGenerationRule::Uuid;
515 let json = serde_json::to_string(&rule).unwrap();
516 assert!(json.contains("uuid"));
517 }
518
519 #[test]
520 fn test_auto_generation_rule_serialize_pattern() {
521 let rule = AutoGenerationRule::Pattern("USR-{increment:06}".to_string());
522 let json = serde_json::to_string(&rule).unwrap();
523 assert!(json.contains("pattern"));
524 assert!(json.contains("USR-{increment:06}"));
525 }
526
527 #[test]
528 fn test_auto_generation_rule_serialize_realistic() {
529 let rule = AutoGenerationRule::Realistic {
530 prefix: "cus".to_string(),
531 length: 14,
532 };
533 let json = serde_json::to_string(&rule).unwrap();
534 assert!(json.contains("realistic"));
535 assert!(json.contains("\"value\":{"));
537 assert!(json.contains("\"prefix\":\"cus\""));
538 assert!(json.contains("\"length\":14"));
539 }
540
541 #[test]
542 fn test_auto_generation_rule_all_variants() {
543 let rules = vec![
544 AutoGenerationRule::AutoIncrement,
545 AutoGenerationRule::Uuid,
546 AutoGenerationRule::Timestamp,
547 AutoGenerationRule::Date,
548 AutoGenerationRule::Custom("NOW()".to_string()),
549 AutoGenerationRule::Pattern("{uuid}".to_string()),
550 AutoGenerationRule::Realistic {
551 prefix: "test".to_string(),
552 length: 10,
553 },
554 ];
555
556 for rule in rules {
557 let json = serde_json::to_string(&rule).unwrap();
558 assert!(!json.is_empty());
559 }
560 }
561
562 #[test]
564 fn test_vbr_schema_definition_new() {
565 let base = SchemaDefinition::new("User".to_string());
566 let schema = VbrSchemaDefinition::new(base);
567
568 assert_eq!(schema.primary_key, vec!["id"]);
569 assert!(schema.foreign_keys.is_empty());
570 assert!(schema.indexes.is_empty());
571 assert!(schema.unique_constraints.is_empty());
572 assert!(schema.auto_generation.is_empty());
573 assert!(schema.many_to_many.is_empty());
574 }
575
576 #[test]
577 fn test_vbr_schema_definition_with_primary_key() {
578 let schema = create_test_schema()
579 .with_primary_key(vec!["user_id".to_string(), "role_id".to_string()]);
580 assert_eq!(schema.primary_key, vec!["user_id", "role_id"]);
581 }
582
583 #[test]
584 fn test_vbr_schema_definition_with_foreign_key() {
585 let fk = ForeignKeyDefinition {
586 field: "user_id".to_string(),
587 target_entity: "User".to_string(),
588 target_field: "id".to_string(),
589 on_delete: CascadeAction::Cascade,
590 on_update: CascadeAction::NoAction,
591 };
592
593 let schema = create_test_schema().with_foreign_key(fk);
594 assert_eq!(schema.foreign_keys.len(), 1);
595 assert_eq!(schema.foreign_keys[0].field, "user_id");
596 }
597
598 #[test]
599 fn test_vbr_schema_definition_with_index() {
600 let idx = IndexDefinition {
601 name: "idx_email".to_string(),
602 fields: vec!["email".to_string()],
603 unique: true,
604 };
605
606 let schema = create_test_schema().with_index(idx);
607 assert_eq!(schema.indexes.len(), 1);
608 assert!(schema.indexes[0].unique);
609 }
610
611 #[test]
612 fn test_vbr_schema_definition_with_unique_constraint() {
613 let constraint = UniqueConstraint {
614 name: "uq_email".to_string(),
615 fields: vec!["email".to_string()],
616 };
617
618 let schema = create_test_schema().with_unique_constraint(constraint);
619 assert_eq!(schema.unique_constraints.len(), 1);
620 }
621
622 #[test]
623 fn test_vbr_schema_definition_with_auto_generation() {
624 let schema =
625 create_test_schema().with_auto_generation("id".to_string(), AutoGenerationRule::Uuid);
626 assert!(schema.auto_generation.contains_key("id"));
627 }
628
629 #[test]
630 fn test_vbr_schema_definition_with_many_to_many() {
631 let m2m = ManyToManyDefinition::new("User".to_string(), "Role".to_string());
632 let schema = create_test_schema().with_many_to_many(m2m);
633 assert_eq!(schema.many_to_many.len(), 1);
634 }
635
636 #[test]
637 fn test_vbr_schema_definition_builder_chain() {
638 let schema = create_test_schema()
639 .with_primary_key(vec!["id".to_string()])
640 .with_auto_generation("id".to_string(), AutoGenerationRule::Uuid)
641 .with_auto_generation("created_at".to_string(), AutoGenerationRule::Timestamp)
642 .with_index(IndexDefinition {
643 name: "idx_email".to_string(),
644 fields: vec!["email".to_string()],
645 unique: true,
646 })
647 .with_unique_constraint(UniqueConstraint {
648 name: "uq_username".to_string(),
649 fields: vec!["username".to_string()],
650 })
651 .with_foreign_key(ForeignKeyDefinition {
652 field: "org_id".to_string(),
653 target_entity: "Organization".to_string(),
654 target_field: "id".to_string(),
655 on_delete: CascadeAction::SetNull,
656 on_update: CascadeAction::NoAction,
657 });
658
659 assert_eq!(schema.auto_generation.len(), 2);
660 assert_eq!(schema.indexes.len(), 1);
661 assert_eq!(schema.unique_constraints.len(), 1);
662 assert_eq!(schema.foreign_keys.len(), 1);
663 }
664
665 #[test]
666 fn test_vbr_schema_definition_clone() {
667 let schema = create_test_schema().with_primary_key(vec!["custom_id".to_string()]);
668
669 let cloned = schema.clone();
670 assert_eq!(schema.primary_key, cloned.primary_key);
671 }
672
673 #[test]
674 fn test_vbr_schema_definition_debug() {
675 let schema = create_test_schema();
676 let debug = format!("{:?}", schema);
677 assert!(debug.contains("VbrSchemaDefinition"));
678 }
679}