rust_ef/migration/
snapshot_io.rs1use super::types::{ModelSnapshot, SnapshotColumn, SnapshotEntityType};
4use crate::error::{EFError, EFResult};
5
6pub fn parse_model_snapshot_json(text: &str) -> EFResult<Option<ModelSnapshot>> {
8 snapshot_from_json(text)
9}
10
11pub(super) fn migration_io_err(e: std::io::Error) -> EFError {
12 EFError::migration(e.to_string())
13}
14
15pub(super) fn snapshot_to_json(snapshot: &ModelSnapshot) -> String {
16 let mut out = String::from("{\n \"migration_id\": \"");
17 out.push_str(&snapshot.migration_id.replace('"', "\\\""));
18 out.push_str("\",\n \"entity_types\": [\n");
19 for (i, et) in snapshot.entity_types.iter().enumerate() {
20 if i > 0 {
21 out.push(',');
22 }
23 out.push_str(" {\n");
24 out.push_str(&format!(
25 " \"type_name\": \"{}\",\n",
26 et.type_name.replace('"', "\\\"")
27 ));
28 out.push_str(&format!(
29 " \"table_name\": \"{}\",\n",
30 et.table_name.replace('"', "\\\"")
31 ));
32 out.push_str(" \"columns\": [\n");
33 for (j, col) in et.columns.iter().enumerate() {
34 if j > 0 {
35 out.push(',');
36 }
37 out.push_str(&format!(
38 " {{\"field_name\":\"{}\",\"column_name\":\"{}\",\"type_name\":\"{}\",\"is_primary_key\":{},\"is_required\":{},\"is_foreign_key\":{},\"max_length\":{},\"is_auto_increment\":{},\"is_sequence\":{},\"sequence_name\":{},\"fk_referenced_table\":{},\"fk_referenced_column\":{},\"has_index\":{},\"is_unique\":{},\"fk_on_delete\":{}}}\n",
39 col.field_name.replace('"', "\\\""),
40 col.column_name.replace('"', "\\\""),
41 col.type_name.replace('"', "\\\""),
42 col.is_primary_key,
43 col.is_required,
44 col.is_foreign_key,
45 col.max_length.map(|n| n.to_string()).unwrap_or_else(|| "null".into()),
46 col.is_auto_increment,
47 col.is_sequence,
48 col.sequence_name
49 .as_ref()
50 .map(|s| format!("\"{}\"", s.replace('"', "\\\"")))
51 .unwrap_or_else(|| "null".into()),
52 col.fk_referenced_table
53 .as_ref()
54 .map(|s| format!("\"{}\"", s.replace('"', "\\\"")))
55 .unwrap_or_else(|| "null".into()),
56 col.fk_referenced_column
57 .as_ref()
58 .map(|s| format!("\"{}\"", s.replace('"', "\\\"")))
59 .unwrap_or_else(|| "null".into()),
60 col.has_index,
61 col.is_unique,
62 col.fk_on_delete
63 .as_ref()
64 .map(|s| format!("\"{}\"", s.replace('"', "\\\"")))
65 .unwrap_or_else(|| "null".into())
66 ));
67 }
68 out.push_str(" ]\n }\n");
69 }
70 out.push_str(" ]\n}\n");
71 out
72}
73
74fn snapshot_from_json(text: &str) -> EFResult<Option<ModelSnapshot>> {
75 let migration_id =
77 extract_json_string(text, "migration_id").unwrap_or_else(|| "__snapshot__".to_string());
78 let mut entity_types = Vec::new();
79 if let Some(arr_start) = text.find("\"entity_types\"") {
80 let slice = &text[arr_start..];
81 for table_block in slice.split("\"table_name\"").skip(1) {
82 let table_name = extract_quoted_after_colon(table_block).unwrap_or_default();
83 let _type_name = entity_types.len().to_string(); let columns_start = table_block.find("\"columns\"");
85 let mut columns = Vec::new();
86 if let Some(cs) = columns_start {
87 let col_slice = &table_block[cs..];
88 for col_chunk in col_slice.split("\"field_name\"").skip(1) {
89 if let Some(field_name) = extract_quoted_after_colon(col_chunk) {
90 let column_name = extract_json_string(col_chunk, "column_name")
91 .unwrap_or_else(|| field_name.clone());
92 let type_name_col = extract_json_string(col_chunk, "type_name")
93 .unwrap_or_else(|| "String".to_string());
94 columns.push(SnapshotColumn {
95 field_name,
96 column_name,
97 type_name: type_name_col,
98 is_primary_key: col_chunk.contains("\"is_primary_key\":true"),
99 is_required: col_chunk.contains("\"is_required\":true"),
100 is_foreign_key: col_chunk.contains("\"is_foreign_key\":true"),
101 max_length: None,
102 is_auto_increment: col_chunk.contains("\"is_auto_increment\":true"),
103 is_sequence: col_chunk.contains("\"is_sequence\":true"),
104 sequence_name: extract_json_string(col_chunk, "sequence_name"),
105 fk_referenced_table: extract_json_string(
106 col_chunk,
107 "fk_referenced_table",
108 ),
109 fk_referenced_column: extract_json_string(
110 col_chunk,
111 "fk_referenced_column",
112 ),
113 has_index: col_chunk.contains("\"has_index\":true"),
114 is_unique: col_chunk.contains("\"is_unique\":true"),
115 fk_on_delete: extract_json_string(col_chunk, "fk_on_delete"),
116 });
117 }
118 }
119 }
120 if !table_name.is_empty() {
121 entity_types.push(SnapshotEntityType {
122 type_name: table_name.clone(),
123 table_name,
124 columns,
125 });
126 }
127 }
128 }
129 if entity_types.is_empty() {
130 return Ok(None);
131 }
132 Ok(Some(ModelSnapshot {
133 migration_id,
134 entity_types,
135 }))
136}
137
138fn extract_json_string(haystack: &str, key: &str) -> Option<String> {
139 let needle = format!("\"{key}\"");
140 let pos = haystack.find(&needle)?;
141 extract_quoted_after_colon(&haystack[pos + needle.len()..])
142}
143
144fn extract_quoted_after_colon(s: &str) -> Option<String> {
145 let colon = s.find(':')?;
146 let rest = s[colon + 1..].trim_start();
147 if !rest.starts_with('"') {
148 return None;
149 }
150 let inner = &rest[1..];
151 let end = inner.find('"')?;
152 Some(inner[..end].to_string())
153}