use std::time::Instant;
use crate::core::types::JsonRpcNotification;
use super::errors::OpenStreamError;
use super::registry::{OpenStreamRegistry, OpenStreamRegistryPolicy};
use super::session::{FrameOutcome, OpenStreamSession};
pub struct OpenStreamReceiver {
registry: OpenStreamRegistry,
}
impl Default for OpenStreamReceiver {
fn default() -> Self {
Self::new()
}
}
impl OpenStreamReceiver {
pub fn new() -> Self {
Self {
registry: OpenStreamRegistry::new(),
}
}
pub fn with_policy(policy: OpenStreamRegistryPolicy) -> Self {
Self {
registry: OpenStreamRegistry::with_policy(policy),
}
}
pub fn is_open_stream_frame(notification: &JsonRpcNotification) -> bool {
OpenStreamRegistry::is_open_stream_progress(notification)
}
pub async fn process_frame(
&mut self,
notification: &JsonRpcNotification,
) -> Result<FrameOutcome, OpenStreamError> {
self.registry
.process_frame(Instant::now(), notification)
.await
}
pub fn get_session(&self, progress_token: &str) -> Option<OpenStreamSession> {
self.registry.get_session(progress_token)
}
pub fn active_stream_count(&self) -> usize {
self.registry.size()
}
pub fn registry(&self) -> &OpenStreamRegistry {
&self.registry
}
pub fn registry_mut(&mut self) -> &mut OpenStreamRegistry {
&mut self.registry
}
pub fn clear(&mut self) {
self.registry.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::open_stream::frame::OpenStreamFrame;
use serde_json::json;
fn start_notification(token: &str, progress: u64) -> JsonRpcNotification {
OpenStreamFrame::Start { content_type: None }
.into_progress_notification(token, progress, None)
.unwrap()
}
#[test]
fn is_open_stream_frame_detects_cvm_payload() {
let frame = start_notification("tok", 1);
assert!(OpenStreamReceiver::is_open_stream_frame(&frame));
let plain = JsonRpcNotification {
jsonrpc: "2.0".to_string(),
method: "notifications/progress".to_string(),
params: Some(json!({ "progressToken": "tok", "progress": 1 })),
};
assert!(!OpenStreamReceiver::is_open_stream_frame(&plain));
let oversized = JsonRpcNotification {
jsonrpc: "2.0".to_string(),
method: "notifications/progress".to_string(),
params: Some(json!({
"progressToken": "tok",
"progress": 1,
"cvm": { "type": "oversized-transfer", "frameType": "end" }
})),
};
assert!(!OpenStreamReceiver::is_open_stream_frame(&oversized));
}
#[tokio::test]
async fn process_frame_delegates_to_registry_and_tracks_session() {
let mut receiver = OpenStreamReceiver::new();
receiver
.process_frame(&start_notification("tok", 1))
.await
.unwrap();
assert_eq!(receiver.active_stream_count(), 1);
assert!(receiver.get_session("tok").is_some());
let close = OpenStreamFrame::Close {
last_chunk_index: None,
}
.into_progress_notification("tok", 2, None)
.unwrap();
receiver.process_frame(&close).await.unwrap();
assert_eq!(receiver.active_stream_count(), 0);
}
#[tokio::test]
async fn non_open_stream_progress_notification_is_not_intercepted() {
let mut receiver = OpenStreamReceiver::new();
let oversized = JsonRpcNotification {
jsonrpc: "2.0".to_string(),
method: "notifications/progress".to_string(),
params: Some(json!({
"progressToken": "tok",
"progress": 1,
"cvm": { "type": "oversized-transfer", "frameType": "end" }
})),
};
let plain = JsonRpcNotification {
jsonrpc: "2.0".to_string(),
method: "notifications/progress".to_string(),
params: Some(json!({ "progressToken": "tok", "progress": 1 })),
};
for notification in [&oversized, &plain] {
assert!(!OpenStreamReceiver::is_open_stream_frame(notification));
if OpenStreamReceiver::is_open_stream_frame(notification) {
receiver.process_frame(notification).await.unwrap();
}
}
assert_eq!(receiver.active_stream_count(), 0);
assert!(receiver.get_session("tok").is_none());
}
}