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