data_modelling_sdk/export/
json_schema.rs

1//! JSON Schema exporter for generating JSON Schema from data models.
2
3use super::{ExportError, ExportResult};
4use crate::models::{DataModel, Table};
5use serde_json::{Value, json};
6
7/// Exporter for JSON Schema format.
8pub struct JSONSchemaExporter;
9
10impl JSONSchemaExporter {
11    /// Export tables to JSON Schema format (SDK interface).
12    ///
13    /// # Arguments
14    ///
15    /// * `tables` - Slice of tables to export
16    ///
17    /// # Returns
18    ///
19    /// An `ExportResult` containing JSON Schema with all tables in the `definitions` section.
20    ///
21    /// # Example
22    ///
23    /// ```rust
24    /// use data_modelling_sdk::export::json_schema::JSONSchemaExporter;
25    /// use data_modelling_sdk::models::{Table, Column};
26    ///
27    /// let tables = vec![
28    ///     Table::new("User".to_string(), vec![Column::new("id".to_string(), "INTEGER".to_string())]),
29    /// ];
30    ///
31    /// let exporter = JSONSchemaExporter;
32    /// let result = exporter.export(&tables).unwrap();
33    /// assert_eq!(result.format, "json_schema");
34    /// assert!(result.content.contains("\"definitions\""));
35    /// ```
36    pub fn export(&self, tables: &[Table]) -> Result<ExportResult, ExportError> {
37        let schema = Self::export_model_from_tables(tables);
38        Ok(ExportResult {
39            content: serde_json::to_string_pretty(&schema)
40                .map_err(|e| ExportError::SerializationError(e.to_string()))?,
41            format: "json_schema".to_string(),
42        })
43    }
44
45    fn export_model_from_tables(tables: &[Table]) -> serde_json::Value {
46        let mut definitions = serde_json::Map::new();
47        for table in tables {
48            let schema = Self::export_table(table);
49            definitions.insert(table.name.clone(), schema);
50        }
51        let mut root = serde_json::Map::new();
52        root.insert(
53            "$schema".to_string(),
54            serde_json::json!("http://json-schema.org/draft-07/schema#"),
55        );
56        root.insert("type".to_string(), serde_json::json!("object"));
57        root.insert("definitions".to_string(), serde_json::json!(definitions));
58        serde_json::json!(root)
59    }
60
61    /// Export a table to JSON Schema format.
62    ///
63    /// # Arguments
64    ///
65    /// * `table` - The table to export
66    ///
67    /// # Returns
68    ///
69    /// A `serde_json::Value` representing the JSON Schema for the table.
70    ///
71    /// # Example
72    ///
73    /// ```rust
74    /// use data_modelling_sdk::export::json_schema::JSONSchemaExporter;
75    /// use data_modelling_sdk::models::{Table, Column};
76    ///
77    /// let table = Table::new(
78    ///     "User".to_string(),
79    ///     vec![Column::new("id".to_string(), "INTEGER".to_string())],
80    /// );
81    ///
82    /// let schema = JSONSchemaExporter::export_table(&table);
83    /// assert_eq!(schema["title"], "User");
84    /// assert_eq!(schema["type"], "object");
85    /// ```
86    pub fn export_table(table: &Table) -> Value {
87        let mut properties = serde_json::Map::new();
88
89        for column in &table.columns {
90            let mut property = serde_json::Map::new();
91
92            // Map data types to JSON Schema types
93            let (json_type, format) = Self::map_data_type_to_json_schema(&column.data_type);
94            property.insert("type".to_string(), json!(json_type));
95
96            if let Some(fmt) = format {
97                property.insert("format".to_string(), json!(fmt));
98            }
99
100            if !column.nullable {
101                // Note: JSON Schema uses "required" array at schema level
102            }
103
104            if !column.description.is_empty() {
105                property.insert("description".to_string(), json!(column.description));
106            }
107
108            properties.insert(column.name.clone(), json!(property));
109        }
110
111        let mut schema = serde_json::Map::new();
112        schema.insert(
113            "$schema".to_string(),
114            json!("http://json-schema.org/draft-07/schema#"),
115        );
116        schema.insert("type".to_string(), json!("object"));
117        schema.insert("title".to_string(), json!(table.name));
118        schema.insert("properties".to_string(), json!(properties));
119
120        // Add required fields (non-nullable columns)
121        let required: Vec<String> = table
122            .columns
123            .iter()
124            .filter(|c| !c.nullable)
125            .map(|c| c.name.clone())
126            .collect();
127
128        if !required.is_empty() {
129            schema.insert("required".to_string(), json!(required));
130        }
131
132        // Add tags if present
133        if !table.tags.is_empty() {
134            let tags_array: Vec<String> = table.tags.iter().map(|t| t.to_string()).collect();
135            schema.insert("tags".to_string(), json!(tags_array));
136        }
137
138        json!(schema)
139    }
140
141    /// Export a data model to JSON Schema format (legacy method for compatibility).
142    pub fn export_model(model: &DataModel, table_ids: Option<&[uuid::Uuid]>) -> Value {
143        let mut definitions = serde_json::Map::new();
144
145        let tables_to_export: Vec<&Table> = if let Some(ids) = table_ids {
146            model
147                .tables
148                .iter()
149                .filter(|t| ids.contains(&t.id))
150                .collect()
151        } else {
152            model.tables.iter().collect()
153        };
154
155        for table in tables_to_export {
156            let schema = Self::export_table(table);
157            definitions.insert(table.name.clone(), schema);
158        }
159
160        let mut root = serde_json::Map::new();
161        root.insert(
162            "$schema".to_string(),
163            json!("http://json-schema.org/draft-07/schema#"),
164        );
165        root.insert("title".to_string(), json!(model.name));
166        root.insert("type".to_string(), json!("object"));
167        root.insert("definitions".to_string(), json!(definitions));
168
169        json!(root)
170    }
171
172    /// Map SQL/ODCL data types to JSON Schema types and formats.
173    fn map_data_type_to_json_schema(data_type: &str) -> (String, Option<String>) {
174        let dt_lower = data_type.to_lowercase();
175
176        match dt_lower.as_str() {
177            "int" | "integer" | "bigint" | "smallint" | "tinyint" => ("integer".to_string(), None),
178            "float" | "double" | "real" | "decimal" | "numeric" => ("number".to_string(), None),
179            "boolean" | "bool" => ("boolean".to_string(), None),
180            "date" => ("string".to_string(), Some("date".to_string())),
181            "time" => ("string".to_string(), Some("time".to_string())),
182            "timestamp" | "datetime" => ("string".to_string(), Some("date-time".to_string())),
183            "uuid" => ("string".to_string(), Some("uuid".to_string())),
184            "uri" | "url" => ("string".to_string(), Some("uri".to_string())),
185            "email" => ("string".to_string(), Some("email".to_string())),
186            _ => {
187                // Default to string for VARCHAR, TEXT, CHAR, etc.
188                // Default to string for VARCHAR, TEXT, CHAR, etc.
189                ("string".to_string(), None)
190            }
191        }
192    }
193}