use std::fmt;
use std::pin::Pin;
use std::sync::{Arc, Mutex, PoisonError, Weak};
use serde::Serialize;
use serde_json::{Map, Value};
use tokio::sync::broadcast::error::RecvError;
use tokio::sync::{broadcast, mpsc, oneshot, watch};
use crate::client;
use crate::error::Error;
use crate::identifier::Identifier;
use crate::message::Message;
use crate::protocol::Command;
const EVENT_BUFFER: usize = 16;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Event {
Connected {
reconnected: bool,
},
Disconnected {
will_reconnect: bool,
},
Rejected,
}
#[derive(Clone)]
pub struct Subscription {
inner: Arc<Inner>,
}
struct Inner {
client: Weak<client::Inner>,
registration: Arc<Registration>,
messages: tokio::sync::Mutex<mpsc::Receiver<Message>>,
events: tokio::sync::Mutex<broadcast::Receiver<Event>>,
callbacks: Option<Arc<watch::Sender<bool>>>,
}
impl Subscription {
pub(crate) fn new(
client: Weak<client::Inner>,
registration: Arc<Registration>,
receivers: Receivers,
callbacks: Option<Arc<watch::Sender<bool>>>,
) -> Subscription {
Subscription {
inner: Arc::new(Inner {
client,
registration,
messages: tokio::sync::Mutex::new(receivers.messages),
events: tokio::sync::Mutex::new(receivers.events),
callbacks,
}),
}
}
pub fn key(&self) -> &str {
&self.inner.registration.key
}
pub async fn next(&self) -> Option<Message> {
let message = self.inner.messages.lock().await.recv().await;
if message.is_none()
&& let Some(callbacks) = &self.inner.callbacks
{
let mut finished = callbacks.subscribe();
let _last = finished.wait_for(|finished| *finished).await;
}
message
}
pub fn err(&self) -> Option<Error> {
self.inner.registration.reason()
}
pub async fn next_event(&self) -> Option<Event> {
let mut events = self.inner.events.lock().await;
loop {
match events.recv().await {
Ok(event) => break Some(event),
Err(RecvError::Lagged(skipped)) => {
tracing::warn!(key = %self.key(), skipped, "connection events went unread and were dropped");
}
Err(RecvError::Closed) => break None,
}
}
}
pub async fn perform(&self, action: &str, data: impl Serialize) -> Result<(), Error> {
let payload = perform_payload(action, data)?;
self.client()?
.send(Command::message(self.key(), payload))
.await
}
pub async fn send(&self, data: impl Serialize) -> Result<(), Error> {
let payload = serde_json::to_string(&data)?;
self.client()?
.send(Command::message(self.key(), payload))
.await
}
pub async fn unsubscribe(&self) -> Result<(), Error> {
let client = self.client()?;
if client.forget(&self.inner.registration, &Error::Unsubscribed) {
client.send(Command::unsubscribe(self.key())).await
} else {
Ok(())
}
}
fn client(&self) -> Result<Arc<client::Inner>, Error> {
self.inner.client.upgrade().ok_or(Error::Closed)
}
}
impl fmt::Debug for Subscription {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Subscription")
.field("key", &self.key())
.finish()
}
}
impl Drop for Inner {
fn drop(&mut self) {
if let Some(client) = self.client.upgrade()
&& client.forget(&self.registration, &Error::Unsubscribed)
{
client.unsubscribe_later(&self.registration.key);
}
}
}
fn perform_payload(action: &str, data: impl Serialize) -> Result<String, Error> {
let mut fields = match serde_json::to_value(data)? {
Value::Object(fields) => Ok(fields),
Value::Null => Ok(Map::new()),
_ => Err(Error::DataNotAnObject {
action: action.to_string(),
}),
}?;
fields.insert("action".to_string(), Value::String(action.to_string()));
Ok(serde_json::to_string(&fields)?)
}
pub struct SubscribeRequest {
identifier: Identifier,
callbacks: Callbacks,
}
impl SubscribeRequest {
pub fn new(identifier: Identifier) -> SubscribeRequest {
SubscribeRequest {
identifier,
callbacks: Callbacks::default(),
}
}
pub fn on_connected<Fut>(
mut self,
callback: impl Fn(bool) -> Fut + Send + Sync + 'static,
) -> SubscribeRequest
where
Fut: Future<Output = ()> + Send + 'static,
{
self.callbacks.connected =
Some(Box::new(move |reconnected| Box::pin(callback(reconnected))));
self
}
pub fn on_disconnected<Fut>(
mut self,
callback: impl Fn(bool) -> Fut + Send + Sync + 'static,
) -> SubscribeRequest
where
Fut: Future<Output = ()> + Send + 'static,
{
self.callbacks.disconnected = Some(Box::new(move |will_reconnect| {
Box::pin(callback(will_reconnect))
}));
self
}
pub fn on_rejected<Fut>(
mut self,
callback: impl Fn() -> Fut + Send + Sync + 'static,
) -> SubscribeRequest
where
Fut: Future<Output = ()> + Send + 'static,
{
self.callbacks.rejected = Some(Box::new(move |()| Box::pin(callback())));
self
}
pub(crate) fn into_parts(self) -> (Identifier, Callbacks) {
(self.identifier, self.callbacks)
}
}
impl From<Identifier> for SubscribeRequest {
fn from(identifier: Identifier) -> SubscribeRequest {
SubscribeRequest::new(identifier)
}
}
type Callback<Arg> = Box<dyn Fn(Arg) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
#[derive(Default)]
pub(crate) struct Callbacks {
connected: Option<Callback<bool>>,
disconnected: Option<Callback<bool>>,
rejected: Option<Callback<()>>,
}
impl Callbacks {
fn is_empty(&self) -> bool {
self.connected.is_none() && self.disconnected.is_none() && self.rejected.is_none()
}
pub(crate) fn spawn(
self,
events: broadcast::Receiver<Event>,
) -> Option<Arc<watch::Sender<bool>>> {
if self.is_empty() {
return None;
}
let finished = Arc::new(watch::Sender::new(false));
let last = Arc::clone(&finished);
tokio::spawn(async move {
self.run(events).await;
last.send_replace(true);
});
Some(finished)
}
async fn run(self, mut events: broadcast::Receiver<Event>) {
loop {
match events.recv().await {
Ok(Event::Connected { reconnected }) => {
if let Some(callback) = &self.connected {
callback(reconnected).await;
}
}
Ok(Event::Disconnected { will_reconnect }) => {
if let Some(callback) = &self.disconnected {
callback(will_reconnect).await;
}
}
Ok(Event::Rejected) => {
if let Some(callback) = &self.rejected {
callback(()).await;
}
}
Err(RecvError::Lagged(skipped)) => {
tracing::warn!(
skipped,
"a callback fell behind and missed connection events"
);
}
Err(RecvError::Closed) => break,
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Decision {
Confirmed,
Rejected,
}
pub(crate) struct Registration {
pub(crate) key: String,
decision: Mutex<Option<oneshot::Sender<Decision>>>,
messages: Mutex<Option<mpsc::Sender<Message>>>,
events: Mutex<Option<broadcast::Sender<Event>>>,
reason: Mutex<Option<Error>>,
}
pub(crate) struct Receivers {
pub(crate) decision: oneshot::Receiver<Decision>,
pub(crate) messages: mpsc::Receiver<Message>,
pub(crate) events: broadcast::Receiver<Event>,
}
impl Registration {
pub(crate) fn register(key: String, buffer: usize) -> (Arc<Registration>, Receivers) {
let (decide, decision) = oneshot::channel();
let (deliver, messages) = mpsc::channel(buffer.max(1));
let (announce, events) = broadcast::channel(EVENT_BUFFER);
let registration = Arc::new(Registration {
key,
decision: Mutex::new(Some(decide)),
messages: Mutex::new(Some(deliver)),
events: Mutex::new(Some(announce)),
reason: Mutex::new(None),
});
(
registration,
Receivers {
decision,
messages,
events,
},
)
}
pub(crate) fn watch_events(&self) -> broadcast::Receiver<Event> {
match self
.events
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
{
Some(announce) => announce.subscribe(),
None => broadcast::channel(1).1,
}
}
pub(crate) fn confirm(&self, reconnected: bool) {
self.announce(Event::Connected { reconnected });
self.decide(Decision::Confirmed);
}
pub(crate) fn reject(&self) {
self.announce(Event::Rejected);
self.decide(Decision::Rejected);
self.close(&Error::Rejected {
key: self.key.clone(),
});
}
pub(crate) fn disconnect(&self, will_reconnect: bool) {
self.announce(Event::Disconnected { will_reconnect });
}
pub(crate) fn deliver(&self, message: Message) -> bool {
match self
.messages
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
{
Some(deliver) => deliver.try_send(message).is_ok(),
None => true,
}
}
pub(crate) fn close(&self, reason: &Error) {
self.reason
.lock()
.unwrap_or_else(PoisonError::into_inner)
.get_or_insert_with(|| reason.clone());
self.messages
.lock()
.unwrap_or_else(PoisonError::into_inner)
.take();
self.events
.lock()
.unwrap_or_else(PoisonError::into_inner)
.take();
}
fn reason(&self) -> Option<Error> {
self.reason
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
fn decide(&self, decision: Decision) {
if let Some(decide) = self
.decision
.lock()
.unwrap_or_else(PoisonError::into_inner)
.take()
&& decide.send(decision).is_err()
{
tracing::trace!(key = %self.key, "nobody is waiting on the subscribe");
}
}
fn announce(&self, event: Event) {
if let Some(announce) = self
.events
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
&& announce.send(event).is_err()
{
tracing::trace!(key = %self.key, "nobody is listening for events");
}
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "a test that can't have what it asked for has nothing left to assert"
)]
mod tests {
use std::collections::BTreeMap;
use serde_json::json;
use super::*;
#[test]
fn perform_payload_carries_the_action_alongside_the_data() {
assert_eq!(
r#"{"action":"speak","body":"Hello!"}"#,
perform_payload("speak", json!({ "body": "Hello!" })).unwrap()
);
assert_eq!(
r#"{"action":"speak"}"#,
perform_payload("speak", ()).unwrap()
);
assert!(matches!(
perform_payload("speak", vec!["nope"]),
Err(Error::DataNotAnObject { action }) if action == "speak"
));
assert!(matches!(
perform_payload("speak", BTreeMap::from([(vec![1u8], 1u8)])),
Err(Error::Json(_))
));
}
}