use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
use futures::stream::Stream;
use tokio::sync::{broadcast, mpsc};
use tokio_stream::wrappers::BroadcastStream;
use tracing::warn;
use super::pricing::PriceUpdate;
use super::source::{StreamCommand, StreamSource, run_stream_loop};
use super::yahoo::YahooStreamSource;
use crate::error::FinanceError;
pub type StreamResult<T> = std::result::Result<T, StreamError>;
#[derive(Debug, Clone)]
pub enum StreamError {
ConnectionFailed(String),
WebSocketError(String),
DecodeError(String),
}
impl std::fmt::Display for StreamError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
StreamError::ConnectionFailed(e) => write!(f, "Connection failed: {}", e),
StreamError::WebSocketError(e) => write!(f, "WebSocket error: {}", e),
StreamError::DecodeError(e) => write!(f, "Decode error: {}", e),
}
}
}
impl std::error::Error for StreamError {}
impl From<StreamError> for FinanceError {
fn from(e: StreamError) -> Self {
FinanceError::ResponseStructureError {
field: "streaming".to_string(),
context: e.to_string(),
}
}
}
const RECONNECT_BACKOFF_SECS: u64 = 3;
const CHANNEL_CAPACITY: usize = 1024;
pub struct PriceStream {
inner: BroadcastStream<PriceUpdate>,
_handle: Arc<StreamHandle>,
}
struct StreamHandle {
command_tx: mpsc::Sender<StreamCommand>,
broadcast_tx: broadcast::Sender<PriceUpdate>,
}
impl PriceStream {
pub async fn subscribe(symbols: &[&str]) -> StreamResult<Self> {
Self::subscribe_with_source(
Arc::new(YahooStreamSource),
symbols,
Duration::from_secs(RECONNECT_BACKOFF_SECS),
)
.await
}
pub(crate) async fn subscribe_with_source(
source: Arc<dyn StreamSource>,
symbols: &[&str],
retry_delay: Duration,
) -> StreamResult<Self> {
let (broadcast_tx, broadcast_rx) = broadcast::channel(CHANNEL_CAPACITY);
let (command_tx, command_rx) = mpsc::channel(32);
let initial_symbols: Vec<String> = symbols.iter().map(|s| s.to_string()).collect();
let tx_clone = broadcast_tx.clone();
tokio::spawn(run_stream_loop(
source,
initial_symbols,
broadcast_tx,
command_rx,
retry_delay,
));
let handle = Arc::new(StreamHandle {
command_tx,
broadcast_tx: tx_clone,
});
Ok(PriceStream {
inner: BroadcastStream::new(broadcast_rx),
_handle: handle,
})
}
pub fn resubscribe(&self) -> Self {
PriceStream {
inner: BroadcastStream::new(self._handle.broadcast_tx.subscribe()),
_handle: Arc::clone(&self._handle),
}
}
pub async fn add_symbols(&self, symbols: &[&str]) {
let symbols: Vec<String> = symbols.iter().map(|s| s.to_string()).collect();
let _ = self
._handle
.command_tx
.send(StreamCommand::Subscribe(symbols))
.await;
}
pub async fn remove_symbols(&self, symbols: &[&str]) {
let symbols: Vec<String> = symbols.iter().map(|s| s.to_string()).collect();
let _ = self
._handle
.command_tx
.send(StreamCommand::Unsubscribe(symbols))
.await;
}
pub async fn close(&self) {
let _ = self._handle.command_tx.send(StreamCommand::Close).await;
}
}
impl Stream for PriceStream {
type Item = PriceUpdate;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match Pin::new(&mut self.inner).poll_next(cx) {
Poll::Ready(Some(Ok(data))) => Poll::Ready(Some(data)),
Poll::Ready(Some(Err(e))) => {
warn!("Broadcast error: {:?}", e);
cx.waker().wake_by_ref();
Poll::Pending
}
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
}
pub struct PriceStreamBuilder {
symbols: Vec<String>,
retry_delay: Duration,
}
impl PriceStreamBuilder {
pub fn new() -> Self {
Self {
symbols: Vec::new(),
retry_delay: Duration::from_secs(RECONNECT_BACKOFF_SECS),
}
}
pub fn symbols(mut self, symbols: &[&str]) -> Self {
self.symbols.extend(symbols.iter().map(|s| s.to_string()));
self
}
pub fn retry(mut self, delay: Duration) -> Self {
self.retry_delay = delay;
self
}
pub async fn build(self) -> StreamResult<PriceStream> {
let symbol_refs: Vec<&str> = self.symbols.iter().map(|s| s.as_str()).collect();
PriceStream::subscribe_with_source(
Arc::new(YahooStreamSource),
&symbol_refs,
self.retry_delay,
)
.await
}
}
impl Default for PriceStreamBuilder {
fn default() -> Self {
Self::new()
}
}