grapevine/src/cli.rs

189 lines
5.4 KiB
Rust

//! Integration with `clap`
//!
//! CLI argument structs are defined in this module. Execution logic for each
//! command goes in a submodule.
use std::{path::PathBuf, str::FromStr};
use clap::{Parser, Subcommand};
use crate::error;
mod migrate_db;
mod serve;
/// Command line arguments
#[derive(Parser)]
#[clap(
about,
version = crate::version(),
)]
pub(crate) struct Args {
#[clap(subcommand)]
pub(crate) command: Command,
}
#[derive(Subcommand)]
pub(crate) enum Command {
/// Run the server.
Serve(ServeArgs),
/// Commands for interacting with the database.
#[clap(subcommand)]
Db(DbCommand),
}
/// Wrapper for the `--config` arg.
///
/// This exists to centralize the `mut_arg` code that sets the help value based
/// on runtime information.
#[derive(clap::Args)]
#[clap(mut_arg("config", |x| {
let help = "Set the path to the configuration file";
x.help(help).long_help(format!(
"{}\n\nIf this option is specified, the provided value is used \
as-is.\n\nIf this option is not specified, then the XDG Base \
Directory Specification is followed, searching for the path `{}` \
in the configuration directories.
",
help,
crate::config::DEFAULT_PATH.display(),
))
}))]
pub(crate) struct ConfigArg {
/// Path to the configuration file
#[clap(long, short)]
pub(crate) config: Option<PathBuf>,
}
#[derive(clap::Args)]
pub(crate) struct ServeArgs {
#[clap(flatten)]
pub(crate) config: ConfigArg,
}
#[derive(Subcommand)]
pub(crate) enum DbCommand {
/// Migrate database from one server implementation to another.
///
/// This command is not protected against symlink-swapping attacks. Do not
/// use it when any subdirectories or parents of the `--in`, `--out`, or
/// `--inplace` directories may be written by an untrusted user during
/// execution.
Migrate(MigrateDbArgs),
}
#[derive(clap::Args)]
pub(crate) struct MigrateDbArgs {
#[clap(flatten)]
config: ConfigArg,
/// Target server implementation to migrate database to.
///
/// If migrating to the current version of grapevine, specify the version
/// as 'grapevine'.
///
/// If migrating to a released version of conduit, specified the version
/// of conduit as `conduit-{version}` (example: `conduit-0.8.0`). If
/// migrating to an unreleased conduit build, instead specify the raw
/// database version as `conduit-db-{version}` (example: `conduit-db-13`).
/// The raw database version can be found by looking at the
/// `latest_database_version` variable in `src/database/mod.rs`.
///
/// The server implementation used for the current database will be
/// detected automatically, and does not need to be specified.
#[clap(long)]
pub(crate) to: DbMigrationTarget,
/// Path to read database from.
#[clap(long = "in", short)]
pub(crate) in_path: PathBuf,
/// Path to write migrated database to.
#[clap(long = "out", short)]
pub(crate) out_path: PathBuf,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum DbMigrationTarget {
/// The latest grapevine db version
///
/// Example:
///
/// ```
/// assert_eq!("grapevine".parse(), Ok(DbMigrationTarget::Grapevine))
/// ```
Grapevine,
/// A conduit-compatible db version.
///
/// This may either be specified as a released version number or directly
/// as a database version. The raw database version must be used when
/// migrating to a conduit deployment built from an unreleased commit
/// on the `next` branch.
Conduit(ConduitDbVersion),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ConduitDbVersion {
/// A conduit release version number
///
/// Example:
///
/// ```
/// assert_eq!(
/// "conduit-0.8.0".parse(),
/// Ok(DbMigrationTarget::Conduit(ConduitDbVersion::Release("0.8.0")))
/// );
/// ```
Release(String),
/// A raw database version
///
/// This corresponds directly to a
/// [`crate::service::globals::DbVersion::Conduit`] version.
///
/// Example:
///
/// ```
/// assert_eq!(
/// "conduit-db-13".parse(),
/// Ok(DbMigrationTarget::Conduit(ConduitDbVersion::Db(13)))
/// );
/// ```
Db(u64),
}
#[derive(thiserror::Error, Debug)]
#[error("invalid db migration target version")]
pub(crate) struct DbMigrationTargetParseError;
impl FromStr for DbMigrationTarget {
type Err = DbMigrationTargetParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s == "grapevine" {
Ok(DbMigrationTarget::Grapevine)
} else if let Some(version) = s.strip_prefix("conduit-db-") {
let version =
version.parse().map_err(|_| DbMigrationTargetParseError)?;
Ok(DbMigrationTarget::Conduit(ConduitDbVersion::Db(version)))
} else if let Some(version) = s.strip_prefix("conduit-") {
Ok(DbMigrationTarget::Conduit(ConduitDbVersion::Release(
version.to_owned(),
)))
} else {
Err(DbMigrationTargetParseError)
}
}
}
impl Args {
pub(crate) async fn run(self) -> Result<(), error::Main> {
match self.command {
Command::Serve(args) => serve::run(args).await?,
Command::Db(DbCommand::Migrate(args)) => {
migrate_db::run(args).await?;
}
}
Ok(())
}
}