1use super::types::{
4 ModelSnapshot, SnapshotCheck, SnapshotColumn, SnapshotEntityType, SnapshotForeignKey,
5 SnapshotIndex, SnapshotIndexColumn, SnapshotTableOptions,
6};
7use crate::error::{EFError, EFResult};
8use serde_json::{json, Map, Value};
9
10pub fn parse_model_snapshot_json(text: &str) -> EFResult<Option<ModelSnapshot>> {
13 snapshot_from_json(text)
14}
15
16pub(super) fn migration_io_err(e: std::io::Error) -> EFError {
17 EFError::migration(e.to_string())
18}
19
20pub(super) fn snapshot_to_json(snapshot: &ModelSnapshot) -> String {
21 let entity_types: Vec<Value> = snapshot
22 .entity_types
23 .iter()
24 .map(|et| {
25 let columns: Vec<Value> = et
26 .columns
27 .iter()
28 .map(|c| {
29 json!({
30 "field_name": c.field_name,
31 "column_name": c.column_name,
32 "type_name": c.type_name,
33 "is_primary_key": c.is_primary_key,
34 "is_required": c.is_required,
35 "is_foreign_key": c.is_foreign_key,
36 "max_length": c.max_length,
37 "is_auto_increment": c.is_auto_increment,
38 "is_sequence": c.is_sequence,
39 "sequence_name": c.sequence_name,
40 "fk_referenced_table": c.fk_referenced_table,
41 "fk_referenced_column": c.fk_referenced_column,
42 "has_index": c.has_index,
43 "is_unique": c.is_unique,
44 "fk_on_delete": c.fk_on_delete,
45 "default_value": c.default_value,
46 "column_type": c.column_type,
47 "precision": c.precision,
48 "scale": c.scale,
49 "is_fixed_length": c.is_fixed_length,
50 "collation": c.collation,
51 "comment": c.comment,
52 "computed_sql": c.computed_sql,
53 "computed_stored": c.computed_stored,
54 })
55 })
56 .collect();
57 let indexes: Vec<Value> = et
58 .indexes
59 .iter()
60 .map(|i| {
61 json!({
62 "name": i.name,
63 "columns": i
64 .columns
65 .iter()
66 .map(|c| json!({"value": c.value, "is_expression": c.is_expression}))
67 .collect::<Vec<_>>(),
68 "is_unique": i.is_unique,
69 "filter": i.filter,
70 "include": i.include,
71 })
72 })
73 .collect();
74 let foreign_keys: Vec<Value> = et
75 .foreign_keys
76 .iter()
77 .map(|f| {
78 json!({
79 "name": f.name,
80 "columns": f.columns,
81 "referenced_table": f.referenced_table,
82 "referenced_columns": f.referenced_columns,
83 "on_delete": f.on_delete,
84 })
85 })
86 .collect();
87 let checks: Vec<Value> = et
88 .checks
89 .iter()
90 .map(|c| json!({"name": c.name, "expression": c.expression}))
91 .collect();
92 json!({
93 "type_name": et.type_name,
94 "table_name": et.table_name,
95 "columns": columns,
96 "indexes": indexes,
97 "foreign_keys": foreign_keys,
98 "checks": checks,
99 "table_options": {
100 "engine": et.table_options.engine,
101 "charset": et.table_options.charset,
102 "collate": et.table_options.collate,
103 },
104 "is_view": et.is_view,
105 "is_keyless": et.is_keyless,
106 "comment": et.comment,
107 })
108 })
109 .collect();
110
111 serde_json::to_string_pretty(&json!({
112 "migration_id": snapshot.migration_id,
113 "entity_types": entity_types,
114 }))
115 .unwrap_or_else(|_| "{}".to_string())
116}
117
118fn snapshot_from_json(text: &str) -> EFResult<Option<ModelSnapshot>> {
119 let root: Value = match serde_json::from_str(text) {
120 Ok(v) => v,
121 Err(_) => return Ok(None),
122 };
123 let Some(entity_types) = root.get("entity_types").and_then(Value::as_array) else {
124 return Ok(None);
125 };
126
127 let migration_id = root
128 .get("migration_id")
129 .and_then(Value::as_str)
130 .unwrap_or("__snapshot__")
131 .to_string();
132
133 let mut types = Vec::with_capacity(entity_types.len());
134 for et in entity_types {
135 let Some(obj) = et.as_object() else {
136 continue;
137 };
138 let table_name = string_field(obj, "table_name");
139 if table_name.is_empty() {
140 continue;
141 }
142 let columns = obj
143 .get("columns")
144 .and_then(Value::as_array)
145 .map(|cols| {
146 cols.iter()
147 .filter_map(Value::as_object)
148 .map(|c| SnapshotColumn {
149 field_name: string_field(c, "field_name"),
150 column_name: {
151 let name = string_field(c, "column_name");
152 if name.is_empty() {
153 string_field(c, "field_name")
154 } else {
155 name
156 }
157 },
158 type_name: {
159 let name = string_field(c, "type_name");
160 if name.is_empty() {
161 "String".to_string()
162 } else {
163 name
164 }
165 },
166 is_primary_key: bool_field(c, "is_primary_key"),
167 is_required: bool_field(c, "is_required"),
168 is_foreign_key: bool_field(c, "is_foreign_key"),
169 max_length: c
170 .get("max_length")
171 .and_then(Value::as_u64)
172 .map(|n| n as usize),
173 is_auto_increment: bool_field(c, "is_auto_increment"),
174 is_sequence: bool_field(c, "is_sequence"),
175 sequence_name: opt_string(c, "sequence_name"),
176 fk_referenced_table: opt_string(c, "fk_referenced_table"),
177 fk_referenced_column: opt_string(c, "fk_referenced_column"),
178 has_index: bool_field(c, "has_index"),
179 is_unique: bool_field(c, "is_unique"),
180 fk_on_delete: opt_string(c, "fk_on_delete"),
181 default_value: opt_string(c, "default_value"),
182 column_type: opt_string(c, "column_type"),
183 precision: c.get("precision").and_then(Value::as_u64).map(|n| n as u32),
184 scale: c.get("scale").and_then(Value::as_u64).map(|n| n as u32),
185 is_fixed_length: bool_field(c, "is_fixed_length"),
186 collation: opt_string(c, "collation"),
187 comment: opt_string(c, "comment"),
188 computed_sql: opt_string(c, "computed_sql"),
189 computed_stored: bool_field(c, "computed_stored"),
190 })
191 .collect()
192 })
193 .unwrap_or_default();
194
195 let indexes = obj
196 .get("indexes")
197 .and_then(Value::as_array)
198 .map(|items| {
199 items
200 .iter()
201 .filter_map(Value::as_object)
202 .map(|i| SnapshotIndex {
203 name: string_field(i, "name"),
204 columns: i
205 .get("columns")
206 .and_then(Value::as_array)
207 .map(|cols| {
208 cols.iter()
209 .filter_map(Value::as_object)
210 .map(|c| SnapshotIndexColumn {
211 value: string_field(c, "value"),
212 is_expression: bool_field(c, "is_expression"),
213 })
214 .collect()
215 })
216 .unwrap_or_default(),
217 is_unique: bool_field(i, "is_unique"),
218 filter: opt_string(i, "filter"),
219 include: string_array(i, "include"),
220 })
221 .collect()
222 })
223 .unwrap_or_default();
224
225 let foreign_keys = obj
226 .get("foreign_keys")
227 .and_then(Value::as_array)
228 .map(|items| {
229 items
230 .iter()
231 .filter_map(Value::as_object)
232 .map(|f| SnapshotForeignKey {
233 name: string_field(f, "name"),
234 columns: string_array(f, "columns"),
235 referenced_table: string_field(f, "referenced_table"),
236 referenced_columns: string_array(f, "referenced_columns"),
237 on_delete: opt_string(f, "on_delete"),
238 })
239 .collect()
240 })
241 .unwrap_or_default();
242
243 let checks = obj
244 .get("checks")
245 .and_then(Value::as_array)
246 .map(|items| {
247 items
248 .iter()
249 .filter_map(Value::as_object)
250 .map(|c| SnapshotCheck {
251 name: string_field(c, "name"),
252 expression: string_field(c, "expression"),
253 })
254 .collect()
255 })
256 .unwrap_or_default();
257
258 let table_options = obj
259 .get("table_options")
260 .and_then(Value::as_object)
261 .map(|o| SnapshotTableOptions {
262 engine: opt_string(o, "engine"),
263 charset: opt_string(o, "charset"),
264 collate: opt_string(o, "collate"),
265 })
266 .unwrap_or_default();
267
268 types.push(SnapshotEntityType {
269 type_name: {
270 let name = string_field(obj, "type_name");
271 if name.is_empty() {
272 table_name.clone()
273 } else {
274 name
275 }
276 },
277 table_name,
278 columns,
279 indexes,
280 foreign_keys,
281 checks,
282 table_options,
283 is_view: bool_field(obj, "is_view"),
284 is_keyless: bool_field(obj, "is_keyless"),
285 comment: opt_string(obj, "comment"),
286 });
287 }
288
289 if types.is_empty() {
290 return Ok(None);
291 }
292 Ok(Some(ModelSnapshot {
293 migration_id,
294 entity_types: types,
295 }))
296}
297
298fn string_field(obj: &Map<String, Value>, key: &str) -> String {
299 obj.get(key)
300 .and_then(Value::as_str)
301 .unwrap_or_default()
302 .to_string()
303}
304
305fn opt_string(obj: &Map<String, Value>, key: &str) -> Option<String> {
306 obj.get(key).and_then(Value::as_str).map(str::to_string)
307}
308
309fn bool_field(obj: &Map<String, Value>, key: &str) -> bool {
310 obj.get(key).and_then(Value::as_bool).unwrap_or(false)
311}
312
313fn string_array(obj: &Map<String, Value>, key: &str) -> Vec<String> {
314 obj.get(key)
315 .and_then(Value::as_array)
316 .map(|items| {
317 items
318 .iter()
319 .filter_map(Value::as_str)
320 .map(str::to_string)
321 .collect()
322 })
323 .unwrap_or_default()
324}