1#![warn(missing_docs)]
4
5pub mod broadcaster;
7pub mod executor;
9pub mod ext;
11pub mod operators;
13pub mod sink;
15pub mod source;
17
18pub use broadcaster::{BroadcastStream, Broadcaster, DEFAULT_BROADCAST_BUFFER};
19pub use executor::{Step, StepExecutor, StepStatus};
20pub use ext::RskitStreamExt;
21pub use operators::combine::{concat, merge};
22pub use sink::{collect, drain, for_each};
23pub use source::{from_channel, from_fn, from_slice};
24
25#[cfg(test)]
26mod tests {
27 use parking_lot::Mutex;
28 use std::sync::Arc;
29 use std::time::Duration;
30
31 use futures::StreamExt as _;
32
33 use crate::{RskitStreamExt, from_fn, from_slice, merge};
34
35 #[tokio::test]
39 async fn test_from_slice_yields_all_in_order() {
40 let items = vec![1u32, 2, 3, 4, 5];
41 let stream = from_slice(items.clone());
42 let collected: Vec<u32> = stream.collect().await;
43 assert_eq!(collected, items);
44 }
45
46 #[tokio::test]
48 async fn test_from_slice_empty() {
49 let stream = from_slice::<u32>(vec![]);
50 let collected: Vec<u32> = stream.collect().await;
51 assert!(collected.is_empty());
52 }
53
54 #[tokio::test]
56 async fn test_from_fn_yields_until_none() {
57 let counter = Arc::new(Mutex::new(0u32));
58 let c = counter.clone();
59 let stream = from_fn(move || {
60 let c = c.clone();
61 async move {
62 let mut n = c.lock();
63 if *n < 5 {
64 let val = *n;
65 *n += 1;
66 Some(val)
67 } else {
68 None
69 }
70 }
71 });
72 let collected: Vec<u32> = stream.collect().await;
73 assert_eq!(collected, vec![0, 1, 2, 3, 4]);
74 }
75
76 #[tokio::test]
78 async fn test_from_fn_immediate_none() {
79 let stream = from_fn(|| async { None::<u32> });
80 let collected: Vec<u32> = stream.collect().await;
81 assert!(collected.is_empty());
82 }
83
84 #[tokio::test]
86 async fn test_merge_set_equality() {
87 let s1 = from_slice(vec![1u32, 3, 5]);
88 let s2 = from_slice(vec![2u32, 4, 6]);
89 let mut combined: Vec<u32> = merge(s1, s2).collect().await;
90 combined.sort();
91 assert_eq!(combined, vec![1, 2, 3, 4, 5, 6]);
92 }
93
94 #[tokio::test]
96 async fn test_merge_both_empty() {
97 let s1 = from_slice::<u32>(vec![]);
98 let s2 = from_slice::<u32>(vec![]);
99 let combined: Vec<u32> = merge(s1, s2).collect().await;
100 assert!(combined.is_empty());
101 }
102
103 #[tokio::test]
107 async fn test_rmap_transforms_items() {
108 let stream = from_slice(vec![1u32, 2, 3]);
109 let results: Vec<_> = stream
110 .rmap(|x| async move { Ok::<u32, rskit_errors::AppError>(x * 10) })
111 .collect()
112 .await;
113 let values: Vec<u32> = results.into_iter().map(|r| r.unwrap()).collect();
114 assert_eq!(values, vec![10, 20, 30]);
115 }
116
117 #[tokio::test]
119 async fn test_rmap_propagates_error() {
120 let stream = from_slice(vec![1u32, 2, 3]);
121 let results: Vec<_> = stream
122 .rmap(|x| async move {
123 if x == 2 {
124 Err(rskit_errors::AppError::new(
125 rskit_errors::ErrorCode::Internal,
126 "bad item",
127 ))
128 } else {
129 Ok(x)
130 }
131 })
132 .collect()
133 .await;
134 assert!(results[0].is_ok());
135 assert!(results[1].is_err());
136 assert!(results[2].is_ok());
137 }
138
139 #[tokio::test]
143 async fn test_rfilter_keeps_matching_items() {
144 let stream = from_slice(vec![1u32, 2, 3, 4, 5, 6]);
145 let evens: Vec<u32> = stream.rfilter(|x| x % 2 == 0).collect().await;
146 assert_eq!(evens, vec![2, 4, 6]);
147 }
148
149 #[tokio::test]
151 async fn test_rfilter_no_match_yields_empty() {
152 let stream = from_slice(vec![1u32, 3, 5]);
153 let result: Vec<u32> = stream.rfilter(|x| x % 2 == 0).collect().await;
154 assert!(result.is_empty());
155 }
156
157 #[tokio::test]
161 async fn test_rtap_calls_side_effect_and_passes_through() {
162 let seen = Arc::new(Mutex::new(Vec::<u32>::new()));
163 let seen_clone = seen.clone();
164
165 let stream = from_slice(vec![10u32, 20, 30]);
166 let output: Vec<u32> = stream
167 .rtap(move |x| {
168 let seen = seen_clone.clone();
169 let val = *x;
170 async move {
171 seen.lock().push(val);
172 }
173 })
174 .collect()
175 .await;
176
177 assert_eq!(output, vec![10, 20, 30]);
178 assert_eq!(*seen.lock(), vec![10, 20, 30]);
179 }
180
181 #[tokio::test]
185 async fn test_rreduce_folds_to_single_value() {
186 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
187 let sum = stream.rreduce(0u32, |acc, x| acc + x).await;
188 assert_eq!(sum, 15);
189 }
190
191 #[tokio::test]
193 async fn test_rreduce_empty_stream_returns_init() {
194 let stream = from_slice::<u32>(vec![]);
195 let result = stream.rreduce(42u32, |acc, x| acc + x).await;
196 assert_eq!(result, 42);
197 }
198
199 #[tokio::test]
203 async fn test_rparallel_collects_all_results() {
204 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
205 let mut results: Vec<u32> = stream
206 .rparallel(
207 3,
208 |x| async move { Ok::<u32, rskit_errors::AppError>(x * 2) },
209 )
210 .collect::<Vec<_>>()
211 .await
212 .into_iter()
213 .map(|r| r.unwrap())
214 .collect();
215 results.sort();
216 assert_eq!(results, vec![2, 4, 6, 8, 10]);
217 }
218
219 #[tokio::test]
221 async fn test_rparallel_propagates_errors() {
222 let stream = from_slice(vec![1u32, 2, 3]);
223 let results: Vec<_> = stream
224 .rparallel(2, |x| async move {
225 if x == 2 {
226 Err(rskit_errors::AppError::new(
227 rskit_errors::ErrorCode::Internal,
228 "parallel error",
229 ))
230 } else {
231 Ok(x)
232 }
233 })
234 .collect()
235 .await;
236 let errors: Vec<_> = results.iter().filter(|r| r.is_err()).collect();
237 assert_eq!(errors.len(), 1);
238 }
239
240 #[tokio::test]
247 async fn test_rfan_out_applies_all_functions() {
248 let add_one = |x: u32| std::future::ready(Ok::<u32, rskit_errors::AppError>(x + 1));
250 let mul_two = |x: u32| std::future::ready(Ok::<u32, rskit_errors::AppError>(x * 2));
251
252 let stream_a = from_slice(vec![5u32, 10u32]);
254 let res_a: Vec<_> = stream_a.rfan_out(1, vec![add_one]).collect().await;
255 let res_a: Vec<Vec<_>> = res_a.into_iter().map(Result::unwrap).collect();
256 assert_eq!(res_a[0][0], 6u32);
257 assert_eq!(res_a[1][0], 11u32);
258
259 let stream_b = from_slice(vec![5u32, 10u32]);
261 let res_b: Vec<_> = stream_b.rfan_out(2, vec![add_one, mul_two]).collect().await;
262 let res_b: Vec<Vec<_>> = res_b.into_iter().map(Result::unwrap).collect();
263 assert_eq!(res_b[0][0], 6u32);
265 assert_eq!(res_b[0][1], 10u32);
266 assert_eq!(res_b[1][0], 11u32);
268 assert_eq!(res_b[1][1], 20u32);
269 }
270
271 #[tokio::test]
273 async fn test_rfan_out_single_function() {
274 let stream = from_slice(vec![3u32, 7u32]);
275 let f = |x: u32| std::future::ready(Ok::<u32, rskit_errors::AppError>(x + 100));
277 let results: Vec<_> = stream.rfan_out(1, vec![f]).collect().await;
278 let results: Vec<Vec<_>> = results.into_iter().map(Result::unwrap).collect();
279 assert_eq!(results.len(), 2);
280 assert_eq!(results[0][0], 103u32);
281 assert_eq!(results[1][0], 107u32);
282 }
283
284 #[tokio::test]
288 async fn test_rbatch_exact_size_batches() {
289 tokio::time::pause();
290
291 let stream = from_slice(vec![1u32, 2, 3, 4, 5, 6]);
292 let handle = tokio::spawn(async move {
293 stream
294 .rbatch(3, Duration::from_millis(500))
295 .collect::<Vec<_>>()
296 .await
297 });
298
299 tokio::time::advance(Duration::from_millis(600)).await;
300 let batches = handle.await.unwrap();
301
302 assert_eq!(batches.len(), 2);
303 assert_eq!(batches[0], vec![1, 2, 3]);
304 assert_eq!(batches[1], vec![4, 5, 6]);
305 }
306
307 #[tokio::test]
309 async fn test_rbatch_partial_flush_on_timeout() {
310 tokio::time::pause();
311
312 let (tx, rx) = tokio::sync::mpsc::channel::<u32>(16);
314 let stream = crate::source::from_channel(rx);
315
316 let handle = tokio::spawn(async move {
317 stream
318 .rbatch(10, Duration::from_millis(100))
319 .collect::<Vec<_>>()
320 .await
321 });
322
323 tx.send(1).await.unwrap();
325 tx.send(2).await.unwrap();
326 drop(tx); tokio::time::advance(Duration::from_millis(200)).await;
329 let batches = handle.await.unwrap();
330
331 assert_eq!(batches.len(), 1);
332 assert_eq!(batches[0], vec![1, 2]);
333 }
334
335 #[tokio::test]
339 async fn test_rdebounce_emits_last_item() {
340 tokio::time::pause();
341
342 let (tx, rx) = tokio::sync::mpsc::channel::<u32>(16);
343 let stream = crate::source::from_channel(rx);
344
345 let handle = tokio::spawn(async move {
346 stream
347 .rdebounce(Duration::from_millis(100))
348 .collect::<Vec<_>>()
349 .await
350 });
351
352 tx.send(1).await.unwrap();
354 tx.send(2).await.unwrap();
355 tx.send(3).await.unwrap();
356 drop(tx);
357
358 tokio::time::advance(Duration::from_millis(200)).await;
359 let result = handle.await.unwrap();
360
361 assert!(!result.is_empty());
363 assert_eq!(*result.last().unwrap(), 3u32);
364 }
365
366 #[tokio::test]
370 async fn test_rthrottle_drops_fast_items() {
371 tokio::time::pause();
372
373 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
374 let handle = tokio::spawn(async move {
375 stream
376 .rthrottle(Duration::from_millis(100))
377 .collect::<Vec<_>>()
378 .await
379 });
380
381 tokio::time::advance(Duration::from_millis(600)).await;
382 let result = handle.await.unwrap();
383
384 assert!(!result.is_empty());
388 assert_eq!(result[0], 1u32);
389 assert!(result.len() < 5);
391 }
392
393 #[tokio::test]
397 async fn test_rtumbling_window_emits_on_timer() {
398 tokio::time::pause();
399
400 let (tx, rx) = tokio::sync::mpsc::channel::<u32>(16);
401 let stream = crate::source::from_channel(rx);
402
403 let handle = tokio::spawn(async move {
404 stream
405 .rtumbling_window(Duration::from_millis(100), 128)
406 .collect::<Vec<_>>()
407 .await
408 });
409
410 tx.send(10).await.unwrap();
412 tx.send(20).await.unwrap();
413 tx.send(30).await.unwrap();
414 drop(tx);
415
416 tokio::time::advance(Duration::from_millis(200)).await;
417 let windows = handle.await.unwrap();
418
419 assert!(!windows.is_empty());
420 let all_items: Vec<u32> = windows.into_iter().flatten().collect();
421 let mut sorted = all_items.clone();
422 sorted.sort();
423 assert_eq!(sorted, vec![10, 20, 30]);
424 }
425
426 #[tokio::test]
428 async fn test_rtumbling_window_empty_input() {
429 tokio::time::pause();
430
431 let stream = from_slice::<u32>(vec![]);
432 let handle = tokio::spawn(async move {
433 stream
434 .rtumbling_window(Duration::from_millis(100), 128)
435 .collect::<Vec<_>>()
436 .await
437 });
438
439 tokio::time::advance(Duration::from_millis(200)).await;
440 let windows = handle.await.unwrap();
441 assert!(windows.is_empty());
442 }
443
444 #[tokio::test]
445 async fn test_rdistinct_filters_duplicates() {
446 let stream = from_slice(vec![1u32, 2, 2, 3, 1, 4]);
447 let values: Vec<u32> = stream.rdistinct().collect().await;
448 assert_eq!(values, vec![1, 2, 3, 4]);
449 }
450
451 #[tokio::test]
452 async fn test_rtake_and_rskip_compose() {
453 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
454 let values: Vec<u32> = stream.rskip(1).rtake(3).collect().await;
455 assert_eq!(values, vec![2, 3, 4]);
456 }
457
458 #[tokio::test]
459 async fn test_rpartition_splits_stream() {
460 let stream = from_slice(vec![1u32, 2, 3, 4, 5, 6]);
461 let (even_stream, odd_stream) = stream.rpartition(|value| value % 2 == 0);
462 let (evens, odds) = tokio::join!(
463 even_stream.collect::<Vec<_>>(),
464 odd_stream.collect::<Vec<_>>()
465 );
466 assert_eq!(evens, vec![2, 4, 6]);
467 assert_eq!(odds, vec![1, 3, 5]);
468 }
469
470 #[tokio::test]
471 async fn test_rsliding_window_emits_overlapping_windows() {
472 let stream = from_slice(vec![1u32, 2, 3, 4, 5]);
473 let windows: Vec<Vec<u32>> = stream.rsliding_window(3, 1).collect().await;
474 assert_eq!(windows, vec![vec![1, 2, 3], vec![2, 3, 4], vec![3, 4, 5]]);
475 }
476}