sql_splitter/graph/format/
json.rs1use crate::graph::view::GraphView;
4use serde::Serialize;
5
6#[derive(Debug, Serialize)]
8pub struct ErdJson {
9 pub tables: Vec<TableJson>,
10 pub relationships: Vec<RelationshipJson>,
11 pub stats: ErdStats,
12}
13
14#[derive(Debug, Serialize)]
16pub struct TableJson {
17 pub name: String,
18 pub columns: Vec<ColumnJson>,
19}
20
21#[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#[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#[derive(Debug, Serialize)]
48pub struct ErdStats {
49 pub table_count: usize,
50 pub column_count: usize,
51 pub relationship_count: usize,
52}
53
54pub 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
60pub fn build_erd_json(view: &GraphView) -> ErdJson {
62 let mut total_columns = 0;
63
64 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 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}