inv2004 / coinbase-pro-rs

Coinbase pro client for Rust

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

dynamic subscription

SeanKim opened this issue · comments

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!

I will check it a bit later. Thx for the proposal