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]
385 async fn test_rthrottle_drops_fast_items() {
386 tokio::time::pause();
387
388 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
389 let handle = tokio::spawn(async move {
390 stream
391 .rthrottle(Duration::from_millis(100))
392 .collect::<Vec<_>>()
393 .await
394 });
395
396 tokio::time::advance(Duration::from_millis(600)).await;
397 let result = handle.await.unwrap();
398
399 assert!(!result.is_empty());
403 assert_eq!(result[0], 1u32);
404 assert!(result.len() < 5);
406 }
407
408 #[tokio::test]
412 async fn test_rtumbling_window_emits_on_timer() {
413 tokio::time::pause();
414
415 let (tx, rx) = tokio::sync::mpsc::channel::<u32>(16);
416 let stream = crate::source::from_channel(rx);
417
418 let handle = tokio::spawn(async move {
419 stream
420 .rtumbling_window(Duration::from_millis(100), 128)
421 .collect::<Vec<_>>()
422 .await
423 });
424
425 tx.send(10).await.unwrap();
427 tx.send(20).await.unwrap();
428 tx.send(30).await.unwrap();
429 drop(tx);
430
431 tokio::time::advance(Duration::from_millis(200)).await;
432 let windows = handle.await.unwrap();
433
434 assert!(!windows.is_empty());
435 let all_items: Vec<u32> = windows.into_iter().flatten().collect();
436 let mut sorted = all_items;
437 sorted.sort_unstable();
438 assert_eq!(sorted, vec![10, 20, 30]);
439 }
440
441 #[tokio::test]
443 async fn test_rtumbling_window_empty_input() {
444 tokio::time::pause();
445
446 let stream = from_slice::<u32>(vec![]);
447 let handle = tokio::spawn(async move {
448 stream
449 .rtumbling_window(Duration::from_millis(100), 128)
450 .collect::<Vec<_>>()
451 .await
452 });
453
454 tokio::time::advance(Duration::from_millis(200)).await;
455 let windows = handle.await.unwrap();
456 assert!(windows.is_empty());
457 }
458
459 #[tokio::test]
460 async fn test_rdistinct_filters_duplicates() {
461 let stream = from_slice(vec![1u32, 2, 2, 3, 1, 4]);
462 let values: Vec<u32> = stream.rdistinct().collect().await;
463 assert_eq!(values, vec![1, 2, 3, 4]);
464 }
465
466 #[tokio::test]
467 async fn test_rtake_and_rskip_compose() {
468 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
469 let values: Vec<u32> = stream.rskip(1).rtake(3).collect().await;
470 assert_eq!(values, vec![2, 3, 4]);
471 }
472
473 #[tokio::test]
474 async fn test_rpartition_splits_stream() {
475 let stream = from_slice(vec![1u32, 2, 3, 4, 5, 6]);
476 let (even_stream, odd_stream) = stream.rpartition(|value| value % 2 == 0);
477 let (evens, odds) = tokio::join!(
478 even_stream.collect::<Vec<_>>(),
479 odd_stream.collect::<Vec<_>>()
480 );
481 assert_eq!(evens, vec![2, 4, 6]);
482 assert_eq!(odds, vec![1, 3, 5]);
483 }
484
485 #[tokio::test]
486 async fn test_rsliding_window_emits_overlapping_windows() {
487 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
488 let windows: Vec<Vec<u32>> = stream.rsliding_window(3, 1).collect().await;
489 assert_eq!(windows, vec![vec![1, 2, 3], vec![2, 3, 4], vec![3, 4, 5]]);
490 }
491}