1#![warn(missing_docs)]
15
16pub mod broadcaster;
18pub mod ext;
20pub mod operators;
22pub mod sink;
24pub mod source;
26pub mod task;
28
29pub use broadcaster::{BroadcastStream, Broadcaster, DEFAULT_BROADCAST_BUFFER};
30pub use ext::RskitStreamExt;
31pub use operators::combine::{concat, merge};
32pub use sink::{collect, drain, for_each};
33pub use source::{from_channel, from_fn, from_slice};
34pub use task::{SpawnedTask, TaskGroup};
35
36pub use tokio_util::sync::CancellationToken;
37
38#[cfg(test)]
39mod tests {
40 use parking_lot::Mutex;
41 use std::sync::Arc;
42 use std::time::Duration;
43
44 use futures::StreamExt as _;
45
46 use crate::{RskitStreamExt, from_fn, from_slice, merge};
47
48 #[tokio::test]
52 async fn test_from_slice_yields_all_in_order() {
53 let items = vec![1u32, 2, 3, 4, 5];
54 let stream = from_slice(items.clone());
55 let collected: Vec<u32> = stream.collect().await;
56 assert_eq!(collected, items);
57 }
58
59 #[tokio::test]
61 async fn test_from_slice_empty() {
62 let stream = from_slice::<u32>(vec![]);
63 let collected: Vec<u32> = stream.collect().await;
64 assert!(collected.is_empty());
65 }
66
67 #[tokio::test]
69 async fn test_from_fn_yields_until_none() {
70 let counter = Arc::new(Mutex::new(0u32));
71 let c = counter.clone();
72 let stream = from_fn(move || {
73 let c = c.clone();
74 async move {
75 let mut guard = c.lock();
76 let next = if *guard < 5 {
77 let val = *guard;
78 *guard += 1;
79 Some(val)
80 } else {
81 None
82 };
83 drop(guard);
84 next
85 }
86 });
87 let collected: Vec<u32> = stream.collect().await;
88 assert_eq!(collected, vec![0, 1, 2, 3, 4]);
89 }
90
91 #[tokio::test]
93 async fn test_from_fn_immediate_none() {
94 let stream = from_fn(|| async { None::<u32> });
95 let collected: Vec<u32> = stream.collect().await;
96 assert!(collected.is_empty());
97 }
98
99 #[tokio::test]
101 async fn test_merge_set_equality() {
102 let s1 = from_slice(vec![1u32, 3, 5]);
103 let s2 = from_slice(vec![2u32, 4, 6]);
104 let mut combined: Vec<u32> = merge(s1, s2).collect().await;
105 combined.sort_unstable();
106 assert_eq!(combined, vec![1, 2, 3, 4, 5, 6]);
107 }
108
109 #[tokio::test]
111 async fn test_merge_both_empty() {
112 let s1 = from_slice::<u32>(vec![]);
113 let s2 = from_slice::<u32>(vec![]);
114 let combined: Vec<u32> = merge(s1, s2).collect().await;
115 assert!(combined.is_empty());
116 }
117
118 #[tokio::test]
122 async fn test_rmap_transforms_items() {
123 let stream = from_slice(vec![1u32, 2, 3]);
124 let results: Vec<_> = stream
125 .rmap(|x| async move { Ok::<u32, rskit_errors::AppError>(x * 10) })
126 .collect()
127 .await;
128 let values: Vec<u32> = results.into_iter().map(|r| r.unwrap()).collect();
129 assert_eq!(values, vec![10, 20, 30]);
130 }
131
132 #[tokio::test]
134 async fn test_rmap_propagates_error() {
135 let stream = from_slice(vec![1u32, 2, 3]);
136 let results: Vec<_> = stream
137 .rmap(|x| async move {
138 if x == 2 {
139 Err(rskit_errors::AppError::new(
140 rskit_errors::ErrorCode::Internal,
141 "bad item",
142 ))
143 } else {
144 Ok(x)
145 }
146 })
147 .collect()
148 .await;
149 assert!(results[0].is_ok());
150 assert!(results[1].is_err());
151 assert!(results[2].is_ok());
152 }
153
154 #[tokio::test]
158 async fn test_rfilter_keeps_matching_items() {
159 let stream = from_slice(vec![1u32, 2, 3, 4, 5, 6]);
160 let evens: Vec<u32> = stream.rfilter(|x| x % 2 == 0).collect().await;
161 assert_eq!(evens, vec![2, 4, 6]);
162 }
163
164 #[tokio::test]
166 async fn test_rfilter_no_match_yields_empty() {
167 let stream = from_slice(vec![1u32, 3, 5]);
168 let result: Vec<u32> = stream.rfilter(|x| x % 2 == 0).collect().await;
169 assert!(result.is_empty());
170 }
171
172 #[tokio::test]
176 async fn test_rtap_calls_side_effect_and_passes_through() {
177 let seen = Arc::new(Mutex::new(Vec::<u32>::new()));
178 let seen_clone = seen.clone();
179
180 let stream = from_slice(vec![10u32, 20, 30]);
181 let output: Vec<u32> = stream
182 .rtap(move |x| {
183 let seen = seen_clone.clone();
184 let val = *x;
185 async move {
186 seen.lock().push(val);
187 }
188 })
189 .collect()
190 .await;
191
192 assert_eq!(output, vec![10, 20, 30]);
193 assert_eq!(*seen.lock(), vec![10, 20, 30]);
194 }
195
196 #[tokio::test]
200 async fn test_rreduce_folds_to_single_value() {
201 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
202 let sum = stream.rreduce(0u32, |acc, x| acc + x).await;
203 assert_eq!(sum, 15);
204 }
205
206 #[tokio::test]
208 async fn test_rreduce_empty_stream_returns_init() {
209 let stream = from_slice::<u32>(vec![]);
210 let result = stream.rreduce(42u32, |acc, x| acc + x).await;
211 assert_eq!(result, 42);
212 }
213
214 #[tokio::test]
218 async fn test_rparallel_collects_all_results() {
219 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
220 let mut results: Vec<u32> = stream
221 .rparallel(
222 3,
223 |x| async move { Ok::<u32, rskit_errors::AppError>(x * 2) },
224 )
225 .collect::<Vec<_>>()
226 .await
227 .into_iter()
228 .map(|r| r.unwrap())
229 .collect();
230 results.sort_unstable();
231 assert_eq!(results, vec![2, 4, 6, 8, 10]);
232 }
233
234 #[tokio::test]
236 async fn test_rparallel_propagates_errors() {
237 let stream = from_slice(vec![1u32, 2, 3]);
238 let results: Vec<_> = stream
239 .rparallel(2, |x| async move {
240 if x == 2 {
241 Err(rskit_errors::AppError::new(
242 rskit_errors::ErrorCode::Internal,
243 "parallel error",
244 ))
245 } else {
246 Ok(x)
247 }
248 })
249 .collect()
250 .await;
251 let error_count = results.iter().filter(|r| r.is_err()).count();
252 assert_eq!(error_count, 1);
253 }
254
255 #[tokio::test]
262 async fn test_rfan_out_applies_all_functions() {
263 let add_one = |x: u32| std::future::ready(Ok::<u32, rskit_errors::AppError>(x + 1));
265 let mul_two = |x: u32| std::future::ready(Ok::<u32, rskit_errors::AppError>(x * 2));
266
267 let stream_a = from_slice(vec![5u32, 10u32]);
269 let res_a: Vec<_> = stream_a.rfan_out(1, vec![add_one]).collect().await;
270 let res_a: Vec<Vec<_>> = res_a.into_iter().map(Result::unwrap).collect();
271 assert_eq!(res_a[0][0], 6u32);
272 assert_eq!(res_a[1][0], 11u32);
273
274 let stream_b = from_slice(vec![5u32, 10u32]);
276 let res_b: Vec<_> = stream_b.rfan_out(2, vec![add_one, mul_two]).collect().await;
277 let res_b: Vec<Vec<_>> = res_b.into_iter().map(Result::unwrap).collect();
278 assert_eq!(res_b[0][0], 6u32);
280 assert_eq!(res_b[0][1], 10u32);
281 assert_eq!(res_b[1][0], 11u32);
283 assert_eq!(res_b[1][1], 20u32);
284 }
285
286 #[tokio::test]
288 async fn test_rfan_out_single_function() {
289 let stream = from_slice(vec![3u32, 7u32]);
290 let f = |x: u32| std::future::ready(Ok::<u32, rskit_errors::AppError>(x + 100));
292 let results: Vec<_> = stream.rfan_out(1, vec![f]).collect().await;
293 let results: Vec<Vec<_>> = results.into_iter().map(Result::unwrap).collect();
294 assert_eq!(results.len(), 2);
295 assert_eq!(results[0][0], 103u32);
296 assert_eq!(results[1][0], 107u32);
297 }
298
299 #[tokio::test]
303 async fn test_rbatch_exact_size_batches() {
304 tokio::time::pause();
305
306 let stream = from_slice(vec![1u32, 2, 3, 4, 5, 6]);
307 let handle = tokio::spawn(async move {
308 stream
309 .rbatch(3, Duration::from_millis(500))
310 .collect::<Vec<_>>()
311 .await
312 });
313
314 tokio::time::advance(Duration::from_millis(600)).await;
315 let batches = handle.await.unwrap();
316
317 assert_eq!(batches.len(), 2);
318 assert_eq!(batches[0], vec![1, 2, 3]);
319 assert_eq!(batches[1], vec![4, 5, 6]);
320 }
321
322 #[tokio::test]
324 async fn test_rbatch_partial_flush_on_timeout() {
325 tokio::time::pause();
326
327 let (tx, rx) = tokio::sync::mpsc::channel::<u32>(16);
329 let stream = crate::source::from_channel(rx);
330
331 let handle = tokio::spawn(async move {
332 stream
333 .rbatch(10, Duration::from_millis(100))
334 .collect::<Vec<_>>()
335 .await
336 });
337
338 tx.send(1).await.unwrap();
340 tx.send(2).await.unwrap();
341 drop(tx); tokio::time::advance(Duration::from_millis(200)).await;
344 let batches = handle.await.unwrap();
345
346 assert_eq!(batches.len(), 1);
347 assert_eq!(batches[0], vec![1, 2]);
348 }
349
350 #[tokio::test]
354 async fn test_rdebounce_emits_last_item() {
355 tokio::time::pause();
356
357 let (tx, rx) = tokio::sync::mpsc::channel::<u32>(16);
358 let stream = crate::source::from_channel(rx);
359
360 let handle = tokio::spawn(async move {
361 stream
362 .rdebounce(Duration::from_millis(100))
363 .collect::<Vec<_>>()
364 .await
365 });
366
367 tx.send(1).await.unwrap();
369 tx.send(2).await.unwrap();
370 tx.send(3).await.unwrap();
371 drop(tx);
372
373 tokio::time::advance(Duration::from_millis(200)).await;
374 let result = handle.await.unwrap();
375
376 assert!(!result.is_empty());
378 assert_eq!(*result.last().unwrap(), 3u32);
379 }
380
381 #[tokio::test(start_paused = true)]
387 async fn test_rdebounce_batch_collapses_a_burst() {
388 let source = async_stream::stream! {
389 yield 1u32;
390 yield 2u32;
391 tokio::time::sleep(Duration::from_millis(50)).await;
393 yield 3u32;
394 };
395 let result: Vec<Vec<u32>> = source
396 .rdebounce_batch(Duration::from_millis(100), 1024)
397 .collect()
398 .await;
399 assert_eq!(result, vec![vec![1u32, 2, 3]]);
400 }
401
402 #[tokio::test(start_paused = true)]
405 async fn test_rdebounce_batch_splits_separate_windows() {
406 let source = async_stream::stream! {
407 yield 1u32;
408 tokio::time::sleep(Duration::from_millis(150)).await;
409 yield 2u32;
410 tokio::time::sleep(Duration::from_millis(150)).await;
411 };
412 let result: Vec<Vec<u32>> = source
413 .rdebounce_batch(Duration::from_millis(100), 1024)
414 .collect()
415 .await;
416 assert_eq!(result, vec![vec![1u32], vec![2u32]]);
417 }
418
419 #[tokio::test(start_paused = true)]
422 async fn test_rdebounce_batch_flushes_pending_on_close() {
423 let source = async_stream::stream! { yield 7u32; };
424 let result: Vec<Vec<u32>> = source
425 .rdebounce_batch(Duration::from_millis(100), 1024)
426 .collect()
427 .await;
428 assert_eq!(result, vec![vec![7u32]]);
429 }
430
431 #[tokio::test(start_paused = true)]
434 async fn test_rdebounce_batch_force_flushes_at_cap() {
435 let source = async_stream::stream! {
438 for value in 1u32..=5 {
439 yield value;
440 }
441 };
442 let result: Vec<Vec<u32>> = source
443 .rdebounce_batch(Duration::from_millis(100), 2)
444 .collect()
445 .await;
446 assert_eq!(result, vec![vec![1u32, 2], vec![3u32, 4], vec![5u32]]);
447 }
448
449 #[tokio::test]
452 async fn test_rdebounce_batch_consumer_drop_ends_pipeline() {
453 let (tx, rx) = tokio::sync::mpsc::channel::<u32>(16);
454 let batched =
455 crate::source::from_channel(rx).rdebounce_batch(Duration::from_millis(100), 1024);
456 drop(batched); assert!(
459 tx.send(0).await.is_err(),
460 "dropping the debounced stream must close the source"
461 );
462 }
463
464 #[tokio::test]
468 async fn test_rthrottle_drops_fast_items() {
469 tokio::time::pause();
470
471 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
472 let handle = tokio::spawn(async move {
473 stream
474 .rthrottle(Duration::from_millis(100))
475 .collect::<Vec<_>>()
476 .await
477 });
478
479 tokio::time::advance(Duration::from_millis(600)).await;
480 let result = handle.await.unwrap();
481
482 assert!(!result.is_empty());
486 assert_eq!(result[0], 1u32);
487 assert!(result.len() < 5);
489 }
490
491 #[tokio::test]
494 async fn test_rthrottle_huge_interval_emits_first_without_panic() {
495 tokio::time::pause();
496
497 let stream = from_slice(vec![1u32, 2, 3]);
498 let result = stream
499 .rthrottle(Duration::from_secs(u64::MAX))
500 .collect::<Vec<_>>()
501 .await;
502
503 assert_eq!(result, vec![1u32]);
504 }
505
506 #[tokio::test]
510 async fn test_rtumbling_window_emits_on_timer() {
511 tokio::time::pause();
512
513 let (tx, rx) = tokio::sync::mpsc::channel::<u32>(16);
514 let stream = crate::source::from_channel(rx);
515
516 let handle = tokio::spawn(async move {
517 stream
518 .rtumbling_window(Duration::from_millis(100), 128)
519 .collect::<Vec<_>>()
520 .await
521 });
522
523 tx.send(10).await.unwrap();
525 tx.send(20).await.unwrap();
526 tx.send(30).await.unwrap();
527 drop(tx);
528
529 tokio::time::advance(Duration::from_millis(200)).await;
530 let windows = handle.await.unwrap();
531
532 assert!(!windows.is_empty());
533 let all_items: Vec<u32> = windows.into_iter().flatten().collect();
534 let mut sorted = all_items;
535 sorted.sort_unstable();
536 assert_eq!(sorted, vec![10, 20, 30]);
537 }
538
539 #[tokio::test]
541 async fn test_rtumbling_window_empty_input() {
542 tokio::time::pause();
543
544 let stream = from_slice::<u32>(vec![]);
545 let handle = tokio::spawn(async move {
546 stream
547 .rtumbling_window(Duration::from_millis(100), 128)
548 .collect::<Vec<_>>()
549 .await
550 });
551
552 tokio::time::advance(Duration::from_millis(200)).await;
553 let windows = handle.await.unwrap();
554 assert!(windows.is_empty());
555 }
556
557 #[tokio::test]
558 async fn test_rdistinct_filters_duplicates() {
559 let stream = from_slice(vec![1u32, 2, 2, 3, 1, 4]);
560 let values: Vec<u32> = stream.rdistinct().collect().await;
561 assert_eq!(values, vec![1, 2, 3, 4]);
562 }
563
564 #[tokio::test]
565 async fn test_rtake_and_rskip_compose() {
566 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
567 let values: Vec<u32> = stream.rskip(1).rtake(3).collect().await;
568 assert_eq!(values, vec![2, 3, 4]);
569 }
570
571 #[tokio::test]
572 async fn test_rpartition_splits_stream() {
573 let stream = from_slice(vec![1u32, 2, 3, 4, 5, 6]);
574 let (even_stream, odd_stream) = stream.rpartition(|value| value % 2 == 0);
575 let (evens, odds) = tokio::join!(
576 even_stream.collect::<Vec<_>>(),
577 odd_stream.collect::<Vec<_>>()
578 );
579 assert_eq!(evens, vec![2, 4, 6]);
580 assert_eq!(odds, vec![1, 3, 5]);
581 }
582
583 #[tokio::test]
584 async fn test_rsliding_window_emits_overlapping_windows() {
585 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
586 let windows: Vec<Vec<u32>> = stream.rsliding_window(3, 1).collect().await;
587 assert_eq!(windows, vec![vec![1, 2, 3], vec![2, 3, 4], vec![3, 4, 5]]);
588 }
589}