use std::future::{Future, ready};
#[cfg(feature = "asyncapi")]
use ruststream::asyncapi::{Binding, Bindings};
use ruststream::runtime::IntoSource;
use ruststream::{AddressedCopies, RedeliveryAddress, RedeliveryAddressed, SubscriptionSource};
#[cfg(feature = "asyncapi")]
use serde::Serialize;
use crate::error::SeaFileError;
use crate::file::ConnectedFileBroker;
use crate::subscriber::FileSubscriber;
#[derive(Debug, Clone, PartialEq, Eq)]
#[must_use]
pub struct FileStream {
stream: String,
replay: bool,
}
impl FileStream {
pub fn new(stream: impl Into<String>) -> Self {
Self {
stream: stream.into(),
replay: false,
}
}
pub fn replay(mut self) -> Self {
self.replay = true;
self
}
#[must_use]
pub fn stream(&self) -> &str {
&self.stream
}
pub(crate) fn replay_value(&self) -> bool {
self.replay
}
fn redelivery_address_value(&self) -> RedeliveryAddress {
RedeliveryAddress::new(self.stream.clone())
}
#[cfg(feature = "asyncapi")]
fn channel_extension(&self) -> Bindings {
channel_extension(self.stream())
}
pub(crate) fn validate(&self) -> Result<(), SeaFileError> {
if self.stream.is_empty() {
return Err(SeaFileError::Invalid("stream key must be non-empty".into()));
}
Ok(())
}
}
#[cfg(feature = "asyncapi")]
const FILE_EXTENSION: &str = "x-ruststream-file";
#[cfg(feature = "asyncapi")]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct FileChannel<'a> {
stream_key: &'a str,
}
#[cfg(feature = "asyncapi")]
pub(crate) fn channel_extension(stream_key: &str) -> Bindings {
let body = FileChannel { stream_key };
Binding::extension(FILE_EXTENSION, &body)
.map(|binding| Bindings::new().with(binding))
.unwrap_or_default()
}
impl IntoSource for FileStream {
type Source = Self;
fn into_source(self) -> Self {
self
}
}
impl SubscriptionSource<ConnectedFileBroker> for FileStream {
type Subscriber = FileSubscriber;
type Copies = AddressedCopies;
fn name(&self) -> &str {
self.stream()
}
async fn subscribe(
self,
connected: &ConnectedFileBroker,
) -> Result<FileSubscriber, SeaFileError> {
connected.subscribe_stream(self).await
}
#[cfg(feature = "asyncapi")]
fn channel_bindings(&self) -> Bindings {
self.channel_extension()
}
}
impl RedeliveryAddressed<ConnectedFileBroker> for FileStream {
fn redelivery_address(
&self,
_connected: &ConnectedFileBroker,
) -> impl Future<Output = Result<RedeliveryAddress, SeaFileError>> {
ready(Ok(self.redelivery_address_value()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_stream_keys_are_rejected_before_io() {
assert!(FileStream::new("").validate().is_err());
}
#[test]
fn replay_reads_the_retained_file() {
assert!(FileStream::new("orders").replay().replay_value());
assert!(!FileStream::new("orders").replay_value());
}
}