data_modelling_sdk/export/
json_schema.rs1use super::{ExportError, ExportResult};
4use crate::models::{DataModel, Table};
5use serde_json::{Value, json};
6
7pub struct JSONSchemaExporter;
9
10impl JSONSchemaExporter {
11 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 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 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 }
103
104 if !column.description.is_empty() {
105 property.insert("description".to_string(), json!(column.description));
106 }
107
108 if let Some(ref_path) = &column.ref_path {
110 property.insert("$ref".to_string(), json!(ref_path));
111 }
112
113 if !column.enum_values.is_empty() {
115 let enum_vals: Vec<Value> = column
116 .enum_values
117 .iter()
118 .map(|v| {
119 if let Ok(num) = v.parse::<i64>() {
121 json!(num)
122 } else if let Ok(num) = v.parse::<f64>() {
123 json!(num)
124 } else if let Ok(b) = v.parse::<bool>() {
125 json!(b)
126 } else if v == "null" {
127 json!(null)
128 } else {
129 json!(v)
130 }
131 })
132 .collect();
133 property.insert("enum".to_string(), json!(enum_vals));
134 }
135
136 Self::export_validation_keywords(&mut property, column);
138
139 properties.insert(column.name.clone(), json!(property));
140 }
141
142 let mut schema = serde_json::Map::new();
143 schema.insert(
144 "$schema".to_string(),
145 json!("http://json-schema.org/draft-07/schema#"),
146 );
147 schema.insert("type".to_string(), json!("object"));
148 schema.insert("title".to_string(), json!(table.name));
149 schema.insert("properties".to_string(), json!(properties));
150
151 let required: Vec<String> = table
153 .columns
154 .iter()
155 .filter(|c| !c.nullable)
156 .map(|c| c.name.clone())
157 .collect();
158
159 if !required.is_empty() {
160 schema.insert("required".to_string(), json!(required));
161 }
162
163 if !table.tags.is_empty() {
165 let tags_array: Vec<String> = table.tags.iter().map(|t| t.to_string()).collect();
166 schema.insert("tags".to_string(), json!(tags_array));
167 }
168
169 json!(schema)
170 }
171
172 pub fn export_model(model: &DataModel, table_ids: Option<&[uuid::Uuid]>) -> Value {
174 let mut definitions = serde_json::Map::new();
175
176 let tables_to_export: Vec<&Table> = if let Some(ids) = table_ids {
177 model
178 .tables
179 .iter()
180 .filter(|t| ids.contains(&t.id))
181 .collect()
182 } else {
183 model.tables.iter().collect()
184 };
185
186 for table in tables_to_export {
187 let schema = Self::export_table(table);
188 definitions.insert(table.name.clone(), schema);
189 }
190
191 let mut root = serde_json::Map::new();
192 root.insert(
193 "$schema".to_string(),
194 json!("http://json-schema.org/draft-07/schema#"),
195 );
196 root.insert("title".to_string(), json!(model.name));
197 root.insert("type".to_string(), json!("object"));
198 root.insert("definitions".to_string(), json!(definitions));
199
200 json!(root)
201 }
202
203 fn map_data_type_to_json_schema(data_type: &str) -> (String, Option<String>) {
205 let dt_lower = data_type.to_lowercase();
206
207 match dt_lower.as_str() {
208 "int" | "integer" | "bigint" | "smallint" | "tinyint" => ("integer".to_string(), None),
209 "float" | "double" | "real" | "decimal" | "numeric" => ("number".to_string(), None),
210 "boolean" | "bool" => ("boolean".to_string(), None),
211 "date" => ("string".to_string(), Some("date".to_string())),
212 "time" => ("string".to_string(), Some("time".to_string())),
213 "timestamp" | "datetime" => ("string".to_string(), Some("date-time".to_string())),
214 "uuid" => ("string".to_string(), Some("uuid".to_string())),
215 "uri" | "url" => ("string".to_string(), Some("uri".to_string())),
216 "email" => ("string".to_string(), Some("email".to_string())),
217 _ => {
218 ("string".to_string(), None)
220 }
221 }
222 }
223
224 fn export_validation_keywords(
226 property: &mut serde_json::Map<String, Value>,
227 column: &crate::models::Column,
228 ) {
229 for rule in &column.quality {
230 let source = rule.get("source").and_then(|v| v.as_str());
233 if source.is_some() && source != Some("json_schema") {
234 continue;
235 }
236
237 if let Some(rule_type) = rule.get("type").and_then(|v| v.as_str()) {
238 match rule_type {
239 "pattern" => {
240 if let Some(pattern) = rule.get("pattern").or_else(|| rule.get("value")) {
241 property.insert("pattern".to_string(), pattern.clone());
242 }
243 }
244 "minimum" => {
245 if let Some(value) = rule.get("value") {
246 property.insert("minimum".to_string(), value.clone());
247 if let Some(exclusive) = rule.get("exclusive")
248 && exclusive.as_bool() == Some(true)
249 {
250 property.insert("exclusiveMinimum".to_string(), json!(true));
251 }
252 }
253 }
254 "maximum" => {
255 if let Some(value) = rule.get("value") {
256 property.insert("maximum".to_string(), value.clone());
257 if let Some(exclusive) = rule.get("exclusive")
258 && exclusive.as_bool() == Some(true)
259 {
260 property.insert("exclusiveMaximum".to_string(), json!(true));
261 }
262 }
263 }
264 "minLength" => {
265 if let Some(value) = rule.get("value") {
266 property.insert("minLength".to_string(), value.clone());
267 }
268 }
269 "maxLength" => {
270 if let Some(value) = rule.get("value") {
271 property.insert("maxLength".to_string(), value.clone());
272 }
273 }
274 "multipleOf" => {
275 if let Some(value) = rule.get("value") {
276 property.insert("multipleOf".to_string(), value.clone());
277 }
278 }
279 "const" => {
280 if let Some(value) = rule.get("value") {
281 property.insert("const".to_string(), value.clone());
282 }
283 }
284 "minItems" => {
285 if let Some(value) = rule.get("value") {
286 property.insert("minItems".to_string(), value.clone());
287 }
288 }
289 "maxItems" => {
290 if let Some(value) = rule.get("value") {
291 property.insert("maxItems".to_string(), value.clone());
292 }
293 }
294 "uniqueItems" => {
295 if let Some(value) = rule.get("value")
296 && value.as_bool() == Some(true)
297 {
298 property.insert("uniqueItems".to_string(), json!(true));
299 }
300 }
301 "minProperties" => {
302 if let Some(value) = rule.get("value") {
303 property.insert("minProperties".to_string(), value.clone());
304 }
305 }
306 "maxProperties" => {
307 if let Some(value) = rule.get("value") {
308 property.insert("maxProperties".to_string(), value.clone());
309 }
310 }
311 "additionalProperties" => {
312 if let Some(value) = rule.get("value") {
313 property.insert("additionalProperties".to_string(), value.clone());
314 }
315 }
316 "format" => {
317 if let Some(value) = rule.get("value").and_then(|v| v.as_str()) {
320 if !property.contains_key("format") {
322 property.insert("format".to_string(), json!(value));
323 }
324 }
325 }
326 "allOf" | "anyOf" | "oneOf" | "not" => {
327 if let Some(value) = rule.get("value") {
329 property.insert(rule_type.to_string(), value.clone());
330 }
331 }
332 _ => {
333 }
336 }
337 }
338 }
339
340 for constraint in &column.constraints {
342 let constraint_upper = constraint.to_uppercase();
344 if constraint_upper.contains("UNIQUE") {
345 } else if constraint_upper.starts_with("CHECK") {
348 }
351 }
352 }
353}