mockforge_data/
schema.rs

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