dynamic subscription
SeanKim opened this issue · comments
SeanKim commented
Hi, I want to implement dynamic subscribe functionality.
coinbase-pro API support subscription to new items in the middle of data reception.
I think it can be done without breaking backward compatibility by simply changes WSFeed's return type
from
impl Stream<Item = Result<Message, CBError>>
to
impl Stream<Item = Result<Message, CBError>> + Sink<TMessage, Error = TError> + Unpin
below is more detailed PoC code for this changes
use async_trait::async_trait;
use futures::{future, Sink, Stream};
use futures_util::{future::TryFutureExt, sink::SinkExt, stream::TryStreamExt};
use serde_json;
use tokio_tungstenite::{
connect_async, tungstenite::Error as TError, tungstenite::Message as TMessage,
};
fn convert_msg(msg: TMessage) -> Message {
match msg {
TMessage::Text(str) => serde_json::from_str(&str).unwrap_or_else(|e| {
Message::InternalError(CBError::Serde {
error: e,
data: str,
})
}),
_ => unreachable!(), // filtered in stream
}
}
use crate::{structs::wsfeed::Subscribe, CBError};
use crate::{structs::wsfeed::*, WSError};
#[async_trait]
pub trait CBWSExt {
async fn subscribe(&mut self, subscribe: Subscribe) -> Result<(), CBError>;
}
#[async_trait]
impl<T> CBWSExt for T
where
T: Stream<Item = Result<Message, CBError>> + Sink<TMessage, Error = TError> + Unpin + Send,
{
async fn subscribe(&mut self, subscribe: Subscribe) -> Result<(), CBError> {
let subscribe = serde_json::to_string(&subscribe).unwrap();
self.send(TMessage::Text(subscribe))
.map_err(|e| CBError::Websocket(WSError::Send(e)))
.await
}
}
pub async fn connect_cbws(
uri: &str,
) -> Result<
impl Stream<Item = Result<Message, CBError>> + Sink<TMessage, Error = TError> + Unpin,
CBError,
> {
let (stream, _) = connect_async(uri)
.map_err(|e| CBError::Websocket(WSError::Connect(e)))
.await?;
log::debug!("subsription sent");
Ok(stream
.try_filter(|msg| future::ready(msg.is_text()))
.map_ok(convert_msg)
.map_err(|e| CBError::Websocket(WSError::Read(e))))
}
async fn test(uri: &str, product_ids: &[&str], channels: &[ChannelType]) {
let subscribe = Subscribe {
_type: SubscribeCmd::Subscribe,
product_ids: product_ids.into_iter().map(|x| x.to_string()).collect(),
channels: channels
.to_vec()
.into_iter()
.map(|x| Channel::Name(x))
.collect::<Vec<_>>(),
auth: None,
};
let mut cbws = connect_cbws(uri).await.expect("failed to connect.");
cbws.subscribe(subscribe).await.unwrap();
}
is it okay to modify the code in this way?
I want to know if you are thinking about a better way or if there was a reason for not implementing the subscription functionality.
thanks!
inv2004 commented
I will check it a bit later. Thx for the proposal