tern_cli/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
//! The CLI for the [`tern`][tern-docs] migration library.
//!
//! This exports the [`App`] type and [`ContextOptions`], which help turn a
//! project using `tern` into a CLI.
//!
//! The `App` is the CLI. `ContextOptions` exists to connect a generic context
//! to the CLI since it is the CLI that supplies the database URL, surely
//! required of the context, but not anything else the context might need to
//! initialize.
//!
//! [tern-docs]: https://docs.rs/crate/tern/latest
use clap::Parser;
use tern_core::error::TernResult;
use tern_core::future::Future;
use tern_core::migration::MigrationContext;
use tern_core::runner::Runner;
mod cli;
mod commands;
/// A type that can build a particular context with a database url.
/// This is needed because the context is arbitrary, yet the CLI options have
/// the database URL, which is certainly required to build it.
pub trait ContextOptions {
type Ctx: MigrationContext;
/// Establish a connection with this context.
fn connect(&self, db_url: &str) -> impl Future<Output = TernResult<Self::Ctx>>;
}
/// The CLI app to run.
pub struct App<Opts> {
opts: Opts,
cli: cli::Tern,
}
impl<Opts> App<Opts>
where
Opts: ContextOptions,
{
pub fn new(opts: Opts) -> Self {
let cli = cli::Tern::parse();
Self { opts, cli }
}
pub async fn run(&self) -> anyhow::Result<()> {
match &self.cli.commands {
cli::TernCommands::History(history) => match &history.commands {
cli::HistoryCommands::Init { connect_opts } => {
let db_url = connect_opts.required_db_url()?.to_string();
let context = self.opts.connect(&db_url).await?;
let mut runner = Runner::new(context);
runner.init_history().await?;
Ok(())
}
cli::HistoryCommands::Drop { connect_opts } => {
let db_url = connect_opts.required_db_url()?.to_string();
let context = self.opts.connect(&db_url).await?;
let mut runner = Runner::new(context);
runner.drop_history().await?;
Ok(())
}
cli::HistoryCommands::SoftApply {
from_version,
to_version,
connect_opts,
} => {
let db_url = connect_opts.required_db_url()?.to_string();
let context = self.opts.connect(&db_url).await?;
let mut runner = Runner::new(context);
let report = runner.soft_apply(*from_version, *to_version).await?;
log::info!("{report:#?}");
Ok(())
}
},
cli::TernCommands::Migrate(migrate) => match &migrate.commands {
cli::MigrateCommands::ApplyAll {
dryrun,
connect_opts,
} => {
let db_url = connect_opts.required_db_url()?.to_string();
let context = self.opts.connect(&db_url).await?;
let mut runner = Runner::new(context);
let report = if *dryrun {
runner.dryrun().await?
} else {
runner.apply_all().await?
};
log::info!("{report:#?}");
Ok(())
}
cli::MigrateCommands::ListApplied { connect_opts } => {
let db_url = connect_opts.required_db_url()?.to_string();
let context = self.opts.connect(&db_url).await?;
let mut runner = Runner::new(context);
let report = runner.list_applied().await?;
log::info!("{report:#?}");
Ok(())
}
cli::MigrateCommands::New {
description,
no_tx,
migration_type,
source,
} => commands::new(
description.to_string(),
*no_tx,
*migration_type,
source.path.clone(),
),
},
}
}
}