use async_trait::async_trait;
use std::time::Duration;
use crate::live_engine::CompletedBar;
#[derive(Debug, thiserror::Error)]
pub enum SinkError {
#[error("recoverable: {0}")]
Recoverable(String),
#[error("unrecoverable: {0}")]
Unrecoverable(String),
}
#[derive(Debug, Clone)]
pub struct SourceCheckpoint {
pub last_ref_id: i64,
pub timestamp_ms: i64,
}
#[async_trait]
pub trait BarSource: Send {
async fn next_bar(&mut self, timeout: Duration) -> Option<CompletedBar>;
fn snapshot(&self) -> Option<SourceCheckpoint>;
}
pub trait BarSink: Send {
fn on_bar(&mut self, bar: &CompletedBar) -> Result<(), SinkError>;
fn flush(&mut self) -> Result<(), SinkError>;
fn name(&self) -> &str;
}
pub trait EngineClock: Send + Sync {
fn now_ms(&self) -> i64;
}
#[cfg(test)]
mod tests {
use super::*;
struct NoopSink;
impl BarSink for NoopSink {
fn on_bar(&mut self, _bar: &CompletedBar) -> Result<(), SinkError> {
Ok(())
}
fn flush(&mut self) -> Result<(), SinkError> {
Ok(())
}
fn name(&self) -> &str {
"noop"
}
}
#[test]
fn test_sink_error_display() {
let r = SinkError::Recoverable("channel full".into());
assert!(r.to_string().contains("recoverable"));
let u = SinkError::Unrecoverable("serialization failed".into());
assert!(u.to_string().contains("unrecoverable"));
}
#[test]
fn test_noop_sink() {
let mut sink = NoopSink;
assert_eq!(sink.name(), "noop");
assert!(sink.flush().is_ok());
}
}