rorm_cli/utils/
migrations.rs1use std::fs::{read_dir, read_to_string, DirEntry, File};
2use std::io::Write;
3use std::path::Path;
4
5use anyhow::{anyhow, Context};
6use rorm_declaration::imr::InternalModelFormat;
7use rorm_declaration::migration::{Migration, MigrationFile};
8
9use crate::utils::imr_as_state::InternalModelFormatExt;
10use crate::utils::re::RE;
11
12pub fn convert_migration_to_file(migration: Migration, path: &Path) -> anyhow::Result<()> {
19 let toml_str = toml::to_string_pretty(&MigrationFile { migration })
20 .with_context(|| "Error while serializing migration")?;
21
22 let mut output = File::create(path).with_context(|| {
23 format!(
24 "Error while opening file {:?} to write migration into",
25 path.file_name()
26 )
27 })?;
28 write!(output, "{toml_str}").with_context(|| "Error while writing to migration file")?;
29
30 Ok(())
31}
32
33pub fn convert_file_to_migration(path: &DirEntry) -> anyhow::Result<MigrationFile> {
39 let toml_str = read_to_string(path.path()).with_context(|| {
40 format!(
41 "Error occurred while reading {}",
42 path.path().to_str().unwrap()
43 )
44 })?;
45
46 let mut migration: MigrationFile = toml::from_str(toml_str.as_str()).with_context(|| {
47 format!(
48 "Error while deserializing migration {:?} from TOML",
49 path.file_name()
50 )
51 })?;
52
53 migration.migration.id = path.path().file_stem().unwrap().to_str().unwrap()[..4].parse()?;
54 migration.migration.name = path.path().file_stem().unwrap().to_str().unwrap()[5..].to_string();
55
56 Ok(migration)
57}
58
59pub(crate) fn get_migration_files(migration_dir: &str) -> anyhow::Result<Vec<DirEntry>> {
60 let dir_entries =
61 read_dir(migration_dir).with_context(|| "Error while searching the migration directory")?;
62
63 let file_list: Vec<DirEntry> = dir_entries
64 .filter(|x| {
65 x.as_ref().unwrap().file_type().unwrap().is_file()
66 && RE.migration_allowed_name.is_match(
67 x.as_ref()
68 .unwrap()
69 .file_name()
70 .into_string()
71 .unwrap()
72 .as_str(),
73 )
74 })
75 .map(|x| x.unwrap())
76 .collect();
77
78 Ok(file_list)
79}
80
81pub fn get_existing_migrations(migration_dir: &str) -> anyhow::Result<Vec<Migration>> {
91 let migrations = get_all_existing_migrations(migration_dir)?;
92
93 let mut migration_list: Vec<Migration> = vec![];
94
95 for m in migrations {
97 if m.replaces.is_empty() {
98 migration_list.push(m);
99 }
100 }
101
102 let mut sorted_migration_list: Vec<Migration> = vec![];
103
104 let mut current_id = None;
105 loop {
106 match current_id {
107 None => {
108 if let Some(&initial) = migration_list
109 .iter()
110 .filter(|x| x.initial)
111 .collect::<Vec<&Migration>>()
112 .first()
113 {
114 current_id = Some(initial.id);
115 sorted_migration_list.push(initial.clone());
116 continue;
117 }
118 }
119 Some(curr) => {
120 if let Some(&next) = migration_list
121 .iter()
122 .filter(|x| {
123 if let Some(dependency) = x.dependency {
124 dependency == curr
125 } else {
126 false
127 }
128 })
129 .collect::<Vec<&Migration>>()
130 .first()
131 {
132 current_id = Some(next.id);
133 sorted_migration_list.push(next.clone());
134 continue;
135 }
136 }
137 }
138 break;
139 }
140
141 if sorted_migration_list.len() != migration_list.len() {
142 return Err(anyhow!("Migrations does not assemble to a coherent list."));
143 }
144
145 Ok(sorted_migration_list)
146}
147
148pub fn get_all_existing_migrations(migration_dir: &str) -> anyhow::Result<Vec<Migration>> {
154 let file_list = get_migration_files(migration_dir)?;
155 let mut migration_list: Vec<Migration> = vec![];
156 for file in &file_list {
157 migration_list.push(convert_file_to_migration(file)?.migration);
158 }
159
160 Ok(migration_list)
161}
162
163pub fn convert_migrations_to_internal_models(
169 migrations: &[Migration],
170) -> anyhow::Result<InternalModelFormat> {
171 let mut state = InternalModelFormat { models: Vec::new() };
172
173 for migration in migrations {
174 for operation in &migration.operations {
175 state.apply_operation(operation)?;
176 }
177 }
178
179 Ok(state)
180}
181
182#[cfg(test)]
183mod test {
184 use std::path::Path;
185
186 use rorm_declaration::migration::Migration;
187 use temp_dir::TempDir;
188
189 use crate::utils::migrations::{convert_migration_to_file, get_existing_migrations};
190
191 #[test]
192 fn test_get_existing_migrations_non_initial() {
193 let tmp = TempDir::new().expect("Could not create a temporary directory");
194 let p = tmp.path().join("0001_not_initial.toml");
195
196 let migration = Migration {
197 hash: "".to_string(),
198 initial: false,
199 id: 0,
200 name: "".to_string(),
201 dependency: None,
202 replaces: vec![],
203 operations: vec![],
204 };
205
206 convert_migration_to_file(migration, Path::new(p.to_str().unwrap()))
207 .expect("Could not write to file");
208
209 assert!(get_existing_migrations(tmp.path().to_str().unwrap()).is_err());
210 }
211
212 #[test]
213 fn test_get_existing_migrations_initial() {
214 let tmp = TempDir::new().expect("Could not create a temporary directory");
215 let p = tmp.path().join("0001_initial.toml");
216
217 let migration = Migration {
218 hash: "".to_string(),
219 initial: true,
220 id: 0,
221 name: "".to_string(),
222 dependency: None,
223 replaces: vec![],
224 operations: vec![],
225 };
226
227 convert_migration_to_file(migration, Path::new(p.to_str().unwrap()))
228 .expect("Could not write to file");
229
230 assert!(get_existing_migrations(tmp.path().to_str().unwrap()).is_ok());
231 }
232
233 #[test]
234 fn test_get_existing_migrations_multiple_connected() {
235 let tmp = TempDir::new().expect("Could not create a temporary directory");
236 let p = tmp.path().join("0001_initial.toml");
237 let p_2 = tmp.path().join("00002_foobar.toml");
238
239 let migration = Migration {
240 hash: "".to_string(),
241 initial: true,
242 id: 0,
243 name: "".to_string(),
244 dependency: None,
245 replaces: vec![],
246 operations: vec![],
247 };
248
249 convert_migration_to_file(migration, Path::new(p.to_str().unwrap()))
250 .expect("Could not write to file");
251
252 let migration = Migration {
253 hash: "".to_string(),
254 initial: false,
255 id: 0,
256 name: "".to_string(),
257 dependency: Some(1),
258 replaces: vec![],
259 operations: vec![],
260 };
261
262 convert_migration_to_file(migration, Path::new(p_2.to_str().unwrap()))
263 .expect("Could not write to file");
264
265 assert!(get_existing_migrations(tmp.path().to_str().unwrap()).is_ok());
266 }
267}