use std::ffi::OsString;
use std::fmt::Debug;
use std::path::PathBuf;
use bonsaidb_client::fabruic::Certificate;
use bonsaidb_client::AsyncClient;
use bonsaidb_core::async_trait::async_trait;
#[cfg(any(feature = "password-hashing", feature = "token-authentication"))]
use bonsaidb_core::connection::AsyncStorageConnection;
use bonsaidb_server::{Backend, CustomServer, NoBackend, ServerConfiguration};
use clap::{Parser, Subcommand};
use url::Url;
use crate::AnyServerConnection;
#[derive(Subcommand, Debug)]
pub enum Command<Cli: CommandLine> {
#[clap(flatten)]
Server(bonsaidb_server::cli::Command<Cli::Backend>),
#[clap(flatten)]
External(Cli::Subcommand),
}
impl<Cli> Command<Cli>
where
Cli: CommandLine,
{
pub async fn execute(
self,
server_url: Option<Url>,
pinned_certificate: Option<Certificate>,
#[cfg(feature = "password-hashing")] username: Option<String>,
#[cfg(feature = "token-authentication")] token_id: Option<u64>,
mut cli: Cli,
) -> anyhow::Result<()> {
match self {
Command::Server(server) => {
if server_url.is_some() {
anyhow::bail!("server url provided for local-only command.")
}
server.execute_on(cli.open_server().await?).await?;
}
Command::External(command) => {
let connection = if let Some(server_url) = server_url {
let mut client = AsyncClient::build(server_url);
if let Some(certificate) = pinned_certificate {
client = client.with_certificate(certificate);
}
AnyServerConnection::Networked(client.build()?)
} else {
AnyServerConnection::Local(cli.open_server().await?)
};
#[cfg(feature = "password-hashing")]
let connection = if let Some(username) = username {
let password = bonsaidb_local::cli::read_password_from_stdin(false)?;
connection
.authenticate_with_password(&username, password)
.await?
} else {
connection
};
#[cfg(feature = "token-authentication")]
let connection = if let Some(token_id) = token_id {
let token = bonsaidb_core::connection::SensitiveString(std::env::var(
"BONSAIDB_TOKEN_SECRET",
)?);
connection.authenticate_with_token(token_id, &token).await?
} else {
connection
};
cli.execute(command, connection).await?;
}
}
Ok(())
}
}
#[derive(Parser, Debug)]
pub struct Args<Cli: CommandLine> {
#[clap(long)]
pub url: Option<Url>,
#[clap(short = 'c', long)]
pub pinned_certificate: Option<PathBuf>,
#[cfg(feature = "token-authentication")]
#[clap(long = "token", short = 't')]
pub token_id: Option<u64>,
#[cfg(feature = "password-hashing")]
#[clap(long = "username", short = 'u')]
pub username: Option<String>,
#[clap(subcommand)]
pub command: Command<Cli>,
}
impl<Cli: CommandLine> Args<Cli> {
pub async fn execute(self, cli: Cli) -> anyhow::Result<()> {
let pinned_certificate = if let Some(cert_path) = self.pinned_certificate {
let bytes = tokio::fs::read(cert_path).await?;
Some(Certificate::from_der(bytes)?)
} else {
None
};
self.command
.execute(
self.url,
pinned_certificate,
#[cfg(feature = "password-hashing")]
self.username,
#[cfg(feature = "token-authentication")]
self.token_id,
cli,
)
.await
}
}
#[async_trait]
pub trait CommandLine: Sized + Send + Sync {
type Backend: Backend;
type Subcommand: Subcommand + Send + Sync + Debug;
async fn run(self) -> anyhow::Result<()> {
Args::<Self>::parse().execute(self).await
}
async fn run_from<I, T>(self, itr: I) -> anyhow::Result<()>
where
I: IntoIterator<Item = T> + Send,
T: Into<OsString> + Clone + Send,
{
Args::<Self>::parse_from(itr).execute(self).await
}
async fn open_server(&mut self) -> anyhow::Result<CustomServer<Self::Backend>> {
Ok(CustomServer::<Self::Backend>::open(self.configuration().await?).await?)
}
async fn configuration(&mut self) -> anyhow::Result<ServerConfiguration<Self::Backend>>;
async fn execute(
&mut self,
command: Self::Subcommand,
connection: AnyServerConnection<Self::Backend>,
) -> anyhow::Result<()>;
}
#[async_trait]
impl CommandLine for NoBackend {
type Backend = Self;
type Subcommand = NoSubcommand;
async fn configuration(&mut self) -> anyhow::Result<ServerConfiguration> {
Ok(ServerConfiguration::default())
}
async fn execute(
&mut self,
command: Self::Subcommand,
_connection: AnyServerConnection<Self>,
) -> anyhow::Result<()> {
match command {}
}
}
pub async fn run<B: Backend>(configuration: ServerConfiguration<B>) -> anyhow::Result<()> {
Args::parse()
.execute(NoCommandLine::<B> {
configuration: Some(configuration),
})
.await
}
#[derive(Debug)]
struct NoCommandLine<B: Backend> {
configuration: Option<ServerConfiguration<B>>,
}
#[async_trait]
impl<B: Backend> CommandLine for NoCommandLine<B> {
type Backend = B;
type Subcommand = NoSubcommand;
async fn configuration(&mut self) -> anyhow::Result<ServerConfiguration<B>> {
self.configuration
.take()
.ok_or_else(|| anyhow::anyhow!("configuration already consumed"))
}
async fn execute(
&mut self,
command: Self::Subcommand,
_connection: AnyServerConnection<B>,
) -> anyhow::Result<()> {
match command {}
}
}
#[derive(clap::Subcommand, Debug)]
pub enum NoSubcommand {}