mod backend;
mod transports;
mod types;
pub use backend::{ChannelBackend, ChannelReceiver, LocalChannelBackend};
pub use transports::{ChannelLongPoll, ChannelResponse, ChannelSse, ChannelWebSocket};
pub use types::{
ALL_TRANSPORTS, ChannelConf, ChannelCursor, ChannelError, ChannelEvent, ChannelEventId,
ChannelKey, ChannelTransport, POLL, SSE, SlowSubscriberPolicy, UserKey, WS,
};
use axum::extract::ws::WebSocketUpgrade;
use crate::{
Error, Site,
callables::{self, DataValue},
notifiers::CancellationNotifier,
};
#[derive(Clone)]
pub struct Channels {
backend: LocalChannelBackend,
}
#[derive(Clone)]
pub struct UserStream {
channels: Channels,
user_key: UserKey,
channel_key: Option<ChannelKey>,
deliveries: Vec<types::DeliverySpec>,
}
pub(crate) struct OpenStream {
pub(crate) replay: Vec<ChannelEvent>,
pub(crate) receiver: ChannelReceiver,
pub(crate) keepalive: std::time::Duration,
pub(crate) poll_timeout: std::time::Duration,
}
impl Channels {
pub(crate) fn new(backend: LocalChannelBackend) -> Self {
Self { backend }
}
pub fn user(&self, user_key: UserKey) -> UserStream {
UserStream {
channels: self.clone(),
user_key,
channel_key: None,
deliveries: Vec::new(),
}
}
pub async fn close(
&self,
user_key: &UserKey,
channel_key: &ChannelKey,
) -> Result<bool, ChannelError> {
self.backend.close(user_key, channel_key).await
}
pub async fn find(
&self,
user_key: &UserKey,
channel_key: &ChannelKey,
) -> Result<bool, ChannelError> {
self.backend.find(user_key, channel_key).await
}
pub(crate) async fn publish_signal<T>(&self, data: &T) -> Result<(), ChannelError>
where
T: DataValue,
{
self.backend.publish_signal(data).await
}
pub(crate) async fn publish_box(
&self,
payload: &crate::callables::DataBox,
) -> Result<(), ChannelError> {
self.backend.publish_box(payload).await
}
pub(crate) async fn open_stream(
&self,
stream: UserStream,
after: Option<ChannelCursor>,
) -> Result<OpenStream, ChannelError> {
let channel_key = stream.channel_key.unwrap_or_else(ChannelKey::generated);
let open = self
.backend
.open_user(stream.user_key, channel_key, stream.deliveries, after)
.await?;
Ok(OpenStream {
replay: open.replay,
receiver: open.receiver,
keepalive: std::time::Duration::from_millis(self.backend.conf().sse_keepalive_ms),
poll_timeout: std::time::Duration::from_millis(
self.backend.conf().long_poll_timeout_ms,
),
})
}
}
impl UserStream {
pub fn channel(mut self, channel_key: ChannelKey) -> Self {
self.channel_key = Some(channel_key);
self
}
pub fn deliver<T>(mut self) -> Self
where
T: DataValue,
{
self.deliveries.push(delivery::<T, _>(|_| true));
self
}
pub fn deliver_if<T, F>(mut self, predicate: F) -> Self
where
T: DataValue,
F: Fn(&T) -> bool + Send + Sync + 'static,
{
self.deliveries.push(delivery::<T, _>(predicate));
self
}
pub(crate) fn channels(&self) -> Channels {
self.channels.clone()
}
}
impl OpenStream {
pub(crate) fn into_sse(self, shutdown: CancellationNotifier) -> ChannelSse {
ChannelSse::new(self.replay, self.receiver, self.keepalive, shutdown)
}
pub(crate) fn into_websocket(
self,
upgrade: WebSocketUpgrade,
shutdown: CancellationNotifier,
) -> ChannelWebSocket {
ChannelWebSocket::new(upgrade, self.replay, self.receiver, shutdown)
}
pub(crate) async fn into_poll(self, shutdown: CancellationNotifier) -> ChannelLongPoll {
if !self.replay.is_empty() {
return ChannelLongPoll::from_events(self.replay);
}
let events = ChannelLongPoll::wait(self.receiver, self.poll_timeout, shutdown).await;
ChannelLongPoll::from_events(events)
}
}
fn delivery<T, F>(predicate: F) -> types::DeliverySpec
where
T: DataValue,
F: Fn(&T) -> bool + Send + Sync + 'static,
{
let predicate = std::sync::Arc::new(
move |value: &dyn std::any::Any| matches!(value.downcast_ref::<T>(), Some(value) if predicate(value)),
);
types::DeliverySpec {
type_id: std::any::TypeId::of::<T>(),
type_key: short_hash(std::any::type_name::<T>()),
event_type: event_type::<T>(),
predicate,
}
}
fn event_type<T>() -> String
where
T: DataValue,
{
<T as schemars::JsonSchema>::schema_name().into_owned()
}
fn short_hash(value: &str) -> String {
blake3::hash(value.as_bytes())
.to_hex()
.chars()
.take(16)
.collect()
}
impl callables::FromSite for Channels {
fn from_site(site: &Site) -> Result<Self, callables::CallError> {
Ok(site.channels())
}
}
impl axum::extract::FromRequestParts<Site> for Channels {
type Rejection = std::convert::Infallible;
async fn from_request_parts(
_parts: &mut axum::http::request::Parts,
state: &Site,
) -> Result<Self, Self::Rejection> {
Ok(state.channels())
}
}
impl callables::IntoArgPart for Channels {
fn into_arg_part() -> callables::ArgPart {
callables::ArgPart::Ignore
}
}
impl callables::IntoArgPart for WebSocketUpgrade {
fn into_arg_part() -> callables::ArgPart {
callables::ArgPart::Ignore
}
}
impl From<ChannelError> for Error {
fn from(err: ChannelError) -> Self {
match err {
ChannelError::InvalidKey(_)
| ChannelError::InvalidCursor(_)
| ChannelError::TransportNotAllowed => Error::bad_request(err.to_string()),
ChannelError::MessageTooLarge { .. } => Error::bad_request(err.to_string()),
ChannelError::BackendUnavailable => Error::unavailable(err.to_string()),
ChannelError::Serialization(_) | ChannelError::Transport(_) => Error::other(err),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
struct TestNotice {
user_id: i64,
text: String,
}
#[tokio::test]
async fn user_stream_receives_matching_signal() -> Result<(), Box<dyn std::error::Error>> {
let channels = Channels::new(LocalChannelBackend::default());
let stream = channels
.user(UserKey::new("42")?)
.deliver_if::<TestNotice, _>(|notice| notice.user_id == 42);
let mut open = channels.open_stream(stream, None).await?;
channels
.publish_signal(&TestNotice {
user_id: 42,
text: "ready".to_string(),
})
.await?;
let event =
match tokio::time::timeout(std::time::Duration::from_millis(100), open.receiver.recv())
.await?
{
Some(event) => event,
None => return Err("channel closed before event arrived".into()),
};
assert_eq!(event.event_type, "TestNotice");
assert_eq!(event.data["text"], serde_json::json!("ready"));
Ok(())
}
#[tokio::test]
async fn user_stream_filters_rejected_signal() -> Result<(), Box<dyn std::error::Error>> {
let channels = Channels::new(LocalChannelBackend::default());
let stream = channels
.user(UserKey::new("42")?)
.deliver_if::<TestNotice, _>(|notice| notice.user_id == 42);
let mut open = channels.open_stream(stream, None).await?;
channels
.publish_signal(&TestNotice {
user_id: 7,
text: "hidden".to_string(),
})
.await?;
let event =
tokio::time::timeout(std::time::Duration::from_millis(25), open.receiver.recv()).await;
assert!(event.is_err());
Ok(())
}
#[tokio::test]
async fn repeated_user_policy_replaces_old_rules() -> Result<(), Box<dyn std::error::Error>> {
let channels = Channels::new(LocalChannelBackend::default());
let first = channels.user(UserKey::new("42")?).deliver::<TestNotice>();
let _first_open = channels.open_stream(first, None).await?;
let second = channels
.user(UserKey::new("42")?)
.deliver_if::<TestNotice, _>(|notice| notice.user_id == 7);
let mut second_open = channels.open_stream(second, None).await?;
channels
.publish_signal(&TestNotice {
user_id: 42,
text: "old".to_string(),
})
.await?;
let event = tokio::time::timeout(
std::time::Duration::from_millis(25),
second_open.receiver.recv(),
)
.await;
assert!(event.is_err());
Ok(())
}
#[tokio::test]
async fn retained_queue_is_per_user() -> Result<(), Box<dyn std::error::Error>> {
let channels = Channels::new(LocalChannelBackend::default());
let stream = channels.user(UserKey::new("42")?).deliver::<TestNotice>();
let mut first = channels.open_stream(stream, None).await?;
channels
.publish_signal(&TestNotice {
user_id: 42,
text: "one".to_string(),
})
.await?;
let event = match tokio::time::timeout(
std::time::Duration::from_millis(100),
first.receiver.recv(),
)
.await?
{
Some(event) => event,
None => return Err("channel closed before event arrived".into()),
};
let stream = channels.user(UserKey::new("42")?).deliver::<TestNotice>();
let second = channels
.open_stream(stream, Some(ChannelCursor::new(event.id)))
.await?;
assert!(second.replay.is_empty());
Ok(())
}
#[tokio::test]
async fn circular_retention_drops_old_messages() -> Result<(), Box<dyn std::error::Error>> {
let conf = ChannelConf {
retention_events: 2,
..ChannelConf::default()
};
let channels = Channels::new(LocalChannelBackend::new(conf));
let stream = channels.user(UserKey::new("42")?).deliver::<TestNotice>();
let _open = channels.open_stream(stream, None).await?;
for text in ["one", "two", "three"] {
channels
.publish_signal(&TestNotice {
user_id: 42,
text: text.to_string(),
})
.await?;
}
let stream = channels.user(UserKey::new("42")?).deliver::<TestNotice>();
let open = channels.open_stream(stream, None).await?;
assert_eq!(open.replay.len(), 2);
assert_eq!(
open.replay.first().map(|event| &event.data["text"]),
Some(&serde_json::json!("two"))
);
Ok(())
}
}