use std::{future::IntoFuture, marker::PhantomData};
use futures::{future::BoxFuture, FutureExt};
use crate::{
builder::ClientBuilder,
emulator::{self, EmulatorData, CLIENT_CONNECT_RETRY_DEFAULT},
pubsub,
};
use tracing::debug;
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<S: builder_state::State> {
project: Option<String>,
connection_retry_limit: usize,
_state: PhantomData<S>,
}
impl Emulator<builder_state::NotReady> {
pub fn new() -> Self {
Self {
project: None,
connection_retry_limit: CLIENT_CONNECT_RETRY_DEFAULT,
_state: PhantomData,
}
}
pub fn project(self, project: impl Into<String>) -> Emulator<builder_state::Ready> {
Emulator {
project: Some(project.into()),
connection_retry_limit: CLIENT_CONNECT_RETRY_DEFAULT,
_state: PhantomData,
}
}
}
impl<S: builder_state::State> Emulator<S> {
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> {
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.connection_retry_limit).await }
.boxed()
}
}
pub struct EmulatorClient {
inner: crate::emulator::EmulatorClient,
_temp: tempdir::TempDir,
}
fn data(tmp_dir: &tempdir::TempDir) -> EmulatorData {
EmulatorData {
gcloud_param: "pubsub",
kill_pattern: "pubsub",
availability_check: create_schema_client,
extra_args: vec!["--data-dir".into(), tmp_dir.path().into()],
}
}
impl EmulatorClient {
async fn new(
project_name: impl Into<String>,
connect_retry_limit: usize,
) -> Result<Self, BoxError> {
let temp = tempdir::TempDir::new("pubsub_emulator")?;
Ok(EmulatorClient {
inner: emulator::EmulatorClient::new(data(&temp), project_name, connect_retry_limit)
.await?,
_temp: temp,
})
}
pub fn endpoint(&self) -> String {
self.inner.endpoint()
}
pub fn project(&self) -> &str {
self.inner.project()
}
pub fn builder(&self) -> &ClientBuilder {
self.inner.builder()
}
pub async fn create_topic(&self, topic_name: impl AsRef<str>) -> Result<(), BoxError> {
let config = pubsub::PubSubConfig {
endpoint: self.endpoint(),
..pubsub::PubSubConfig::default()
};
let mut publisher = self.builder().build_pubsub_publisher(config).await?;
publisher
.raw_api_mut()
.create_topic(pubsub::api::Topic {
name: pubsub::ProjectTopicName::new(self.project(), topic_name.as_ref()).into(),
..pubsub::api::Topic::default()
})
.await?;
debug!(topic = topic_name.as_ref(), "Emulator created topic");
Ok(())
}
}
fn create_schema_client(port: &str) -> BoxFuture<Result<(), tonic::transport::Error>> {
async move {
pubsub::api::schema_service_client::SchemaServiceClient::connect(format!(
"http://{}:{}",
crate::emulator::HOST,
port
))
.await?;
Ok(())
}
.boxed()
}