contextvm_sdk/transport/open_stream/
receiver.rs1use std::time::Instant;
14
15use crate::core::types::JsonRpcNotification;
16
17use super::errors::OpenStreamError;
18use super::registry::{OpenStreamRegistry, OpenStreamRegistryPolicy};
19use super::session::{FrameOutcome, OpenStreamSession};
20
21pub struct OpenStreamReceiver {
23 registry: OpenStreamRegistry,
24}
25
26impl Default for OpenStreamReceiver {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl OpenStreamReceiver {
33 pub fn new() -> Self {
35 Self {
36 registry: OpenStreamRegistry::new(),
37 }
38 }
39
40 pub fn with_policy(policy: OpenStreamRegistryPolicy) -> Self {
42 Self {
43 registry: OpenStreamRegistry::with_policy(policy),
44 }
45 }
46
47 pub fn is_open_stream_frame(notification: &JsonRpcNotification) -> bool {
49 OpenStreamRegistry::is_open_stream_progress(notification)
50 }
51
52 pub async fn process_frame(
55 &mut self,
56 notification: &JsonRpcNotification,
57 ) -> Result<FrameOutcome, OpenStreamError> {
58 self.registry
59 .process_frame(Instant::now(), notification)
60 .await
61 }
62
63 pub fn get_session(&self, progress_token: &str) -> Option<OpenStreamSession> {
65 self.registry.get_session(progress_token)
66 }
67
68 pub fn active_stream_count(&self) -> usize {
70 self.registry.size()
71 }
72
73 pub fn registry(&self) -> &OpenStreamRegistry {
76 &self.registry
77 }
78
79 pub fn registry_mut(&mut self) -> &mut OpenStreamRegistry {
81 &mut self.registry
82 }
83
84 pub fn clear(&mut self) {
86 self.registry.clear();
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::transport::open_stream::frame::OpenStreamFrame;
94 use serde_json::json;
95
96 fn start_notification(token: &str, progress: u64) -> JsonRpcNotification {
97 OpenStreamFrame::Start { content_type: None }
98 .into_progress_notification(token, progress, None)
99 .unwrap()
100 }
101
102 #[test]
103 fn is_open_stream_frame_detects_cvm_payload() {
104 let frame = start_notification("tok", 1);
105 assert!(OpenStreamReceiver::is_open_stream_frame(&frame));
106
107 let plain = JsonRpcNotification {
108 jsonrpc: "2.0".to_string(),
109 method: "notifications/progress".to_string(),
110 params: Some(json!({ "progressToken": "tok", "progress": 1 })),
111 };
112 assert!(!OpenStreamReceiver::is_open_stream_frame(&plain));
113
114 let oversized = JsonRpcNotification {
116 jsonrpc: "2.0".to_string(),
117 method: "notifications/progress".to_string(),
118 params: Some(json!({
119 "progressToken": "tok",
120 "progress": 1,
121 "cvm": { "type": "oversized-transfer", "frameType": "end" }
122 })),
123 };
124 assert!(!OpenStreamReceiver::is_open_stream_frame(&oversized));
125 }
126
127 #[tokio::test]
128 async fn process_frame_delegates_to_registry_and_tracks_session() {
129 let mut receiver = OpenStreamReceiver::new();
130 receiver
131 .process_frame(&start_notification("tok", 1))
132 .await
133 .unwrap();
134 assert_eq!(receiver.active_stream_count(), 1);
135 assert!(receiver.get_session("tok").is_some());
136
137 let close = OpenStreamFrame::Close {
139 last_chunk_index: None,
140 }
141 .into_progress_notification("tok", 2, None)
142 .unwrap();
143 receiver.process_frame(&close).await.unwrap();
144 assert_eq!(receiver.active_stream_count(), 0);
145 }
146
147 #[tokio::test]
148 async fn non_open_stream_progress_notification_is_not_intercepted() {
149 let mut receiver = OpenStreamReceiver::new();
150
151 let oversized = JsonRpcNotification {
154 jsonrpc: "2.0".to_string(),
155 method: "notifications/progress".to_string(),
156 params: Some(json!({
157 "progressToken": "tok",
158 "progress": 1,
159 "cvm": { "type": "oversized-transfer", "frameType": "end" }
160 })),
161 };
162 let plain = JsonRpcNotification {
163 jsonrpc: "2.0".to_string(),
164 method: "notifications/progress".to_string(),
165 params: Some(json!({ "progressToken": "tok", "progress": 1 })),
166 };
167
168 for notification in [&oversized, &plain] {
169 assert!(!OpenStreamReceiver::is_open_stream_frame(notification));
170 if OpenStreamReceiver::is_open_stream_frame(notification) {
173 receiver.process_frame(notification).await.unwrap();
174 }
175 }
176
177 assert_eq!(receiver.active_stream_count(), 0);
179 assert!(receiver.get_session("tok").is_none());
180 }
181}