ts_sql_helper_lib/
migrations.rs1use std::{env::current_dir, ffi::OsStr, fs, io};
5
6use postgres::GenericClient;
7
8pub fn perform_migrations<C: GenericClient>(client: &mut C) -> Result<(), MigrationError> {
11 let Ok(current_dir) = current_dir() else {
12 return Ok(());
13 };
14
15 dbg!(¤t_dir);
16
17 let migrations_dir = current_dir.join("migrations");
18 if !fs::exists(&migrations_dir).unwrap() {
19 return Ok(());
20 }
21
22 let directory = fs::read_dir(&migrations_dir)
23 .map_err(|source| MigrationError::ReadMigrationDirectory { source })?;
24 let mut entries: Vec<_> = directory
25 .filter_map(|entry| match entry {
26 Ok(entry) => {
27 if entry
28 .path()
29 .extension()
30 .is_some_and(|extension| extension == OsStr::new("sql"))
31 {
32 Some(Ok(entry))
33 } else {
34 None
35 }
36 }
37 Err(error) => Some(Err(error)),
38 })
39 .collect::<Result<_, _>>()
40 .map_err(|source| MigrationError::ReadMigrationFile { source })?;
41 entries.sort_by_key(|entry| entry.file_name());
42
43 for entry in entries {
44 let sql = fs::read_to_string(entry.path())
45 .map_err(|source| MigrationError::ReadMigrationFile { source })?;
46 client
47 .batch_execute(&sql)
48 .map_err(|source| MigrationError::ExecuteMigration { source, sql })?;
49 }
50
51 Ok(())
52}
53
54#[derive(Debug)]
56#[non_exhaustive]
57#[allow(missing_docs)]
58pub enum MigrationError {
59 #[non_exhaustive]
60 ReadMigrationDirectory { source: io::Error },
61
62 #[non_exhaustive]
63 ReadMigrationFile { source: io::Error },
64
65 #[non_exhaustive]
66 ExecuteMigration {
67 source: postgres::Error,
68 sql: String,
69 },
70}
71impl core::fmt::Display for MigrationError {
72 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
73 match &self {
74 Self::ReadMigrationDirectory { .. } => write!(f, "could not read migration directory"),
75 Self::ReadMigrationFile { .. } => write!(f, "could not read a migration file"),
76 Self::ExecuteMigration { sql, .. } => write!(f, "migration `{sql}` failed to execute"),
77 }
78 }
79}
80impl core::error::Error for MigrationError {
81 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
82 match &self {
83 Self::ReadMigrationDirectory { source, .. } => Some(source),
84 Self::ReadMigrationFile { source, .. } => Some(source),
85 Self::ExecuteMigration { source, .. } => Some(source),
86 }
87 }
88}