sql_splitter/graph/format/
json.rs

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