1use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::Arc;
15use std::time::{Duration, Instant};
16use tokio::sync::{mpsc, RwLock};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
20pub enum StreamingMode {
21 None,
23 Chunked,
25 Deferred,
27 Streamed,
29 Multipart,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub enum CompressionType {
36 None,
38 Gzip,
40 Deflate,
42 Brotli,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ResponseChunk {
49 pub id: u64,
51 pub path: Option<Vec<PathSegment>>,
53 pub data: serde_json::Value,
55 pub has_errors: bool,
57 pub errors: Vec<StreamError>,
59 pub is_final: bool,
61 pub has_next: bool,
63 pub label: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(untagged)]
70pub enum PathSegment {
71 Field(String),
73 Index(usize),
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct StreamError {
80 pub message: String,
82 pub path: Option<Vec<PathSegment>>,
84 pub extensions: Option<HashMap<String, serde_json::Value>>,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct StreamingConfig {
91 pub default_mode: StreamingMode,
93 pub max_chunk_size: usize,
95 pub min_chunk_size: usize,
97 pub compression: CompressionType,
99 pub compression_threshold: usize,
101 pub max_concurrent_deferred: usize,
103 pub chunk_timeout: Duration,
105 pub track_progress: bool,
107 pub buffer_size: usize,
109 pub require_ack: bool,
111 pub heartbeat_interval: Option<Duration>,
113}
114
115impl Default for StreamingConfig {
116 fn default() -> Self {
117 Self {
118 default_mode: StreamingMode::Chunked,
119 max_chunk_size: 64 * 1024, min_chunk_size: 1024, compression: CompressionType::None,
122 compression_threshold: 1024, max_concurrent_deferred: 10,
124 chunk_timeout: Duration::from_secs(30),
125 track_progress: true,
126 buffer_size: 100,
127 require_ack: false,
128 heartbeat_interval: Some(Duration::from_secs(15)),
129 }
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct StreamProgress {
136 pub stream_id: String,
138 pub chunks_sent: u64,
140 pub bytes_sent: u64,
142 pub total_bytes: Option<u64>,
144 pub throughput_bps: f64,
146 pub elapsed_ms: u64,
148 pub pending_chunks: usize,
150 pub is_complete: bool,
152 pub has_errors: bool,
154}
155
156pub struct ResponseStreamer {
158 id: String,
160 config: StreamingConfig,
162 sender: mpsc::Sender<ResponseChunk>,
164 chunk_id: AtomicU64,
166 start_time: Instant,
168 bytes_sent: AtomicU64,
170 state: Arc<RwLock<StreamerState>>,
172}
173
174struct StreamerState {
176 is_active: bool,
178 pending_paths: Vec<Vec<PathSegment>>,
180 errors: Vec<StreamError>,
182 progress_callback: Option<Box<dyn Fn(StreamProgress) + Send + Sync>>,
184}
185
186impl ResponseStreamer {
187 pub fn new(config: StreamingConfig) -> (Self, mpsc::Receiver<ResponseChunk>) {
189 let (sender, receiver) = mpsc::channel(config.buffer_size);
190 let id = uuid::Uuid::new_v4().to_string();
191
192 let streamer = Self {
193 id,
194 config,
195 sender,
196 chunk_id: AtomicU64::new(0),
197 start_time: Instant::now(),
198 bytes_sent: AtomicU64::new(0),
199 state: Arc::new(RwLock::new(StreamerState {
200 is_active: true,
201 pending_paths: Vec::new(),
202 errors: Vec::new(),
203 progress_callback: None,
204 })),
205 };
206
207 (streamer, receiver)
208 }
209
210 pub fn id(&self) -> &str {
212 &self.id
213 }
214
215 pub async fn set_progress_callback<F>(&self, callback: F)
217 where
218 F: Fn(StreamProgress) + Send + Sync + 'static,
219 {
220 let mut state = self.state.write().await;
221 state.progress_callback = Some(Box::new(callback));
222 }
223
224 pub async fn send_chunk(
226 &self,
227 data: serde_json::Value,
228 path: Option<Vec<PathSegment>>,
229 label: Option<String>,
230 is_final: bool,
231 has_next: bool,
232 ) -> Result<(), StreamError> {
233 let state = self.state.read().await;
234 if !state.is_active {
235 return Err(StreamError {
236 message: "Stream is not active".to_string(),
237 path: None,
238 extensions: None,
239 });
240 }
241 drop(state);
242
243 let chunk_id = self.chunk_id.fetch_add(1, Ordering::SeqCst);
244
245 let chunk_json = serde_json::to_string(&data).unwrap_or_default();
247 let chunk_size = chunk_json.len() as u64;
248 self.bytes_sent.fetch_add(chunk_size, Ordering::SeqCst);
249
250 let chunk = ResponseChunk {
251 id: chunk_id,
252 path,
253 data,
254 has_errors: false,
255 errors: Vec::new(),
256 is_final,
257 has_next,
258 label,
259 };
260
261 self.sender.send(chunk).await.map_err(|_| StreamError {
262 message: "Failed to send chunk".to_string(),
263 path: None,
264 extensions: None,
265 })?;
266
267 if self.config.track_progress {
269 self.report_progress().await;
270 }
271
272 Ok(())
273 }
274
275 pub async fn send_error(&self, error: StreamError, path: Option<Vec<PathSegment>>) {
277 let chunk_id = self.chunk_id.fetch_add(1, Ordering::SeqCst);
278
279 let chunk = ResponseChunk {
280 id: chunk_id,
281 path,
282 data: serde_json::Value::Null,
283 has_errors: true,
284 errors: vec![error.clone()],
285 is_final: false,
286 has_next: true,
287 label: None,
288 };
289
290 let _ = self.sender.send(chunk).await;
291
292 let mut state = self.state.write().await;
294 state.errors.push(error);
295 }
296
297 pub async fn send_heartbeat(&self) -> Result<(), StreamError> {
299 let chunk = ResponseChunk {
300 id: self.chunk_id.fetch_add(1, Ordering::SeqCst),
301 path: None,
302 data: serde_json::json!({"__heartbeat": true}),
303 has_errors: false,
304 errors: Vec::new(),
305 is_final: false,
306 has_next: true,
307 label: Some("__heartbeat".to_string()),
308 };
309
310 self.sender.send(chunk).await.map_err(|_| StreamError {
311 message: "Failed to send heartbeat".to_string(),
312 path: None,
313 extensions: None,
314 })
315 }
316
317 pub async fn complete(&self) {
319 let chunk = ResponseChunk {
321 id: self.chunk_id.fetch_add(1, Ordering::SeqCst),
322 path: None,
323 data: serde_json::Value::Null,
324 has_errors: false,
325 errors: Vec::new(),
326 is_final: true,
327 has_next: false,
328 label: None,
329 };
330
331 let _ = self.sender.send(chunk).await;
332
333 let mut state = self.state.write().await;
335 state.is_active = false;
336 }
337
338 pub async fn get_progress(&self) -> StreamProgress {
340 let state = self.state.read().await;
341 let elapsed = self.start_time.elapsed();
342 let bytes = self.bytes_sent.load(Ordering::SeqCst);
343
344 StreamProgress {
345 stream_id: self.id.clone(),
346 chunks_sent: self.chunk_id.load(Ordering::SeqCst),
347 bytes_sent: bytes,
348 total_bytes: None,
349 throughput_bps: if elapsed.as_secs_f64() > 0.0 {
350 bytes as f64 / elapsed.as_secs_f64()
351 } else {
352 0.0
353 },
354 elapsed_ms: elapsed.as_millis() as u64,
355 pending_chunks: state.pending_paths.len(),
356 is_complete: !state.is_active,
357 has_errors: !state.errors.is_empty(),
358 }
359 }
360
361 async fn report_progress(&self) {
363 let progress = self.get_progress().await;
364 let state = self.state.read().await;
365 if let Some(ref callback) = state.progress_callback {
366 callback(progress);
367 }
368 }
369}
370
371pub struct IncrementalDeliveryManager {
373 config: StreamingConfig,
375 streams: Arc<RwLock<HashMap<String, StreamInfo>>>,
377 stats: Arc<RwLock<StreamingStatistics>>,
379}
380
381#[derive(Debug, Clone)]
383struct StreamInfo {
384 id: String,
386 #[allow(dead_code)]
388 mode: StreamingMode,
389 started_at: Instant,
391 bytes_sent: u64,
393 chunks_sent: u64,
395 #[allow(dead_code)]
397 client_id: Option<String>,
398}
399
400#[derive(Debug, Clone, Default, Serialize, Deserialize)]
402pub struct StreamingStatistics {
403 pub total_streams: u64,
405 pub active_streams: u64,
407 pub total_bytes: u64,
409 pub total_chunks: u64,
411 pub avg_duration_ms: f64,
413 pub streams_by_mode: HashMap<String, u64>,
415 pub error_count: u64,
417}
418
419impl IncrementalDeliveryManager {
420 pub fn new(config: StreamingConfig) -> Self {
422 Self {
423 config,
424 streams: Arc::new(RwLock::new(HashMap::new())),
425 stats: Arc::new(RwLock::new(StreamingStatistics::default())),
426 }
427 }
428
429 pub async fn create_stream(
431 &self,
432 mode: StreamingMode,
433 client_id: Option<&str>,
434 ) -> (ResponseStreamer, mpsc::Receiver<ResponseChunk>) {
435 let (streamer, receiver) = ResponseStreamer::new(self.config.clone());
436 let stream_id = streamer.id().to_string();
437
438 {
440 let mut streams = self.streams.write().await;
441 streams.insert(
442 stream_id.clone(),
443 StreamInfo {
444 id: stream_id.clone(),
445 mode,
446 started_at: Instant::now(),
447 bytes_sent: 0,
448 chunks_sent: 0,
449 client_id: client_id.map(|s| s.to_string()),
450 },
451 );
452 }
453
454 {
456 let mut stats = self.stats.write().await;
457 stats.total_streams += 1;
458 stats.active_streams += 1;
459 *stats
460 .streams_by_mode
461 .entry(format!("{:?}", mode))
462 .or_insert(0) += 1;
463 }
464
465 (streamer, receiver)
466 }
467
468 pub async fn close_stream(&self, stream_id: &str) {
470 let stream_info = {
471 let mut streams = self.streams.write().await;
472 streams.remove(stream_id)
473 };
474
475 if let Some(info) = stream_info {
476 let mut stats = self.stats.write().await;
477 stats.active_streams = stats.active_streams.saturating_sub(1);
478 stats.total_bytes += info.bytes_sent;
479 stats.total_chunks += info.chunks_sent;
480
481 let duration = info.started_at.elapsed().as_millis() as f64;
483 let n = stats.total_streams as f64;
484 stats.avg_duration_ms = (stats.avg_duration_ms * (n - 1.0) + duration) / n;
485 }
486 }
487
488 pub async fn get_active_streams(&self) -> Vec<StreamProgress> {
490 let streams = self.streams.read().await;
491 streams
492 .values()
493 .map(|info| StreamProgress {
494 stream_id: info.id.clone(),
495 chunks_sent: info.chunks_sent,
496 bytes_sent: info.bytes_sent,
497 total_bytes: None,
498 throughput_bps: 0.0,
499 elapsed_ms: info.started_at.elapsed().as_millis() as u64,
500 pending_chunks: 0,
501 is_complete: false,
502 has_errors: false,
503 })
504 .collect()
505 }
506
507 pub async fn get_statistics(&self) -> StreamingStatistics {
509 self.stats.read().await.clone()
510 }
511
512 pub async fn process_deferred(
514 &self,
515 streamer: &ResponseStreamer,
516 initial_data: serde_json::Value,
517 deferred_fields: Vec<DeferredField>,
518 ) -> Result<(), StreamError> {
519 let total_deferred = deferred_fields.len();
520
521 streamer
523 .send_chunk(initial_data, None, None, false, total_deferred > 0)
524 .await?;
525
526 for (i, deferred) in deferred_fields.into_iter().enumerate() {
528 let is_last = i == total_deferred - 1;
529
530 let data = (deferred.resolver)().await;
532
533 streamer
534 .send_chunk(data, Some(deferred.path), deferred.label, is_last, !is_last)
535 .await?;
536 }
537
538 Ok(())
539 }
540
541 pub async fn process_stream<I, T>(
543 &self,
544 streamer: &ResponseStreamer,
545 path: Vec<PathSegment>,
546 label: Option<String>,
547 items: I,
548 ) -> Result<(), StreamError>
549 where
550 I: IntoIterator<Item = T>,
551 T: Serialize,
552 {
553 let mut items_iter = items.into_iter().peekable();
554 let mut index = 0;
555
556 while let Some(item) = items_iter.next() {
557 let has_next = items_iter.peek().is_some();
558 let mut item_path = path.clone();
559 item_path.push(PathSegment::Index(index));
560
561 let data = serde_json::to_value(&item).map_err(|e| StreamError {
562 message: format!("Serialization error: {}", e),
563 path: Some(item_path.clone()),
564 extensions: None,
565 })?;
566
567 streamer
568 .send_chunk(data, Some(item_path), label.clone(), !has_next, has_next)
569 .await?;
570
571 index += 1;
572 }
573
574 Ok(())
575 }
576}
577
578pub struct DeferredField {
580 pub path: Vec<PathSegment>,
582 pub label: Option<String>,
584 pub resolver: Box<
586 dyn Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = serde_json::Value> + Send>>
587 + Send
588 + Sync,
589 >,
590}
591
592pub struct MultipartFormatter {
594 boundary: String,
596}
597
598impl MultipartFormatter {
599 pub fn new() -> Self {
601 Self {
602 boundary: format!("-graphql-{}", uuid::Uuid::new_v4()),
603 }
604 }
605
606 pub fn content_type(&self) -> String {
608 format!("multipart/mixed; boundary=\"{}\"", self.boundary)
609 }
610
611 pub fn format_chunk(&self, chunk: &ResponseChunk) -> Vec<u8> {
613 let mut output = Vec::new();
614
615 output.extend_from_slice(format!("--{}\r\n", self.boundary).as_bytes());
617
618 output.extend_from_slice(b"Content-Type: application/json; charset=utf-8\r\n");
620
621 if chunk.is_final {
622 output.extend_from_slice(b"X-GraphQL-Final: true\r\n");
623 }
624
625 if let Some(ref label) = chunk.label {
626 output.extend_from_slice(format!("X-GraphQL-Label: {}\r\n", label).as_bytes());
627 }
628
629 output.extend_from_slice(b"\r\n");
630
631 let body = serde_json::json!({
633 "data": chunk.data,
634 "path": chunk.path,
635 "hasNext": chunk.has_next,
636 "errors": if chunk.has_errors { Some(&chunk.errors) } else { None },
637 });
638
639 output.extend_from_slice(
640 serde_json::to_string(&body)
641 .expect("serializing JSON body should succeed")
642 .as_bytes(),
643 );
644 output.extend_from_slice(b"\r\n");
645
646 output
647 }
648
649 pub fn format_final(&self) -> Vec<u8> {
651 format!("--{}--\r\n", self.boundary).into_bytes()
652 }
653}
654
655impl Default for MultipartFormatter {
656 fn default() -> Self {
657 Self::new()
658 }
659}
660
661pub mod compression {
663 use super::*;
664
665 pub fn compress(data: &[u8], compression_type: CompressionType) -> Result<Vec<u8>, String> {
667 match compression_type {
668 CompressionType::None => Ok(data.to_vec()),
669 CompressionType::Gzip => compress_gzip(data),
670 CompressionType::Deflate => compress_deflate(data),
671 CompressionType::Brotli => compress_brotli(data),
672 }
673 }
674
675 fn compress_gzip(data: &[u8]) -> Result<Vec<u8>, String> {
676 oxiarc_deflate::gzip_compress(data, 6).map_err(|e| format!("Gzip compression error: {}", e))
679 }
680
681 fn compress_deflate(data: &[u8]) -> Result<Vec<u8>, String> {
682 oxiarc_deflate::deflate(data, 6).map_err(|e| format!("Deflate compression error: {}", e))
687 }
688
689 fn compress_brotli(data: &[u8]) -> Result<Vec<u8>, String> {
690 const BROTLI_QUALITY: u32 = 6;
695 oxiarc_brotli::compress(data, BROTLI_QUALITY)
696 .map_err(|e| format!("Brotli compression error: {}", e))
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703
704 #[tokio::test]
705 async fn test_streamer_creation() {
706 let config = StreamingConfig::default();
707 let (streamer, _receiver) = ResponseStreamer::new(config);
708 assert!(!streamer.id().is_empty());
709 }
710
711 #[tokio::test]
712 async fn test_send_chunk() {
713 let config = StreamingConfig::default();
714 let (streamer, mut receiver) = ResponseStreamer::new(config);
715
716 let data = serde_json::json!({"field": "value"});
717 streamer
718 .send_chunk(data.clone(), None, None, false, true)
719 .await
720 .expect("should succeed");
721
722 let chunk = receiver.recv().await.expect("should succeed");
723 assert_eq!(chunk.data, data);
724 assert!(!chunk.is_final);
725 assert!(chunk.has_next);
726 }
727
728 #[tokio::test]
729 async fn test_send_error() {
730 let config = StreamingConfig::default();
731 let (streamer, mut receiver) = ResponseStreamer::new(config);
732
733 let error = StreamError {
734 message: "Test error".to_string(),
735 path: None,
736 extensions: None,
737 };
738 streamer.send_error(error, None).await;
739
740 let chunk = receiver.recv().await.expect("should succeed");
741 assert!(chunk.has_errors);
742 assert!(!chunk.errors.is_empty());
743 }
744
745 #[tokio::test]
746 async fn test_complete_stream() {
747 let config = StreamingConfig::default();
748 let (streamer, mut receiver) = ResponseStreamer::new(config);
749
750 streamer.complete().await;
751
752 let chunk = receiver.recv().await.expect("should succeed");
753 assert!(chunk.is_final);
754 assert!(!chunk.has_next);
755 }
756
757 #[tokio::test]
758 async fn test_progress_tracking() {
759 let config = StreamingConfig {
760 track_progress: true,
761 ..Default::default()
762 };
763 let (streamer, _receiver) = ResponseStreamer::new(config);
764
765 let data = serde_json::json!({"test": "data"});
767 let _ = streamer.send_chunk(data, None, None, false, true).await;
768
769 let progress = streamer.get_progress().await;
770 assert_eq!(progress.chunks_sent, 1);
771 assert!(progress.bytes_sent > 0);
772 }
773
774 #[tokio::test]
775 async fn test_manager_create_stream() {
776 let manager = IncrementalDeliveryManager::new(StreamingConfig::default());
777
778 let (_streamer, _receiver) = manager.create_stream(StreamingMode::Chunked, None).await;
779
780 let stats = manager.get_statistics().await;
781 assert_eq!(stats.total_streams, 1);
782 assert_eq!(stats.active_streams, 1);
783 }
784
785 #[tokio::test]
786 async fn test_manager_close_stream() {
787 let manager = IncrementalDeliveryManager::new(StreamingConfig::default());
788
789 let (streamer, _receiver) = manager.create_stream(StreamingMode::Chunked, None).await;
790 let stream_id = streamer.id().to_string();
791
792 manager.close_stream(&stream_id).await;
793
794 let stats = manager.get_statistics().await;
795 assert_eq!(stats.active_streams, 0);
796 }
797
798 #[tokio::test]
799 async fn test_multipart_formatter() {
800 let formatter = MultipartFormatter::new();
801
802 assert!(formatter.content_type().contains("multipart/mixed"));
803
804 let chunk = ResponseChunk {
805 id: 0,
806 path: None,
807 data: serde_json::json!({"test": true}),
808 has_errors: false,
809 errors: Vec::new(),
810 is_final: false,
811 has_next: true,
812 label: None,
813 };
814
815 let formatted = formatter.format_chunk(&chunk);
816 assert!(!formatted.is_empty());
817 }
818
819 #[tokio::test]
820 async fn test_path_segments() {
821 let path = vec![
822 PathSegment::Field("users".to_string()),
823 PathSegment::Index(0),
824 PathSegment::Field("name".to_string()),
825 ];
826
827 let json = serde_json::to_string(&path).expect("should succeed");
828 assert!(json.contains("users"));
829 }
830
831 #[tokio::test]
832 async fn test_stream_items() {
833 let manager = IncrementalDeliveryManager::new(StreamingConfig::default());
834 let (streamer, mut receiver) = manager.create_stream(StreamingMode::Streamed, None).await;
835
836 let items = vec!["item1", "item2", "item3"];
837 let path = vec![PathSegment::Field("items".to_string())];
838
839 let streamer_clone = Arc::new(streamer);
841 tokio::spawn(async move {
842 let _ = manager
843 .process_stream(&streamer_clone, path, Some("items".to_string()), items)
844 .await;
845 });
846
847 let mut received = Vec::new();
849 while let Some(chunk) = receiver.recv().await {
850 received.push(chunk);
851 if received.len() >= 3 {
852 break;
853 }
854 }
855
856 assert_eq!(received.len(), 3);
857 }
858
859 #[tokio::test]
860 async fn test_heartbeat() {
861 let config = StreamingConfig::default();
862 let (streamer, mut receiver) = ResponseStreamer::new(config);
863
864 streamer.send_heartbeat().await.expect("should succeed");
865
866 let chunk = receiver.recv().await.expect("should succeed");
867 assert_eq!(chunk.label, Some("__heartbeat".to_string()));
868 }
869
870 #[tokio::test]
871 async fn test_compression() {
872 let data = b"Hello, World!";
873
874 let compressed =
875 compression::compress(data, CompressionType::Gzip).expect("should succeed");
876 assert!(!compressed.is_empty());
877
878 let deflated =
879 compression::compress(data, CompressionType::Deflate).expect("should succeed");
880 assert!(!deflated.is_empty());
881
882 let none = compression::compress(data, CompressionType::None).expect("should succeed");
883 assert_eq!(none, data);
884 }
885
886 #[test]
887 fn test_gzip_compression_roundtrip() {
888 let data: Vec<u8> = b"oxirs-gql streamed response gzip body "
891 .iter()
892 .cycle()
893 .take(4096)
894 .copied()
895 .collect();
896
897 let compressed =
898 compression::compress(&data, CompressionType::Gzip).expect("gzip compression failed");
899 assert_eq!(&compressed[..2], &[0x1f, 0x8b]);
901 assert!(compressed.len() < data.len());
902
903 let decompressed =
904 oxiarc_deflate::gzip_decompress(&compressed).expect("gzip decompression failed");
905 assert_eq!(decompressed, data);
906 }
907
908 #[test]
909 fn test_deflate_compression_roundtrip() {
910 let data: Vec<u8> = b"oxirs-gql streamed response deflate body "
914 .iter()
915 .cycle()
916 .take(4096)
917 .copied()
918 .collect();
919
920 let compressed = compression::compress(&data, CompressionType::Deflate)
921 .expect("deflate compression failed");
922 assert!(compressed.len() < data.len());
923
924 let decompressed = oxiarc_deflate::inflate(&compressed).expect("inflate failed");
925 assert_eq!(decompressed, data);
926 }
927
928 #[test]
929 fn test_brotli_compression_roundtrip() {
930 let data: Vec<u8> = b"oxirs-gql streamed response brotli body "
935 .iter()
936 .cycle()
937 .take(4096)
938 .copied()
939 .collect();
940
941 let compressed = compression::compress(&data, CompressionType::Brotli)
942 .expect("brotli compression should now be implemented");
943 assert!(!compressed.is_empty());
944 assert!(compressed.len() < data.len());
945
946 let decompressed =
947 oxiarc_brotli::decompress(&compressed).expect("brotli decompression failed");
948 assert_eq!(decompressed, data);
949 }
950}