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