1use crate::faker::EnhancedFaker;
4use crate::{Error, Result};
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct FieldDefinition {
12 pub name: String,
14 pub field_type: String,
16 pub required: bool,
18 pub default: Option<Value>,
20 pub constraints: HashMap<String, Value>,
22 pub faker_template: Option<String>,
24 pub description: Option<String>,
26}
27
28impl FieldDefinition {
29 pub fn new(name: String, field_type: String) -> Self {
31 Self {
32 name,
33 field_type,
34 required: true,
35 default: None,
36 constraints: HashMap::new(),
37 faker_template: None,
38 description: None,
39 }
40 }
41
42 pub fn optional(mut self) -> Self {
44 self.required = false;
45 self
46 }
47
48 pub fn with_default(mut self, default: Value) -> Self {
50 self.default = Some(default);
51 self
52 }
53
54 pub fn with_constraint(mut self, key: String, value: Value) -> Self {
56 self.constraints.insert(key, value);
57 self
58 }
59
60 pub fn with_faker_template(mut self, template: String) -> Self {
62 self.faker_template = Some(template);
63 self
64 }
65
66 pub fn with_description(mut self, description: String) -> Self {
68 self.description = Some(description);
69 self
70 }
71
72 pub fn generate_value(&self, faker: &mut EnhancedFaker) -> Value {
74 if let Some(template) = &self.faker_template {
76 return faker.generate_by_type(template);
77 }
78
79 if !self.required {
81 if let Some(default) = &self.default {
82 return default.clone();
83 }
84 }
85
86 faker.generate_by_type(&self.field_type)
88 }
89
90 pub fn validate_value(&self, value: &Value) -> Result<()> {
92 if self.required && value.is_null() {
94 return Err(Error::generic(format!("Required field '{}' is null", self.name)));
95 }
96
97 let expected_type = self
99 .constraints
100 .get("type")
101 .and_then(|v| v.as_str())
102 .unwrap_or(&self.field_type);
103
104 let actual_type = match value {
105 Value::String(_) => "string",
106 Value::Number(_) => match expected_type {
107 "integer" => "integer",
108 _ => "number",
109 },
110 Value::Bool(_) => "boolean",
111 Value::Object(_) => "object",
112 Value::Array(_) => "array",
113 Value::Null => "null",
114 };
115
116 let normalized_expected = expected_type;
118
119 if normalized_expected != actual_type
120 && !(normalized_expected == "number" && actual_type == "integer")
121 && !(normalized_expected == "float" && actual_type == "number")
122 && !(normalized_expected == "uuid" && actual_type == "string")
123 && !(normalized_expected == "email" && actual_type == "string")
124 && !(normalized_expected == "name" && actual_type == "string")
125 && !(normalized_expected == "address" && actual_type == "string")
126 && !(normalized_expected == "phone" && actual_type == "string")
127 && !(normalized_expected == "company" && actual_type == "string")
128 && !(normalized_expected == "url" && actual_type == "string")
129 && !(normalized_expected == "ip" && actual_type == "string")
130 && !(normalized_expected == "color" && actual_type == "string")
131 && !(normalized_expected == "date" && actual_type == "string")
132 && !(normalized_expected == "datetime" && actual_type == "string")
133 && !(normalized_expected == "paragraph" && actual_type == "string")
134 {
135 return Err(Error::generic(format!(
136 "Field '{}' type mismatch: expected {}, got {}",
137 self.name, normalized_expected, actual_type
138 )));
139 }
140
141 if let Value::Number(num) = value {
143 if let Some(min_val) = self.constraints.get("minimum") {
144 if let Some(min_num) = min_val.as_f64() {
145 if num.as_f64().unwrap_or(0.0) < min_num {
146 return Err(Error::generic(format!(
147 "Field '{}' value {} is less than minimum {}",
148 self.name, num, min_num
149 )));
150 }
151 }
152 }
153
154 if let Some(max_val) = self.constraints.get("maximum") {
155 if let Some(max_num) = max_val.as_f64() {
156 if num.as_f64().unwrap_or(0.0) > max_num {
157 return Err(Error::generic(format!(
158 "Field '{}' value {} is greater than maximum {}",
159 self.name, num, max_num
160 )));
161 }
162 }
163 }
164 }
165
166 if let Value::String(s) = value {
168 if let Some(min_len) = self.constraints.get("minLength") {
169 if let Some(min_len_num) = min_len.as_u64() {
170 if s.len() < min_len_num as usize {
171 return Err(Error::generic(format!(
172 "Field '{}' length {} is less than minimum {}",
173 self.name,
174 s.len(),
175 min_len_num
176 )));
177 }
178 }
179 }
180
181 if let Some(max_len) = self.constraints.get("maxLength") {
182 if let Some(max_len_num) = max_len.as_u64() {
183 if s.len() > max_len_num as usize {
184 return Err(Error::generic(format!(
185 "Field '{}' length {} is greater than maximum {}",
186 self.name,
187 s.len(),
188 max_len_num
189 )));
190 }
191 }
192 }
193 }
194
195 Ok(())
196 }
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize)]
201pub struct SchemaDefinition {
202 pub name: String,
204 pub description: Option<String>,
206 pub fields: Vec<FieldDefinition>,
208 pub relationships: HashMap<String, Relationship>,
210 pub metadata: HashMap<String, Value>,
212}
213
214impl SchemaDefinition {
215 pub fn new(name: String) -> Self {
217 Self {
218 name,
219 description: None,
220 fields: Vec::new(),
221 relationships: HashMap::new(),
222 metadata: HashMap::new(),
223 }
224 }
225
226 pub fn with_field(mut self, field: FieldDefinition) -> Self {
228 self.fields.push(field);
229 self
230 }
231
232 pub fn with_fields(mut self, fields: Vec<FieldDefinition>) -> Self {
234 self.fields.extend(fields);
235 self
236 }
237
238 pub fn with_description(mut self, description: String) -> Self {
240 self.description = Some(description);
241 self
242 }
243
244 pub fn with_relationship(mut self, name: String, relationship: Relationship) -> Self {
246 self.relationships.insert(name, relationship);
247 self
248 }
249
250 pub fn with_metadata(mut self, key: String, value: Value) -> Self {
252 self.metadata.insert(key, value);
253 self
254 }
255
256 pub fn generate_row(&self, faker: &mut EnhancedFaker) -> Result<Value> {
258 let mut row = serde_json::Map::new();
259
260 for field in &self.fields {
261 let value = field.generate_value(faker);
262 field.validate_value(&value)?;
263 row.insert(field.name.clone(), value);
264 }
265
266 Ok(Value::Object(row))
267 }
268
269 pub fn get_field(&self, name: &str) -> Option<&FieldDefinition> {
271 self.fields.iter().find(|field| field.name == name)
272 }
273
274 pub fn from_json_schema(json_schema: &Value) -> Result<Self> {
276 let title = json_schema
277 .get("title")
278 .and_then(|v| v.as_str())
279 .unwrap_or("GeneratedSchema")
280 .to_string();
281
282 let description =
283 json_schema.get("description").and_then(|v| v.as_str()).map(|s| s.to_string());
284
285 let mut schema = Self::new(title);
286 if let Some(desc) = description {
287 schema = schema.with_description(desc);
288 }
289
290 if let Some(properties) = json_schema.get("properties") {
291 if let Some(props_obj) = properties.as_object() {
292 for (name, prop_def) in props_obj {
293 let field_type = extract_type_from_json_schema(prop_def);
294 let mut field = FieldDefinition::new(name.clone(), field_type);
295
296 if let Some(required) = json_schema.get("required") {
298 if let Some(required_arr) = required.as_array() {
299 let is_required = required_arr.iter().any(|v| v.as_str() == Some(name));
300 if !is_required {
301 field = field.optional();
302 }
303 }
304 }
305
306 if let Some(desc) = prop_def.get("description").and_then(|v| v.as_str()) {
308 field = field.with_description(desc.to_string());
309 }
310
311 if let Some(minimum) = prop_def.get("minimum") {
313 field = field.with_constraint("minimum".to_string(), minimum.clone());
314 }
315 if let Some(maximum) = prop_def.get("maximum") {
316 field = field.with_constraint("maximum".to_string(), maximum.clone());
317 }
318 if let Some(min_length) = prop_def.get("minLength") {
319 field = field.with_constraint("minLength".to_string(), min_length.clone());
320 }
321 if let Some(max_length) = prop_def.get("maxLength") {
322 field = field.with_constraint("maxLength".to_string(), max_length.clone());
323 }
324
325 schema = schema.with_field(field);
326 }
327 }
328 }
329
330 Ok(schema)
331 }
332
333 pub fn from_openapi_spec(openapi_spec: &Value) -> Result<Self> {
335 if !openapi_spec.is_object() {
337 return Err(Error::generic("OpenAPI spec must be a JSON object"));
338 }
339
340 let spec = openapi_spec.as_object().unwrap();
341
342 let title = spec
344 .get("info")
345 .and_then(|info| info.get("title"))
346 .and_then(|title| title.as_str())
347 .unwrap_or("OpenAPI Generated Schema")
348 .to_string();
349
350 let description = spec
352 .get("info")
353 .and_then(|info| info.get("description"))
354 .and_then(|desc| desc.as_str())
355 .map(|s| s.to_string());
356
357 let mut schema = Self::new(title);
358 if let Some(desc) = description {
359 schema = schema.with_description(desc);
360 }
361
362 if let Some(paths) = spec.get("paths").and_then(|p| p.as_object()) {
364 for (path, path_item) in paths {
365 if let Some(path_obj) = path_item.as_object() {
366 for (method, operation) in path_obj {
368 if let Some(op_obj) = operation.as_object() {
369 if let Some(request_body) = op_obj.get("requestBody") {
371 if let Some(rb_obj) = request_body.as_object() {
372 if let Some(content) = rb_obj.get("content") {
373 if let Some(json_content) = content.get("application/json")
374 {
375 if let Some(schema_obj) = json_content.get("schema") {
376 let field_name = format!(
377 "{}_{}_request",
378 path.replace("/", "_").trim_start_matches("_"),
379 method
380 );
381 if let Some(field) =
382 Self::create_field_from_openapi_schema(
383 &field_name,
384 schema_obj,
385 )
386 {
387 schema = schema.with_field(field);
388 }
389 }
390 }
391 }
392 }
393 }
394
395 if let Some(responses) = op_obj.get("responses") {
397 if let Some(resp_obj) = responses.as_object() {
398 for (status_code, response) in resp_obj {
400 if status_code == "200"
401 || status_code == "201"
402 || status_code.starts_with("2")
403 {
404 if let Some(resp_obj) = response.as_object() {
405 if let Some(content) = resp_obj.get("content") {
406 if let Some(json_content) =
407 content.get("application/json")
408 {
409 if let Some(schema_obj) =
410 json_content.get("schema")
411 {
412 let field_name = format!(
413 "{}_{}_response_{}",
414 path.replace("/", "_")
415 .trim_start_matches("_"),
416 method,
417 status_code
418 );
419 if let Some(field) = Self::create_field_from_openapi_schema(&field_name, schema_obj) {
420 schema = schema.with_field(field);
421 }
422 }
423 }
424 }
425 }
426 }
427 }
428 }
429 }
430 }
431 }
432 }
433 }
434 }
435
436 if let Some(components) = spec.get("components") {
438 if let Some(comp_obj) = components.as_object() {
439 if let Some(schemas) = comp_obj.get("schemas") {
440 if let Some(schema_obj) = schemas.as_object() {
441 for (name, schema_def) in schema_obj {
442 if let Some(field) =
443 Self::create_field_from_openapi_schema(name, schema_def)
444 {
445 schema = schema.with_field(field);
446 }
447 }
448 }
449 }
450 }
451 }
452
453 Ok(schema)
454 }
455
456 fn create_field_from_openapi_schema(name: &str, schema: &Value) -> Option<FieldDefinition> {
458 if !schema.is_object() {
459 return None;
460 }
461
462 let schema_obj = schema.as_object().unwrap();
463
464 let field_type = if let Some(type_val) = schema_obj.get("type") {
466 if let Some(type_str) = type_val.as_str() {
467 match type_str {
468 "string" => "string".to_string(),
469 "number" => "float".to_string(),
470 "integer" => "int".to_string(),
471 "boolean" => "boolean".to_string(),
472 "object" => "object".to_string(),
473 "array" => "array".to_string(),
474 _ => "string".to_string(),
475 }
476 } else {
477 "string".to_string()
478 }
479 } else {
480 "string".to_string()
481 };
482
483 let mut field = FieldDefinition::new(name.to_string(), field_type);
484
485 if let Some(desc) = schema_obj.get("description").and_then(|d| d.as_str()) {
487 field = field.with_description(desc.to_string());
488 }
489
490 if let Some(required) = schema_obj.get("required") {
492 if let Some(required_arr) = required.as_array() {
493 if !required_arr.iter().any(|v| v.as_str() == Some(name)) {
494 field = field.optional();
495 }
496 }
497 }
498
499 if let Some(minimum) = schema_obj.get("minimum") {
501 field = field.with_constraint("minimum".to_string(), minimum.clone());
502 }
503 if let Some(maximum) = schema_obj.get("maximum") {
504 field = field.with_constraint("maximum".to_string(), maximum.clone());
505 }
506 if let Some(min_length) = schema_obj.get("minLength") {
507 field = field.with_constraint("minLength".to_string(), min_length.clone());
508 }
509 if let Some(max_length) = schema_obj.get("maxLength") {
510 field = field.with_constraint("maxLength".to_string(), max_length.clone());
511 }
512
513 if let Some(enum_vals) = schema_obj.get("enum") {
515 if let Some(_enum_arr) = enum_vals.as_array() {
516 field = field.with_constraint("enum".to_string(), enum_vals.clone());
517 }
518 }
519
520 Some(field)
521 }
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
526pub struct Relationship {
527 pub target_schema: String,
529 pub relationship_type: RelationshipType,
531 pub foreign_key: String,
533 pub required: bool,
535}
536
537impl Relationship {
538 pub fn new(
540 target_schema: String,
541 relationship_type: RelationshipType,
542 foreign_key: String,
543 ) -> Self {
544 Self {
545 target_schema,
546 relationship_type,
547 foreign_key,
548 required: true,
549 }
550 }
551
552 pub fn optional(mut self) -> Self {
554 self.required = false;
555 self
556 }
557}
558
559#[derive(Debug, Clone, Serialize, Deserialize)]
561#[serde(rename_all = "lowercase")]
562pub enum RelationshipType {
563 OneToOne,
565 OneToMany,
567 ManyToOne,
569 ManyToMany,
571}
572
573fn extract_type_from_json_schema(prop_def: &Value) -> String {
575 if let Some(type_val) = prop_def.get("type") {
576 if let Some(type_str) = type_val.as_str() {
577 return match type_str {
578 "string" => "string".to_string(),
579 "number" => "float".to_string(),
580 "integer" => "int".to_string(),
581 "boolean" => "boolean".to_string(),
582 "object" => "object".to_string(),
583 "array" => "array".to_string(),
584 "null" => "null".to_string(),
585 _ => "string".to_string(),
586 };
587 }
588 }
589
590 "string".to_string()
592}
593
594pub mod templates {
596 use super::*;
597
598 pub fn user_schema() -> SchemaDefinition {
600 SchemaDefinition::new("User".to_string())
601 .with_description("User account information".to_string())
602 .with_fields(vec![
603 FieldDefinition::new("id".to_string(), "uuid".to_string()),
604 FieldDefinition::new("email".to_string(), "email".to_string()),
605 FieldDefinition::new("name".to_string(), "name".to_string()),
606 FieldDefinition::new("created_at".to_string(), "date".to_string()),
607 FieldDefinition::new("active".to_string(), "boolean".to_string()),
608 ])
609 }
610
611 pub fn product_schema() -> SchemaDefinition {
613 SchemaDefinition::new("Product".to_string())
614 .with_description("Product catalog item".to_string())
615 .with_fields(vec![
616 FieldDefinition::new("id".to_string(), "uuid".to_string()),
617 FieldDefinition::new("name".to_string(), "string".to_string()),
618 FieldDefinition::new("description".to_string(), "paragraph".to_string()),
619 FieldDefinition::new("price".to_string(), "float".to_string())
620 .with_constraint("minimum".to_string(), Value::Number(0.into())),
621 FieldDefinition::new("category".to_string(), "string".to_string()),
622 FieldDefinition::new("in_stock".to_string(), "boolean".to_string()),
623 ])
624 }
625
626 pub fn order_schema() -> SchemaDefinition {
628 SchemaDefinition::new("Order".to_string())
629 .with_description("Customer order".to_string())
630 .with_fields(vec![
631 FieldDefinition::new("id".to_string(), "uuid".to_string()),
632 FieldDefinition::new("user_id".to_string(), "uuid".to_string()),
633 FieldDefinition::new("total_amount".to_string(), "float".to_string())
634 .with_constraint("minimum".to_string(), Value::Number(0.into())),
635 FieldDefinition::new("status".to_string(), "string".to_string()),
636 FieldDefinition::new("created_at".to_string(), "date".to_string()),
637 ])
638 .with_relationship(
639 "user".to_string(),
640 Relationship::new(
641 "User".to_string(),
642 RelationshipType::ManyToOne,
643 "user_id".to_string(),
644 ),
645 )
646 }
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652
653 #[test]
654 fn test_field_definition_new() {
655 let field = FieldDefinition::new("test".to_string(), "string".to_string());
656
657 assert_eq!(field.name, "test");
658 assert_eq!(field.field_type, "string");
659 assert!(field.required);
660 assert!(field.default.is_none());
661 }
662
663 #[test]
664 fn test_field_definition_optional() {
665 let field = FieldDefinition::new("test".to_string(), "string".to_string()).optional();
666
667 assert!(!field.required);
668 }
669
670 #[test]
671 fn test_field_definition_with_default() {
672 let field = FieldDefinition::new("test".to_string(), "string".to_string())
673 .with_default(Value::String("default".to_string()));
674
675 assert_eq!(field.default, Some(Value::String("default".to_string())));
676 }
677
678 #[test]
679 fn test_field_definition_with_constraint() {
680 let field = FieldDefinition::new("age".to_string(), "int".to_string())
681 .with_constraint("minimum".to_string(), Value::Number(0.into()));
682
683 assert!(field.constraints.contains_key("minimum"));
684 }
685
686 #[test]
687 fn test_field_definition_with_faker_template() {
688 let field = FieldDefinition::new("email".to_string(), "string".to_string())
689 .with_faker_template("email".to_string());
690
691 assert_eq!(field.faker_template, Some("email".to_string()));
692 }
693
694 #[test]
695 fn test_field_definition_with_description() {
696 let field = FieldDefinition::new("test".to_string(), "string".to_string())
697 .with_description("Test field".to_string());
698
699 assert_eq!(field.description, Some("Test field".to_string()));
700 }
701
702 #[test]
703 fn test_schema_definition_new() {
704 let schema = SchemaDefinition::new("TestSchema".to_string());
705
706 assert_eq!(schema.name, "TestSchema");
707 assert!(schema.description.is_none());
708 assert_eq!(schema.fields.len(), 0);
709 }
710
711 #[test]
712 fn test_schema_definition_with_field() {
713 let field = FieldDefinition::new("id".to_string(), "uuid".to_string());
714 let schema = SchemaDefinition::new("Test".to_string()).with_field(field);
715
716 assert_eq!(schema.fields.len(), 1);
717 assert_eq!(schema.fields[0].name, "id");
718 }
719
720 #[test]
721 fn test_schema_definition_with_fields() {
722 let fields = vec![
723 FieldDefinition::new("id".to_string(), "uuid".to_string()),
724 FieldDefinition::new("name".to_string(), "string".to_string()),
725 ];
726 let schema = SchemaDefinition::new("Test".to_string()).with_fields(fields);
727
728 assert_eq!(schema.fields.len(), 2);
729 }
730
731 #[test]
732 fn test_schema_definition_with_description() {
733 let schema =
734 SchemaDefinition::new("Test".to_string()).with_description("Test schema".to_string());
735
736 assert_eq!(schema.description, Some("Test schema".to_string()));
737 }
738
739 #[test]
740 fn test_schema_definition_with_metadata() {
741 let schema = SchemaDefinition::new("Test".to_string())
742 .with_metadata("version".to_string(), Value::String("1.0".to_string()));
743
744 assert!(schema.metadata.contains_key("version"));
745 }
746
747 #[test]
748 fn test_schema_definition_get_field() {
749 let field = FieldDefinition::new("email".to_string(), "email".to_string());
750 let schema = SchemaDefinition::new("Test".to_string()).with_field(field);
751
752 assert!(schema.get_field("email").is_some());
753 assert!(schema.get_field("unknown").is_none());
754 }
755
756 #[test]
757 fn test_relationship_new() {
758 let rel = Relationship::new(
759 "User".to_string(),
760 RelationshipType::ManyToOne,
761 "user_id".to_string(),
762 );
763
764 assert_eq!(rel.target_schema, "User");
765 assert_eq!(rel.foreign_key, "user_id");
766 assert!(rel.required);
767 }
768
769 #[test]
770 fn test_relationship_optional() {
771 let rel = Relationship::new(
772 "User".to_string(),
773 RelationshipType::ManyToOne,
774 "user_id".to_string(),
775 )
776 .optional();
777
778 assert!(!rel.required);
779 }
780
781 #[test]
782 fn test_relationship_types() {
783 let one_to_one = RelationshipType::OneToOne;
784 let one_to_many = RelationshipType::OneToMany;
785 let many_to_one = RelationshipType::ManyToOne;
786 let many_to_many = RelationshipType::ManyToMany;
787
788 assert!(matches!(one_to_one, RelationshipType::OneToOne));
789 assert!(matches!(one_to_many, RelationshipType::OneToMany));
790 assert!(matches!(many_to_one, RelationshipType::ManyToOne));
791 assert!(matches!(many_to_many, RelationshipType::ManyToMany));
792 }
793
794 #[test]
795 fn test_extract_type_from_json_schema_string() {
796 let prop = serde_json::json!({"type": "string"});
797 let type_str = extract_type_from_json_schema(&prop);
798
799 assert_eq!(type_str, "string");
800 }
801
802 #[test]
803 fn test_extract_type_from_json_schema_number() {
804 let prop = serde_json::json!({"type": "number"});
805 let type_str = extract_type_from_json_schema(&prop);
806
807 assert_eq!(type_str, "float");
808 }
809
810 #[test]
811 fn test_extract_type_from_json_schema_integer() {
812 let prop = serde_json::json!({"type": "integer"});
813 let type_str = extract_type_from_json_schema(&prop);
814
815 assert_eq!(type_str, "int");
816 }
817
818 #[test]
819 fn test_extract_type_from_json_schema_boolean() {
820 let prop = serde_json::json!({"type": "boolean"});
821 let type_str = extract_type_from_json_schema(&prop);
822
823 assert_eq!(type_str, "boolean");
824 }
825
826 #[test]
827 fn test_extract_type_from_json_schema_default() {
828 let prop = serde_json::json!({});
829 let type_str = extract_type_from_json_schema(&prop);
830
831 assert_eq!(type_str, "string");
832 }
833
834 #[test]
835 fn test_user_schema_template() {
836 let schema = templates::user_schema();
837
838 assert_eq!(schema.name, "User");
839 assert_eq!(schema.fields.len(), 5);
840 assert!(schema.get_field("id").is_some());
841 assert!(schema.get_field("email").is_some());
842 }
843
844 #[test]
845 fn test_product_schema_template() {
846 let schema = templates::product_schema();
847
848 assert_eq!(schema.name, "Product");
849 assert!(schema.get_field("price").is_some());
850 }
851
852 #[test]
853 fn test_order_schema_template() {
854 let schema = templates::order_schema();
855
856 assert_eq!(schema.name, "Order");
857 assert_eq!(schema.relationships.len(), 1);
858 assert!(schema.relationships.contains_key("user"));
859 }
860}