use std::{future::IntoFuture, marker::PhantomData};
use futures::{future::BoxFuture, FutureExt};
use crate::{
bigtable,
builder::ClientBuilder,
emulator::{self, EmulatorData, CLIENT_CONNECT_RETRY_DEFAULT},
};
type BoxError = Box<dyn std::error::Error + Send + Sync + 'static>;
mod builder_state {
pub trait State {}
pub enum NotReady {}
impl State for NotReady {}
pub enum Ready {}
impl State for Ready {}
}
pub struct Emulator<ProjectState: builder_state::State, InstanceState: builder_state::State> {
project: Option<String>,
_project_state: PhantomData<ProjectState>,
instance: Option<String>,
_instance_state: PhantomData<InstanceState>,
connection_retry_limit: usize,
}
impl Emulator<builder_state::NotReady, builder_state::NotReady> {
pub fn new() -> Self {
Self {
project: None,
_project_state: PhantomData,
instance: None,
_instance_state: PhantomData,
connection_retry_limit: CLIENT_CONNECT_RETRY_DEFAULT,
}
}
}
impl<IS: builder_state::State> Emulator<builder_state::NotReady, IS> {
pub fn project(self, project: impl Into<String>) -> Emulator<builder_state::Ready, IS> {
Emulator {
project: Some(project.into()),
_project_state: PhantomData,
instance: self.instance,
_instance_state: PhantomData,
connection_retry_limit: self.connection_retry_limit,
}
}
}
impl<PS: builder_state::State> Emulator<PS, builder_state::NotReady> {
pub fn instance(self, instance: impl Into<String>) -> Emulator<PS, builder_state::Ready> {
Emulator {
project: self.project,
_project_state: PhantomData,
instance: Some(instance.into()),
_instance_state: PhantomData,
connection_retry_limit: self.connection_retry_limit,
}
}
}
impl<PS: builder_state::State, IS: builder_state::State> Emulator<PS, IS> {
pub fn connection_retry_limit(mut self, connection_retry_limit: usize) -> Self {
self.connection_retry_limit = connection_retry_limit;
self
}
}
impl IntoFuture for Emulator<builder_state::Ready, builder_state::Ready> {
type Output = Result<EmulatorClient, BoxError>;
type IntoFuture = BoxFuture<'static, Self::Output>;
fn into_future(self) -> Self::IntoFuture {
async move {
EmulatorClient::new(
self.project.unwrap(),
self.instance.unwrap(),
self.connection_retry_limit,
)
.await
}
.boxed()
}
}
pub struct EmulatorClient {
inner: crate::emulator::EmulatorClient,
instance: String,
}
const DATA: EmulatorData = EmulatorData {
gcloud_param: "bigtable",
kill_pattern: "bigtable",
availability_check: create_bigtable_client,
extra_args: Vec::new(),
};
impl EmulatorClient {
async fn new(
project_name: impl Into<String>,
instance_name: impl Into<String>,
connect_retry_limit: usize,
) -> Result<Self, BoxError> {
Ok(EmulatorClient {
inner: emulator::EmulatorClient::new(DATA, project_name, connect_retry_limit).await?,
instance: instance_name.into(),
})
}
pub fn endpoint(&self) -> String {
self.inner.endpoint()
}
pub fn project(&self) -> &str {
self.inner.project()
}
pub fn instance(&self) -> &str {
&self.instance
}
pub fn builder(&self) -> &ClientBuilder {
self.inner.builder()
}
pub async fn create_table(
&self,
table_name: &str,
column_families: impl IntoIterator<Item = impl Into<String>>,
) -> Result<(), BoxError> {
let config = bigtable::admin::BigtableTableAdminConfig {
endpoint: self.endpoint(),
..bigtable::admin::BigtableTableAdminConfig::default()
};
let mut admin = self
.builder()
.build_bigtable_admin_client(config, self.project(), &self.instance)
.await?;
let column_families = column_families
.into_iter()
.map(|name| (name.into(), bigtable::admin::Rule::MaxNumVersions(i32::MAX)));
admin.create_table(table_name, column_families).await?;
Ok(())
}
}
fn create_bigtable_client(port: &str) -> BoxFuture<Result<(), tonic::transport::Error>> {
async move {
bigtable::api::bigtable::v2::bigtable_client::BigtableClient::connect(format!(
"http://{}:{}",
crate::emulator::HOST,
port
))
.await?;
Ok(())
}
.boxed()
}