database_migration_files/
lib.rs1#![doc(html_root_url = "https://docs.rs/database-migration-files/0.2.0")]
2
3use database_migration::checksum::hash_migration_script;
4use database_migration::definition::{ExcludedFiles, GetFilename, ParseMigration};
5use database_migration::error::Error;
6use database_migration::migration::{Migration, NewMigration, ScriptContent};
7use database_migration::repository::{CreateNewMigration, ListMigrations, ReadScriptContent};
8use std::fs;
9use std::fs::File;
10#[cfg(target_family = "windows")]
11use std::os::windows::fs::FileTypeExt;
12use std::path::Path;
13use walkdir::WalkDir;
14
15#[derive(Clone)]
16pub struct MigrationDirectory<'a> {
17 path: &'a Path,
18 excluded_files: &'a ExcludedFiles,
19}
20
21impl<'a> MigrationDirectory<'a> {
22 pub const fn new(path: &'a Path, excluded_files: &'a ExcludedFiles) -> Self {
23 Self {
24 path,
25 excluded_files,
26 }
27 }
28
29 pub fn create_directory_if_not_existing(&self) -> Result<(), Error> {
30 if self.path.exists() {
31 return Ok(());
32 }
33 fs::create_dir_all(self.path)
34 .map_err(|err| Error::CreatingMigrationsFolder(err.to_string()))
35 }
36
37 pub const fn files<S>(&self, filename_strategy: S) -> MigrationFiles<'a, S> {
38 MigrationFiles::new(self.path, filename_strategy)
39 }
40}
41
42impl ListMigrations for MigrationDirectory<'_> {
43 type Iter = MigDirIter;
44
45 fn list_all_migrations(&self) -> Result<Self::Iter, Error> {
46 if !self.path.exists() {
47 return Err(Error::ScanningMigrationDirectory(format!(
48 r#"migrations folder "{}" does not exist"#,
49 self.path.display()
50 )));
51 }
52 let walk_dir = WalkDir::new(self.path);
53 Ok(MigDirIter {
54 walker: walk_dir.into_iter(),
55 excluded_files: self.excluded_files.clone(),
56 })
57 }
58}
59
60impl ReadScriptContent for MigrationDirectory<'_> {
61 fn read_script_content(&self, migration: &Migration) -> Result<ScriptContent, Error> {
62 let content = fs::read_to_string(&migration.script_path)
63 .map_err(|err| Error::ReadingMigrationFile(err.to_string()))?;
64 let checksum = hash_migration_script(migration, &content);
65 Ok(ScriptContent {
66 key: migration.key,
67 kind: migration.kind,
68 path: migration.script_path.clone(),
69 content,
70 checksum,
71 })
72 }
73}
74
75#[derive(Debug)]
76pub struct MigDirIter {
77 walker: walkdir::IntoIter,
78 excluded_files: ExcludedFiles,
79}
80
81impl Iterator for MigDirIter {
82 type Item = Result<Migration, Error>;
83
84 fn next(&mut self) -> Option<Self::Item> {
85 for dir_entry in &mut self.walker {
86 return match dir_entry {
87 Ok(entry) => {
88 let file_type = entry.file_type();
89 #[cfg(target_family = "windows")]
90 if file_type.is_dir() || file_type.is_symlink_dir() {
91 continue;
92 }
93 #[cfg(not(target_family = "windows"))]
94 if file_type.is_dir() {
95 continue;
96 }
97 let file_path = entry.path();
98 if self.excluded_files.matches(file_path) {
99 continue;
100 }
101 Some(file_path.parse_migration().map_err(Error::from))
102 },
103 Err(err) => Some(Err(Error::ScanningMigrationDirectory(err.to_string()))),
104 };
105 }
106 None
107 }
108
109 fn size_hint(&self) -> (usize, Option<usize>) {
110 self.walker.size_hint()
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub struct MigrationFiles<'a, S> {
116 path: &'a Path,
117 filename_strategy: S,
118}
119
120impl<'a, S> MigrationFiles<'a, S> {
121 pub const fn new(path: &'a Path, filename_strategy: S) -> Self {
122 Self {
123 path,
124 filename_strategy,
125 }
126 }
127}
128
129impl<S> CreateNewMigration for MigrationFiles<'_, S>
130where
131 S: GetFilename,
132{
133 fn create_new_migration(&self, new_migration: NewMigration) -> Result<Migration, Error> {
134 let filename = self.filename_strategy.get_filename(&new_migration);
135 let script_path = self.path.join(&filename);
136 File::create_new(&script_path).map_err(|err| Error::CreatingScriptFile(err.to_string()))?;
137 Ok(Migration {
138 key: new_migration.key,
139 title: new_migration.title,
140 kind: new_migration.kind,
141 script_path,
142 })
143 }
144}
145
146#[cfg(test)]
147mod tests;
148
149#[cfg(test)]
152mod dummy_extern_uses {
153 use version_sync as _;
154}