My personal project and infrastructure archive
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nomicon/development/libs/barrel/src/integrations/diesel.rs

197 lines
5.8 KiB

//!
// This integration relies on _knowing_ which backend is being used at compile-time
// This is a poor woman's XOR - if you know how to make it more pretty, PRs welcome <3
#[cfg(any(
all(feature = "pg", feature = "mysql"),
all(feature = "pg", feature = "sqlite3"),
all(feature = "mysql", feature = "sqlite3")
))]
compile_error!("`barrel` can only integrate with `diesel` if you select one (1) backend!");
use diesel_rs::connection::SimpleConnection;
use diesel_rs::migration::{Migration, RunMigrationsError};
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::process::Command;
/// Represents a migration run inside Diesel
///
/// 1. Path
/// 2. Version
/// 3. Up
/// 4. Down
pub struct BarrelMigration(PathBuf, String, String, String);
impl Migration for BarrelMigration {
fn file_path(&self) -> Option<&Path> {
Some(self.0.as_path())
}
fn version(&self) -> &str {
&self.1
}
fn run(&self, conn: &SimpleConnection) -> Result<(), RunMigrationsError> {
conn.batch_execute(&self.2)?;
Ok(())
}
fn revert(&self, conn: &SimpleConnection) -> Result<(), RunMigrationsError> {
conn.batch_execute(&self.3)?;
Ok(())
}
}
/// Generate migration files using the barrel schema builder
pub fn generate_initial(path: &PathBuf) {
generate_initial_with_content(
path,
&"fn up(migr: &mut Migration) {} \n\n".to_string(),
&"fn down(migr: &mut Migration) {} \n".to_string(),
)
}
/// Generate migration files using the barrel schema builder with initial content
pub fn generate_initial_with_content(path: &PathBuf, up_content: &String, down_content: &String) {
let migr_path = path.join("mod.rs");
println!("Creating {}", migr_path.display());
let mut barrel_migr = fs::File::create(migr_path).unwrap();
barrel_migr.write(b"/// Handle up migrations \n").unwrap();
barrel_migr.write(up_content.as_bytes()).unwrap();
barrel_migr.write(b"/// Handle down migrations \n").unwrap();
barrel_migr.write(down_content.as_bytes()).unwrap();
}
/// Generate a Migration from the provided path
pub fn migration_from(path: &Path) -> Option<Box<Migration>> {
match path.join("mod.rs").exists() {
true => Some(run_barrel_migration_wrapper(&path.join("mod.rs"))),
false => None,
}
}
fn version_from_path(path: &Path) -> Result<String, ()> {
path.parent()
.unwrap_or_else(|| {
panic!(
"Migration doesn't appear to be in a directory: `{:?}`",
path
)
})
.file_name()
.unwrap_or_else(|| panic!("Can't get file name from path `{:?}`", path))
.to_string_lossy()
.split('_')
.nth(0)
.map(|s| Ok(s.replace('-', "")))
.unwrap_or_else(|| Err(()))
}
fn run_barrel_migration_wrapper(path: &Path) -> Box<Migration> {
let (up, down) = run_barrel_migration(&path);
let version = version_from_path(path).unwrap();
let migration_path = match path.parent() {
Some(parent_path) => parent_path.to_path_buf(),
None => path.to_path_buf(),
};
Box::new(BarrelMigration(migration_path, version, up, down))
}
fn run_barrel_migration(migration: &Path) -> (String, String) {
/* Create a tmp dir with src/ child */
use tempfile::Builder;
let dir = Builder::new().prefix("barrel").tempdir().unwrap();
fs::create_dir_all(&dir.path().join("src")).unwrap();
let (feat, ident) = get_backend_pair();
let toml = format!(
"# This file is auto generated by barrel
[package]
name = \"tmp-generator\"
description = \"Doing nasty things with cargo\"
version = \"0.0.0\"
authors = [\"Katharina Fey <kookie@spacekookie.de>\"]
# TODO: Use same `barrel` dependency as crate
[dependencies]
barrel = {{ version = \"*\", features = [ {:?} ] }}",
feat
);
/* Add a Cargo.toml file */
let ct = dir.path().join("Cargo.toml");
let mut cargo_toml = File::create(&ct).unwrap();
cargo_toml.write_all(toml.as_bytes()).unwrap();
/* Generate main.rs based on user migration */
let main_file_path = &dir.path().join("src").join("main.rs");
let mut main_file = File::create(&main_file_path).unwrap();
let user_migration = migration.as_os_str().to_os_string().into_string().unwrap();
main_file
.write_all(
format!(
"//! This file is auto generated by barrel
extern crate barrel;
use barrel::*;
use barrel::backend::{ident};
include!(\"{}\");
fn main() {{
let mut m_up = Migration::new();
up(&mut m_up);
println!(\"{{}}\", m_up.make::<{ident}>());
let mut m_down = Migration::new();
down(&mut m_down);
println!(\"{{}}\", m_down.make::<{ident}>());
}}
",
user_migration,
ident = ident
)
.as_bytes(),
)
.unwrap();
let output = if cfg!(target_os = "windows") {
Command::new("cargo")
.current_dir(dir.path())
.arg("run")
.output()
.expect("failed to execute cargo!")
} else {
Command::new("sh")
.current_dir(dir.path())
.arg("-c")
.arg("cargo run")
.output()
.expect("failed to execute cargo!")
};
let output = String::from_utf8_lossy(&output.stdout);
let vec: Vec<&str> = output.split("\n").collect();
let up = String::from(vec[0]);
let down = String::from(vec[1]);
(up, down)
}
/// Uses the fact that barrel with diesel support is only compiled with _one_ feature
///
/// The first string is the feature-name, the other the struct ident
fn get_backend_pair() -> (&'static str, &'static str) {
#[cfg(feature = "pg")]
return ("pg", "Pg");
#[cfg(feature = "mysql")]
return ("mysql", "Mysql");
#[cfg(feature = "sqlite3")]
return ("sqlite3", "Sqlite");
}