use std::future::{Future, ready};
use std::time::Duration;
#[cfg(feature = "asyncapi")]
use ruststream::asyncapi::{Binding, Bindings};
use ruststream::runtime::{Declared, IntoSource, SubscriberBuilder, SubscriberSettings};
use ruststream::{AddressedCopies, RedeliveryAddress, RedeliveryAddressed, SubscriptionSource};
#[cfg(feature = "asyncapi")]
use serde::Serialize;
use crate::broker::ConnectedKinesisBroker;
use crate::error::KinesisError;
use crate::subscriber::KinesisSubscriber;
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct KinesisStream {
stream: String,
poll_interval: Duration,
create_shards: Option<i32>,
}
impl KinesisStream {
pub fn new(stream: impl Into<String>) -> Self {
Self {
stream: stream.into(),
poll_interval: Duration::from_secs(1),
create_shards: None,
}
}
pub fn poll_interval(mut self, interval: Duration) -> Self {
self.poll_interval = interval;
self
}
pub fn create_if_missing(mut self, shards: i32) -> Self {
self.create_shards = Some(shards);
self
}
#[must_use]
pub fn stream(&self) -> &str {
&self.stream
}
pub(crate) fn poll_value(&self) -> Duration {
self.poll_interval
}
pub(crate) fn create_value(&self) -> Option<i32> {
self.create_shards
}
pub(crate) fn validate(&self) -> Result<(), KinesisError> {
if self.stream.is_empty() {
return Err(KinesisError::Invalid("stream must be non-empty".into()));
}
if let Some(shards) = self.create_shards
&& shards < 1
{
return Err(KinesisError::Invalid(
"create_if_missing needs at least one shard".into(),
));
}
Ok(())
}
}
pub trait KinesisSubscriberExt {
#[must_use]
fn poll_interval(self, interval: Duration) -> Self;
#[must_use]
fn create_if_missing(self, shards: i32) -> Self;
}
impl<Def, State, DefCodec> KinesisSubscriberExt
for SubscriberBuilder<Def, KinesisStream, State, DefCodec>
where
Def: Declared,
{
fn poll_interval(self, interval: Duration) -> Self {
self.map_source(|source| source.poll_interval(interval))
}
fn create_if_missing(self, shards: i32) -> Self {
self.map_source(|source| source.create_if_missing(shards))
}
}
impl IntoSource for KinesisStream {
type Source = Self;
fn into_source(self) -> Self {
self
}
}
#[cfg(feature = "asyncapi")]
#[derive(Debug, Serialize)]
struct KinesisChannelBinding<'a> {
stream: &'a str,
#[serde(rename = "pollIntervalMs")]
poll_interval_ms: u64,
#[serde(rename = "shardCount", skip_serializing_if = "Option::is_none")]
shard_count: Option<i32>,
}
#[cfg(feature = "asyncapi")]
pub(crate) const BINDING_KEY: &str = "x-ruststream-kinesis";
impl KinesisStream {
#[cfg(feature = "asyncapi")]
fn binding(&self) -> Bindings {
let body = KinesisChannelBinding {
stream: self.stream(),
poll_interval_ms: u64::try_from(self.poll_interval.as_millis()).unwrap_or(u64::MAX),
shard_count: self.create_shards,
};
Binding::extension(BINDING_KEY, &body)
.map_or_else(|_| Bindings::new(), |binding| Bindings::new().with(binding))
}
}
impl SubscriptionSource<ConnectedKinesisBroker> for KinesisStream {
type Subscriber = KinesisSubscriber;
type Copies = AddressedCopies;
fn name(&self) -> &str {
self.stream()
}
async fn subscribe(
self,
connected: &ConnectedKinesisBroker,
) -> Result<KinesisSubscriber, KinesisError> {
connected.subscribe_stream(self).await
}
#[cfg(feature = "asyncapi")]
fn channel_bindings(&self) -> Bindings {
self.binding()
}
}
impl RedeliveryAddressed<ConnectedKinesisBroker> for KinesisStream {
fn redelivery_address(
&self,
_connected: &ConnectedKinesisBroker,
) -> impl Future<Output = Result<RedeliveryAddress, KinesisError>> + Send {
ready(Ok(RedeliveryAddress::new(self.stream().to_owned())))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn invalid_descriptors_are_rejected_before_io() {
assert!(KinesisStream::new("").validate().is_err());
assert!(
KinesisStream::new("s")
.create_if_missing(0)
.validate()
.is_err()
);
}
}