diff --git a/Cargo.lock b/Cargo.lock index 1d6d0fc751..8249ae4f70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,12 +384,55 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.81" @@ -1909,8 +1952,10 @@ version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", ] [[package]] @@ -1940,6 +1985,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "common_enums" version = "0.1.0" @@ -2016,7 +2067,7 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml", + "toml 0.8.12", "yaml-rust", ] @@ -2029,7 +2080,7 @@ dependencies = [ "indexmap 2.2.6", "serde", "serde_json", - "toml", + "toml 0.8.12", ] [[package]] @@ -2039,7 +2090,7 @@ dependencies = [ "api_models", "serde", "serde_with", - "toml", + "toml 0.8.12", "utoipa", ] @@ -2588,9 +2639,9 @@ checksum = "b6e854126756c496b8c81dec88f9a706b15b875c5849d4097a3854476b9fdf94" [[package]] name = "diesel" -version = "2.1.5" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fc05c17098f21b89bc7d98fe1dd3cce2c11c2ad8e145f2a44fe08ed28eb559" +checksum = "ff236accb9a5069572099f0b350a92e9560e8e63a9b8d546162f4a5e03026bb2" dependencies = [ "bitflags 2.5.0", "byteorder", @@ -2604,9 +2655,9 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.1.3" +version = "2.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d02eecb814ae714ffe61ddc2db2dd03e6c49a42e269b5001355500d431cce0c" +checksum = "14701062d6bed917b5c7103bdffaee1e4609279e240488ad24e7bd979ca6866c" dependencies = [ "diesel_table_macro_syntax", "proc-macro2", @@ -2614,6 +2665,17 @@ dependencies = [ "syn 2.0.57", ] +[[package]] +name = "diesel_migrations" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", +] + [[package]] name = "diesel_models" version = "0.1.0" @@ -3584,6 +3646,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hsdev" +version = "0.1.0" +dependencies = [ + "clap", + "diesel", + "diesel_migrations", + "serde", + "toml 0.5.11", +] + [[package]] name = "http" version = "0.2.12" @@ -4020,6 +4093,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "iso_country" version = "0.1.4" @@ -4396,6 +4475,27 @@ dependencies = [ "autocfg", ] +[[package]] +name = "migrations_internals" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" +dependencies = [ + "serde", + "toml 0.7.8", +] + +[[package]] +name = "migrations_macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08" +dependencies = [ + "migrations_internals", + "proc-macro2", + "quote", +] + [[package]] name = "mimalloc" version = "0.1.39" @@ -7370,7 +7470,7 @@ dependencies = [ "thirtyfour", "time", "tokio 1.37.0", - "toml", + "toml 0.8.12", ] [[package]] @@ -7798,6 +7898,27 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + [[package]] name = "toml" version = "0.8.12" @@ -7827,6 +7948,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.2.6", + "serde", + "serde_spanned", "toml_datetime", "winnow 0.5.40", ] @@ -8243,6 +8366,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "110352d4e9076c67839003c7788d8604e24dcded13e0b375af3efaa8cf468517" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "utoipa" version = "4.2.0" diff --git a/crates/hsdev/Cargo.toml b/crates/hsdev/Cargo.toml new file mode 100644 index 0000000000..6600d41d9f --- /dev/null +++ b/crates/hsdev/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "hsdev" +version = "0.1.0" +license.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "A simple diesel postgres migrator that uses TOML files" +repository = "https://github.com/juspay/hyperswitch.git" +readme = "README.md" + +[dependencies] +diesel = { version = "2.1.6", features = ["postgres"] } +diesel_migrations = "2.1.0" +toml = "0.5" +clap = { version = "4.1.8", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } diff --git a/crates/hsdev/README.md b/crates/hsdev/README.md new file mode 100644 index 0000000000..926d9b9056 --- /dev/null +++ b/crates/hsdev/README.md @@ -0,0 +1,24 @@ +# HSDEV + +`hsdev` is a simple diesel Postgres migration tool. It is designed to simply running a Postgres database migration with diesel. + +## Installing hsdev +`hsdev` can be installed using `cargo` +```shell +cargo install --force --path crates/hsdev +``` + +## Using hsdev +Using `hsdev` is simple. All you need to do is run the following command. +```shell +hsdev --toml-file [path/to/TOML/file] +``` + +provide `hsdev` with a TOML file containing the following keys: +```toml +username = "your_username" +password = "your_password" +dbname = "your_db_name" +``` + +Simply run the command and let `hsdev` handle the rest. diff --git a/crates/hsdev/src/input_file.rs b/crates/hsdev/src/input_file.rs new file mode 100644 index 0000000000..0d5d0a2eb0 --- /dev/null +++ b/crates/hsdev/src/input_file.rs @@ -0,0 +1,26 @@ +use std::string::String; + +use serde::Deserialize; +use toml::Value; + +#[derive(Deserialize)] +pub struct InputData { + username: String, + password: String, + dbname: String, + host: String, + port: u16, +} + +impl InputData { + pub fn read(db_table: &Value) -> Result { + db_table.clone().try_into() + } + + pub fn postgres_url(&self) -> String { + format!( + "postgres://{}:{}@{}:{}/{}", + self.username, self.password, self.host, self.port, self.dbname + ) + } +} diff --git a/crates/hsdev/src/main.rs b/crates/hsdev/src/main.rs new file mode 100644 index 0000000000..e6ee9e17d4 --- /dev/null +++ b/crates/hsdev/src/main.rs @@ -0,0 +1,139 @@ +use clap::{Parser, ValueHint}; +use diesel::{pg::PgConnection, Connection}; +use diesel_migrations::{FileBasedMigrations, HarnessWithOutput, MigrationHarness}; +use toml::Value; + +mod input_file; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + #[arg(short, long, value_hint = ValueHint::FilePath)] + toml_file: std::path::PathBuf, + + #[arg(long, default_value_t = String::from(""))] + toml_table: String, +} + +fn main() { + let args = Args::parse(); + + let toml_file = &args.toml_file; + let table_name = &args.toml_table; + let toml_contents = match std::fs::read_to_string(toml_file) { + Ok(contents) => contents, + Err(e) => { + eprintln!("Error reading TOML file: {}", e); + return; + } + }; + let toml_data: Value = match toml_contents.parse() { + Ok(data) => data, + Err(e) => { + eprintln!("Error parsing TOML file: {}", e); + return; + } + }; + + let table = get_toml_table(table_name, &toml_data); + + let input = match input_file::InputData::read(table) { + Ok(data) => data, + Err(e) => { + eprintln!("Error loading TOML file: {}", e); + return; + } + }; + + let db_url = input.postgres_url(); + + println!("Attempting to connect to {}", db_url); + + let mut conn = match PgConnection::establish(&db_url) { + Ok(value) => value, + Err(_) => { + eprintln!("Unable to establish database connection"); + return; + } + }; + + let migrations = match FileBasedMigrations::find_migrations_directory() { + Ok(value) => value, + Err(_) => { + eprintln!("Could not find migrations directory"); + return; + } + }; + + let mut harness = HarnessWithOutput::write_to_stdout(&mut conn); + + match harness.run_pending_migrations(migrations) { + Ok(_) => println!("Successfully ran migrations"), + Err(_) => eprintln!("Couldn't run migrations"), + }; +} + +pub fn get_toml_table<'a>(table_name: &'a str, toml_data: &'a Value) -> &'a Value { + if !table_name.is_empty() { + match toml_data.get(table_name) { + Some(value) => value, + None => { + eprintln!("Unable to find toml table: \"{}\"", &table_name); + std::process::abort() + } + } + } else { + toml_data + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + + use std::str::FromStr; + + use toml::Value; + + use crate::{get_toml_table, input_file::InputData}; + + #[test] + fn test_input_file() { + let toml_str = r#"username = "db_user" + password = "db_pass" + dbname = "db_name" + host = "localhost" + port = 5432"#; + + let toml_value = Value::from_str(toml_str); + assert!(toml_value.is_ok()); + let toml_value = toml_value.unwrap(); + + let toml_table = InputData::read(&toml_value); + assert!(toml_table.is_ok()); + let toml_table = toml_table.unwrap(); + + let db_url = toml_table.postgres_url(); + assert_eq!("postgres://db_user:db_pass@localhost:5432/db_name", db_url); + } + + #[test] + fn test_given_toml() { + let toml_str_table = r#"[database] + username = "db_user" + password = "db_pass" + dbname = "db_name" + host = "localhost" + port = 5432"#; + + let table_name = "database"; + let toml_value = Value::from_str(toml_str_table).unwrap(); + let table = get_toml_table(table_name, &toml_value); + + assert!(table.is_table()); + + let table_name = ""; + let table = get_toml_table(table_name, &toml_value); + assert!(table.is_table()); + } +}