use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::Duration;
use aws_config::{BehaviorVersion, Region, SdkConfig};
use aws_sdk_kinesis::client::Waiters;
use ruststream::{Broker, ConnectedBroker, DefaultPublish, DescribeServer, ServerSpec, Subscribe};
use tokio::sync::OnceCell;
use crate::error::{KinesisError, sdk_err};
use crate::lease::{LeaseStore, MemoryLeaseStore};
use crate::publisher::{KinesisPublish, KinesisPublisher};
use crate::stream::KinesisStream;
use crate::subscriber::KinesisSubscriber;
pub(crate) struct Core {
pub(crate) client: aws_sdk_kinesis::Client,
pub(crate) store: Arc<dyn LeaseStore>,
pub(crate) owner: String,
pub(crate) closed: AtomicBool,
}
impl Core {
pub(crate) fn ensure_open(&self) -> Result<(), KinesisError> {
if self.closed.load(Ordering::Acquire) {
return Err(KinesisError::NotConnected);
}
Ok(())
}
}
impl std::fmt::Debug for Core {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Core")
.field("owner", &self.owner)
.field("closed", &self.closed.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
pub(crate) type CoreCell = Arc<OnceCell<Arc<Core>>>;
#[derive(Clone, Default)]
#[must_use]
pub struct KinesisBroker {
endpoint: Option<String>,
region: Option<String>,
test_credentials: bool,
sdk_config: Option<SdkConfig>,
store: Option<Arc<dyn LeaseStore>>,
owner: Option<String>,
cell: CoreCell,
}
impl std::fmt::Debug for KinesisBroker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KinesisBroker")
.field("endpoint", &self.endpoint)
.finish_non_exhaustive()
}
}
impl KinesisBroker {
pub fn new() -> Self {
Self::default()
}
pub fn from_config(config: SdkConfig) -> Self {
Self {
sdk_config: Some(config),
..Self::default()
}
}
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = Some(endpoint.into());
self
}
pub fn region(mut self, region: impl Into<String>) -> Self {
self.region = Some(region.into());
self
}
pub fn test_credentials(mut self) -> Self {
self.test_credentials = true;
self
}
pub fn lease_store(mut self, store: Arc<dyn LeaseStore>) -> Self {
self.store = Some(store);
self
}
pub fn owner_id(mut self, owner: impl Into<String>) -> Self {
self.owner = Some(owner.into());
self
}
#[must_use]
pub fn publisher(&self) -> KinesisPublisher {
KinesisPublisher::new(Arc::clone(&self.cell))
}
}
fn default_owner() -> String {
static SEQ: AtomicU64 = AtomicU64::new(0);
format!(
"rs-{}-{}",
std::process::id(),
SEQ.fetch_add(1, Ordering::Relaxed)
)
}
impl Broker for KinesisBroker {
type Error = KinesisError;
type Connected = ConnectedKinesisBroker;
async fn connect(self) -> Result<Self::Connected, Self::Error> {
let core = self
.cell
.get_or_try_init(async || {
let config = if let Some(config) = self.sdk_config.clone() {
config
} else {
let mut loader = aws_config::defaults(BehaviorVersion::latest());
if let Some(endpoint) = &self.endpoint {
loader = loader.endpoint_url(endpoint.clone());
}
if let Some(region) = &self.region {
loader = loader.region(Region::new(region.clone()));
}
if self.test_credentials {
loader = loader.test_credentials();
}
loader.load().await
};
Ok::<_, KinesisError>(Arc::new(Core {
client: aws_sdk_kinesis::Client::new(&config),
store: self
.store
.clone()
.unwrap_or_else(|| Arc::new(MemoryLeaseStore::new())),
owner: self.owner.clone().unwrap_or_else(default_owner),
closed: AtomicBool::new(false),
}))
})
.await?
.clone();
Ok(ConnectedKinesisBroker {
core,
cell: self.cell,
})
}
}
impl DescribeServer for KinesisBroker {
fn describe_server(&self) -> ServerSpec {
let host = self
.endpoint
.clone()
.unwrap_or_else(|| "kinesis.amazonaws.com".to_owned());
ServerSpec::new(host, "kinesis")
}
}
#[derive(Debug)]
pub struct ConnectedKinesisBroker {
pub(crate) core: Arc<Core>,
cell: CoreCell,
}
impl ConnectedKinesisBroker {
#[must_use]
pub fn publisher(&self) -> KinesisPublisher {
KinesisPublisher::new(Arc::clone(&self.cell))
}
pub async fn subscribe_stream(
&self,
descriptor: KinesisStream,
) -> Result<KinesisSubscriber, KinesisError> {
descriptor.validate()?;
self.core.ensure_open()?;
if let Some(shards) = descriptor.create_value() {
self.ensure_stream(descriptor.stream(), shards).await?;
}
Ok(KinesisSubscriber::open(&self.core, descriptor))
}
async fn ensure_stream(&self, stream: &str, shards: i32) -> Result<(), KinesisError> {
let exists = self
.core
.client
.describe_stream_summary()
.stream_name(stream)
.send()
.await
.is_ok();
if !exists {
let created = self
.core
.client
.create_stream()
.stream_name(stream)
.shard_count(shards)
.send()
.await;
if let Err(err) = created {
let raced = err
.as_service_error()
.is_some_and(|e| e.to_string().contains("ResourceInUse"));
if !raced {
return Err(KinesisError::Stream {
stream: stream.to_owned(),
source: sdk_err(&err),
});
}
}
}
self.core
.client
.wait_until_stream_exists()
.stream_name(stream)
.wait(Duration::from_mins(1))
.await
.map_err(|e| KinesisError::Stream {
stream: stream.to_owned(),
source: Box::new(e),
})?;
Ok(())
}
}
impl ConnectedBroker for ConnectedKinesisBroker {
type Error = KinesisError;
type Closed = ();
async fn shutdown(self) -> Result<(), Self::Error> {
self.core.closed.store(true, Ordering::Release);
Ok(())
}
}
impl Subscribe for ConnectedKinesisBroker {
type Subscriber = KinesisSubscriber;
async fn subscribe(&self, name: &str) -> Result<Self::Subscriber, Self::Error> {
self.subscribe_stream(KinesisStream::new(name)).await
}
}
impl DefaultPublish for ConnectedKinesisBroker {
type Policy = KinesisPublish;
}