Skip to main content

sql_splitter/graph/format/
json.rs

1//! JSON format output for ERD data.
2
3use crate::graph::view::GraphView;
4use schemars::JsonSchema;
5use serde::Serialize;
6
7/// JSON representation of the ERD
8#[derive(Debug, Serialize, JsonSchema)]
9pub struct ErdJson {
10    pub tables: Vec<TableJson>,
11    pub relationships: Vec<RelationshipJson>,
12    pub stats: ErdStats,
13}
14
15/// JSON representation of a table with full column details
16#[derive(Debug, Serialize, JsonSchema)]
17pub struct TableJson {
18    pub name: String,
19    pub columns: Vec<ColumnJson>,
20}
21
22/// JSON representation of a column
23#[derive(Debug, Serialize, JsonSchema)]
24pub struct ColumnJson {
25    pub name: String,
26    #[serde(rename = "type")]
27    pub col_type: String,
28    pub is_primary_key: bool,
29    pub is_foreign_key: bool,
30    pub is_nullable: bool,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub references_table: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub references_column: Option<String>,
35}
36
37/// JSON representation of a relationship
38#[derive(Debug, Serialize, JsonSchema)]
39pub struct RelationshipJson {
40    pub from_table: String,
41    pub from_column: String,
42    pub to_table: String,
43    pub to_column: String,
44    pub cardinality: String,
45}
46
47/// ERD statistics
48#[derive(Debug, Serialize, JsonSchema)]
49pub struct ErdStats {
50    pub table_count: usize,
51    pub column_count: usize,
52    pub relationship_count: usize,
53}
54
55/// Generate JSON output from a graph view
56pub fn to_json(view: &GraphView) -> String {
57    let erd = build_erd_json(view);
58    serde_json::to_string_pretty(&erd).unwrap_or_else(|_| "{}".to_string())
59}
60
61/// Build the JSON structure
62pub fn build_erd_json(view: &GraphView) -> ErdJson {
63    let mut total_columns = 0;
64
65    // Build tables with columns
66    let tables: Vec<TableJson> = view
67        .sorted_tables()
68        .iter()
69        .map(|table| {
70            let columns: Vec<ColumnJson> = table
71                .columns
72                .iter()
73                .map(|col| ColumnJson {
74                    name: col.name.clone(),
75                    col_type: col.col_type.clone(),
76                    is_primary_key: col.is_primary_key,
77                    is_foreign_key: col.is_foreign_key,
78                    is_nullable: col.is_nullable,
79                    references_table: col.references_table.clone(),
80                    references_column: col.references_column.clone(),
81                })
82                .collect();
83
84            total_columns += columns.len();
85
86            TableJson {
87                name: table.name.clone(),
88                columns,
89            }
90        })
91        .collect();
92
93    // Build relationships
94    let relationships: Vec<RelationshipJson> = view
95        .edges
96        .iter()
97        .map(|e| RelationshipJson {
98            from_table: e.from_table.clone(),
99            from_column: e.from_column.clone(),
100            to_table: e.to_table.clone(),
101            to_column: e.to_column.clone(),
102            cardinality: format!("{:?}", e.cardinality),
103        })
104        .collect();
105
106    ErdJson {
107        tables,
108        relationships,
109        stats: ErdStats {
110            table_count: view.table_count(),
111            column_count: total_columns,
112            relationship_count: view.edge_count(),
113        },
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::graph::view::{Cardinality, ColumnInfo, EdgeInfo, TableInfo};
121    use ahash::AHashMap;
122
123    fn create_test_view() -> GraphView {
124        let mut tables = AHashMap::new();
125
126        tables.insert(
127            "users".to_string(),
128            TableInfo {
129                name: "users".to_string(),
130                columns: vec![
131                    ColumnInfo {
132                        name: "id".to_string(),
133                        col_type: "INT".to_string(),
134                        is_primary_key: true,
135                        is_foreign_key: false,
136                        is_nullable: false,
137                        references_table: None,
138                        references_column: None,
139                    },
140                    ColumnInfo {
141                        name: "email".to_string(),
142                        col_type: "VARCHAR".to_string(),
143                        is_primary_key: false,
144                        is_foreign_key: false,
145                        is_nullable: true,
146                        references_table: None,
147                        references_column: None,
148                    },
149                ],
150            },
151        );
152
153        tables.insert(
154            "orders".to_string(),
155            TableInfo {
156                name: "orders".to_string(),
157                columns: vec![ColumnInfo {
158                    name: "user_id".to_string(),
159                    col_type: "INT".to_string(),
160                    is_primary_key: false,
161                    is_foreign_key: true,
162                    is_nullable: false,
163                    references_table: Some("users".to_string()),
164                    references_column: Some("id".to_string()),
165                }],
166            },
167        );
168
169        let edges = vec![EdgeInfo {
170            from_table: "orders".to_string(),
171            from_column: "user_id".to_string(),
172            to_table: "users".to_string(),
173            to_column: "id".to_string(),
174            cardinality: Cardinality::ManyToOne,
175        }];
176
177        GraphView { tables, edges }
178    }
179
180    #[test]
181    fn test_json_structure() {
182        let view = create_test_view();
183        let erd = build_erd_json(&view);
184
185        assert_eq!(erd.tables.len(), 2);
186        assert_eq!(erd.relationships.len(), 1);
187        assert_eq!(erd.stats.table_count, 2);
188        assert_eq!(erd.stats.column_count, 3);
189    }
190
191    #[test]
192    fn test_json_columns() {
193        let view = create_test_view();
194        let erd = build_erd_json(&view);
195
196        let users = erd.tables.iter().find(|t| t.name == "users").unwrap();
197        assert_eq!(users.columns.len(), 2);
198
199        let id_col = users.columns.iter().find(|c| c.name == "id").unwrap();
200        assert!(id_col.is_primary_key);
201        assert!(!id_col.is_nullable);
202    }
203
204    #[test]
205    fn test_json_fk_references() {
206        let view = create_test_view();
207        let erd = build_erd_json(&view);
208
209        let orders = erd.tables.iter().find(|t| t.name == "orders").unwrap();
210        let fk_col = orders.columns.iter().find(|c| c.name == "user_id").unwrap();
211
212        assert!(fk_col.is_foreign_key);
213        assert_eq!(fk_col.references_table, Some("users".to_string()));
214        assert_eq!(fk_col.references_column, Some("id".to_string()));
215    }
216
217    #[test]
218    fn test_json_output() {
219        let view = create_test_view();
220        let output = to_json(&view);
221
222        assert!(output.contains("\"name\": \"orders\""));
223        assert!(output.contains("\"is_primary_key\": true"));
224        assert!(output.contains("\"references_table\": \"users\""));
225    }
226}