1use crate::aggregation::Aggregator;
12use crate::core::{EventKey, EventTimeExtractor, KeyExtractor};
13use crate::error::Result;
14use crate::watermark::Watermark;
15use crate::window::{Window, WindowAssigner};
16use async_trait::async_trait;
17use serde::Serialize;
18use std::fmt::Debug;
19use std::sync::Arc;
20
21#[derive(Debug, Clone)]
23pub struct OperatorContext {
24 pub watermark: Watermark,
26
27 pub partition: u32,
29
30 pub processing_time: i64,
32}
33
34impl OperatorContext {
35 pub fn new(watermark: Watermark, partition: u32) -> Self {
37 Self {
38 watermark,
39 partition,
40 processing_time: chrono::Utc::now().timestamp_millis(),
41 }
42 }
43}
44
45#[async_trait]
50pub trait StreamOperator: Send + Sync + Debug {
51 async fn process(
60 &self,
61 input: Vec<u8>,
62 ctx: &OperatorContext,
63 ) -> Result<Vec<Vec<u8>>>;
64
65 fn name(&self) -> &str;
67
68 async fn open(&mut self) -> Result<()> {
70 Ok(())
71 }
72
73 async fn close(&mut self) -> Result<()> {
75 Ok(())
76 }
77}
78
79#[derive(Clone)]
90pub struct MapOperator<F, T, U>
91where
92 F: Fn(T) -> U + Send + Sync + Clone,
93 T: Send + Sync + Clone,
94 U: Send + Sync + Clone,
95{
96 name: String,
97 func: F,
98 _phantom: std::marker::PhantomData<(T, U)>,
99}
100
101impl<F, T, U> MapOperator<F, T, U>
102where
103 F: Fn(T) -> U + Send + Sync + Clone,
104 T: Send + Sync + Clone + serde::de::DeserializeOwned,
105 U: Send + Sync + Clone + Serialize,
106{
107 pub fn new<S: Into<String>>(name: S, func: F) -> Self {
109 Self {
110 name: name.into(),
111 func,
112 _phantom: std::marker::PhantomData,
113 }
114 }
115}
116
117impl<F, T, U> Debug for MapOperator<F, T, U>
118where
119 F: Fn(T) -> U + Send + Sync + Clone,
120 T: Send + Sync + Clone,
121 U: Send + Sync + Clone,
122{
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 f.debug_struct("MapOperator")
125 .field("name", &self.name)
126 .finish()
127 }
128}
129
130#[async_trait]
131impl<F, T, U> StreamOperator for MapOperator<F, T, U>
132where
133 F: Fn(T) -> U + Send + Sync + Clone,
134 T: Send + Sync + Clone + serde::de::DeserializeOwned,
135 U: Send + Sync + Clone + Serialize,
136{
137 async fn process(
138 &self,
139 input: Vec<u8>,
140 _ctx: &OperatorContext,
141 ) -> Result<Vec<Vec<u8>>> {
142 let value: T = bincode::deserialize(&input)?;
143 let result = (self.func)(value);
144 let serialized = bincode::serialize(&result)?;
145 Ok(vec![serialized])
146 }
147
148 fn name(&self) -> &str {
149 &self.name
150 }
151}
152
153#[derive(Clone)]
164pub struct FilterOperator<F, T>
165where
166 F: Fn(&T) -> bool + Send + Sync + Clone,
167 T: Send + Sync + Clone,
168{
169 name: String,
170 predicate: F,
171 _phantom: std::marker::PhantomData<T>,
172}
173
174impl<F, T> FilterOperator<F, T>
175where
176 F: Fn(&T) -> bool + Send + Sync + Clone,
177 T: Send + Sync + Clone + serde::de::DeserializeOwned,
178{
179 pub fn new<S: Into<String>>(name: S, predicate: F) -> Self {
181 Self {
182 name: name.into(),
183 predicate,
184 _phantom: std::marker::PhantomData,
185 }
186 }
187}
188
189impl<F, T> Debug for FilterOperator<F, T>
190where
191 F: Fn(&T) -> bool + Send + Sync + Clone,
192 T: Send + Sync + Clone,
193{
194 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 f.debug_struct("FilterOperator")
196 .field("name", &self.name)
197 .finish()
198 }
199}
200
201#[async_trait]
202impl<F, T> StreamOperator for FilterOperator<F, T>
203where
204 F: Fn(&T) -> bool + Send + Sync + Clone,
205 T: Send + Sync + Clone + serde::de::DeserializeOwned + Serialize,
206{
207 async fn process(
208 &self,
209 input: Vec<u8>,
210 _ctx: &OperatorContext,
211 ) -> Result<Vec<Vec<u8>>> {
212 let value: T = bincode::deserialize(&input)?;
213 if (self.predicate)(&value) {
214 Ok(vec![input])
215 } else {
216 Ok(vec![])
217 }
218 }
219
220 fn name(&self) -> &str {
221 &self.name
222 }
223}
224
225#[derive(Clone)]
238pub struct FlatMapOperator<F, T, U>
239where
240 F: Fn(T) -> Vec<U> + Send + Sync + Clone,
241 T: Send + Sync + Clone,
242 U: Send + Sync + Clone,
243{
244 name: String,
245 func: F,
246 _phantom: std::marker::PhantomData<(T, U)>,
247}
248
249impl<F, T, U> FlatMapOperator<F, T, U>
250where
251 F: Fn(T) -> Vec<U> + Send + Sync + Clone,
252 T: Send + Sync + Clone + serde::de::DeserializeOwned,
253 U: Send + Sync + Clone + Serialize,
254{
255 pub fn new<S: Into<String>>(name: S, func: F) -> Self {
257 Self {
258 name: name.into(),
259 func,
260 _phantom: std::marker::PhantomData,
261 }
262 }
263}
264
265impl<F, T, U> Debug for FlatMapOperator<F, T, U>
266where
267 F: Fn(T) -> Vec<U> + Send + Sync + Clone,
268 T: Send + Sync + Clone,
269 U: Send + Sync + Clone,
270{
271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272 f.debug_struct("FlatMapOperator")
273 .field("name", &self.name)
274 .finish()
275 }
276}
277
278#[async_trait]
279impl<F, T, U> StreamOperator for FlatMapOperator<F, T, U>
280where
281 F: Fn(T) -> Vec<U> + Send + Sync + Clone,
282 T: Send + Sync + Clone + serde::de::DeserializeOwned,
283 U: Send + Sync + Clone + Serialize,
284{
285 async fn process(
286 &self,
287 input: Vec<u8>,
288 _ctx: &OperatorContext,
289 ) -> Result<Vec<Vec<u8>>> {
290 let value: T = bincode::deserialize(&input)?;
291 let results = (self.func)(value);
292 let mut serialized = Vec::new();
293 for result in results {
294 serialized.push(bincode::serialize(&result)?);
295 }
296 Ok(serialized)
297 }
298
299 fn name(&self) -> &str {
300 &self.name
301 }
302}
303
304pub struct KeyByOperator<T, E>
323where
324 T: Send + Sync + Clone,
325 E: KeyExtractor<T> + Send + Sync,
326{
327 name: String,
328 extractor: E,
329 _phantom: std::marker::PhantomData<T>,
330}
331
332impl<T, E> KeyByOperator<T, E>
333where
334 T: Send + Sync + Clone + serde::de::DeserializeOwned,
335 E: KeyExtractor<T> + Send + Sync,
336{
337 pub fn new<S: Into<String>>(name: S, extractor: E) -> Self {
339 Self {
340 name: name.into(),
341 extractor,
342 _phantom: std::marker::PhantomData,
343 }
344 }
345
346 pub fn extract_key(&self, event: &T) -> EventKey {
348 self.extractor.extract_key(event)
349 }
350}
351
352impl<T, E> Debug for KeyByOperator<T, E>
353where
354 T: Send + Sync + Clone,
355 E: KeyExtractor<T> + Send + Sync,
356{
357 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358 f.debug_struct("KeyByOperator")
359 .field("name", &self.name)
360 .finish()
361 }
362}
363
364#[async_trait]
365impl<T, E> StreamOperator for KeyByOperator<T, E>
366where
367 T: Send + Sync + Clone + serde::de::DeserializeOwned + Serialize,
368 E: KeyExtractor<T> + Send + Sync,
369{
370 async fn process(
371 &self,
372 input: Vec<u8>,
373 _ctx: &OperatorContext,
374 ) -> Result<Vec<Vec<u8>>> {
375 Ok(vec![input])
378 }
379
380 fn name(&self) -> &str {
381 &self.name
382 }
383}
384
385pub struct WindowOperator<T, A, E>
407where
408 T: Send + Sync + Clone,
409 A: WindowAssigner + Send + Sync,
410 E: EventTimeExtractor<T> + Send + Sync,
411{
412 name: String,
413 assigner: A,
414 time_extractor: E,
415 _phantom: std::marker::PhantomData<T>,
416}
417
418impl<T, A, E> WindowOperator<T, A, E>
419where
420 T: Send + Sync + Clone + serde::de::DeserializeOwned,
421 A: WindowAssigner + Send + Sync,
422 E: EventTimeExtractor<T> + Send + Sync,
423{
424 pub fn new<S: Into<String>>(name: S, assigner: A, time_extractor: E) -> Self {
426 Self {
427 name: name.into(),
428 assigner,
429 time_extractor,
430 _phantom: std::marker::PhantomData,
431 }
432 }
433
434 pub fn assign_windows(&self, event: &T, _key: &str) -> Vec<Window> {
436 let timestamp = self.time_extractor.extract_event_time(event);
437 self.assigner.assign_windows(timestamp)
438 }
439}
440
441impl<T, A, E> Debug for WindowOperator<T, A, E>
442where
443 T: Send + Sync + Clone,
444 A: WindowAssigner + Send + Sync,
445 E: EventTimeExtractor<T> + Send + Sync,
446{
447 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
448 f.debug_struct("WindowOperator")
449 .field("name", &self.name)
450 .finish()
451 }
452}
453
454#[async_trait]
455impl<T, A, E> StreamOperator for WindowOperator<T, A, E>
456where
457 T: Send + Sync + Clone + serde::de::DeserializeOwned + Serialize,
458 A: WindowAssigner + Send + Sync,
459 E: EventTimeExtractor<T> + Send + Sync,
460{
461 async fn process(
462 &self,
463 input: Vec<u8>,
464 _ctx: &OperatorContext,
465 ) -> Result<Vec<Vec<u8>>> {
466 Ok(vec![input])
469 }
470
471 fn name(&self) -> &str {
472 &self.name
473 }
474}
475
476pub struct AggregateOperator<T, A>
488where
489 T: Send + Sync + Clone,
490 A: Aggregator<Input = T> + Send + Sync,
491{
492 name: String,
493 aggregator: Arc<std::sync::Mutex<A>>,
494 _phantom: std::marker::PhantomData<T>,
495}
496
497impl<T, A> AggregateOperator<T, A>
498where
499 T: Send + Sync + Clone,
500 A: Aggregator<Input = T> + Send + Sync,
501{
502 pub fn new<S: Into<String>>(name: S, aggregator: A) -> Self {
504 Self {
505 name: name.into(),
506 aggregator: Arc::new(std::sync::Mutex::new(aggregator)),
507 _phantom: std::marker::PhantomData,
508 }
509 }
510
511 pub fn aggregator(&self) -> &Arc<std::sync::Mutex<A>> {
513 &self.aggregator
514 }
515}
516
517impl<T, A> Debug for AggregateOperator<T, A>
518where
519 T: Send + Sync + Clone,
520 A: Aggregator<Input = T> + Send + Sync,
521{
522 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
523 f.debug_struct("AggregateOperator")
524 .field("name", &self.name)
525 .finish()
526 }
527}
528
529impl<T, A> Clone for AggregateOperator<T, A>
530where
531 T: Send + Sync + Clone,
532 A: Aggregator<Input = T> + Send + Sync,
533{
534 fn clone(&self) -> Self {
535 Self {
536 name: self.name.clone(),
537 aggregator: Arc::clone(&self.aggregator),
538 _phantom: std::marker::PhantomData,
539 }
540 }
541}
542
543#[async_trait]
544impl<T, A> StreamOperator for AggregateOperator<T, A>
545where
546 T: Send + Sync + Clone + serde::de::DeserializeOwned + Serialize,
547 A: Aggregator<Input = T, Output = T> + Send + Sync,
548{
549 async fn process(
550 &self,
551 input: Vec<u8>,
552 _ctx: &OperatorContext,
553 ) -> Result<Vec<Vec<u8>>> {
554 let value: T = bincode::deserialize(&input)?;
555 let mut agg = self.aggregator.lock().unwrap();
556 agg.update(value)?;
557 let result = agg.finalize()?;
558 let serialized = bincode::serialize(&result)?;
559 Ok(vec![serialized])
560 }
561
562 fn name(&self) -> &str {
563 &self.name
564 }
565}
566
567pub struct DeduplicationOperator {
591 name: String,
592 config: crate::config::DeduplicationConfig,
593 seen_hashes: Arc<std::sync::RwLock<std::collections::HashMap<String, std::time::Instant>>>,
596}
597
598impl DeduplicationOperator {
599 pub fn new<S: Into<String>>(name: S, config: crate::config::DeduplicationConfig) -> Self {
601 Self {
602 name: name.into(),
603 config,
604 seen_hashes: Arc::new(std::sync::RwLock::new(std::collections::HashMap::new())),
605 }
606 }
607
608 fn compute_signature(&self, data: &[u8], _strategy: crate::config::DeduplicationStrategy) -> String {
610 use sha2::{Digest, Sha256};
613 let mut hasher = Sha256::new();
614 hasher.update(data);
615 format!("{:x}", hasher.finalize())
616 }
617
618 fn is_duplicate(&self, signature: &str) -> bool {
620 let seen = self.seen_hashes.read().unwrap();
621 if let Some(timestamp) = seen.get(signature) {
622 timestamp.elapsed() < self.config.ttl
624 } else {
625 false
626 }
627 }
628
629 fn mark_seen(&self, signature: String) {
631 let mut seen = self.seen_hashes.write().unwrap();
632 seen.insert(signature, std::time::Instant::now());
633
634 if seen.len() > 10000 {
637 let ttl = self.config.ttl;
638 seen.retain(|_, timestamp| timestamp.elapsed() < ttl);
639 }
640 }
641
642 pub fn stats(&self) -> DeduplicationStats {
644 let seen = self.seen_hashes.read().unwrap();
645 DeduplicationStats {
646 cached_signatures: seen.len(),
647 enabled: self.config.enabled,
648 }
649 }
650}
651
652impl Debug for DeduplicationOperator {
653 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
654 f.debug_struct("DeduplicationOperator")
655 .field("name", &self.name)
656 .field("enabled", &self.config.enabled)
657 .field("strategy", &self.config.strategy)
658 .finish()
659 }
660}
661
662impl Clone for DeduplicationOperator {
663 fn clone(&self) -> Self {
664 Self {
665 name: self.name.clone(),
666 config: self.config.clone(),
667 seen_hashes: Arc::clone(&self.seen_hashes),
668 }
669 }
670}
671
672#[async_trait]
673impl StreamOperator for DeduplicationOperator {
674 async fn process(
675 &self,
676 input: Vec<u8>,
677 _ctx: &OperatorContext,
678 ) -> Result<Vec<Vec<u8>>> {
679 if !self.config.enabled {
681 return Ok(vec![input]);
682 }
683
684 let signature = self.compute_signature(&input, self.config.strategy);
686
687 if self.is_duplicate(&signature) {
689 Ok(vec![])
691 } else {
692 self.mark_seen(signature);
694 Ok(vec![input])
695 }
696 }
697
698 fn name(&self) -> &str {
699 &self.name
700 }
701}
702
703#[derive(Debug, Clone)]
705pub struct DeduplicationStats {
706 pub cached_signatures: usize,
708 pub enabled: bool,
710}
711
712pub struct OperatorChain {
714 operators: Vec<Arc<dyn StreamOperator>>,
715}
716
717impl OperatorChain {
718 pub fn new() -> Self {
720 Self {
721 operators: Vec::new(),
722 }
723 }
724
725 pub fn add<O: StreamOperator + 'static>(&mut self, operator: O) {
727 self.operators.push(Arc::new(operator));
728 }
729
730 pub fn operators(&self) -> &[Arc<dyn StreamOperator>] {
732 &self.operators
733 }
734
735 pub async fn process(
737 &self,
738 mut input: Vec<Vec<u8>>,
739 ctx: &OperatorContext,
740 ) -> Result<Vec<Vec<u8>>> {
741 for operator in &self.operators {
742 let mut output = Vec::new();
743 for item in input {
744 let results = operator.process(item, ctx).await?;
745 output.extend(results);
746 }
747 input = output;
748 }
749 Ok(input)
750 }
751
752 pub fn len(&self) -> usize {
754 self.operators.len()
755 }
756
757 pub fn is_empty(&self) -> bool {
759 self.operators.is_empty()
760 }
761}
762
763impl Default for OperatorChain {
764 fn default() -> Self {
765 Self::new()
766 }
767}
768
769impl Debug for OperatorChain {
770 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
771 f.debug_struct("OperatorChain")
772 .field("operator_count", &self.operators.len())
773 .finish()
774 }
775}
776
777#[cfg(test)]
778mod tests {
779 use super::*;
780 use crate::aggregation::SumAggregator;
781 use crate::core::{EventKey, FeedbackEventKeyExtractor, FeedbackEventTimeExtractor};
782 use crate::window::TumblingWindowAssigner;
783 use llm_optimizer_types::events::{EventSource, EventType, FeedbackEvent};
784 use std::time::Duration;
785
786 #[tokio::test]
787 async fn test_map_operator() {
788 let operator = MapOperator::new("double", |x: i32| x * 2);
789 let ctx = OperatorContext::new(Watermark::min(), 0);
790
791 let input = bincode::serialize(&5).unwrap();
792 let output = operator.process(input, &ctx).await.unwrap();
793
794 assert_eq!(output.len(), 1);
795 let result: i32 = bincode::deserialize(&output[0]).unwrap();
796 assert_eq!(result, 10);
797 }
798
799 #[tokio::test]
800 async fn test_filter_operator() {
801 let operator = FilterOperator::new("even", |x: &i32| x % 2 == 0);
802 let ctx = OperatorContext::new(Watermark::min(), 0);
803
804 let input = bincode::serialize(&4).unwrap();
806 let output = operator.process(input, &ctx).await.unwrap();
807 assert_eq!(output.len(), 1);
808
809 let input = bincode::serialize(&5).unwrap();
811 let output = operator.process(input, &ctx).await.unwrap();
812 assert_eq!(output.len(), 0);
813 }
814
815 #[tokio::test]
816 async fn test_flatmap_operator() {
817 let operator = FlatMapOperator::new("range", |n: i32| (0..n).collect::<Vec<_>>());
818 let ctx = OperatorContext::new(Watermark::min(), 0);
819
820 let input = bincode::serialize(&3).unwrap();
821 let output = operator.process(input, &ctx).await.unwrap();
822
823 assert_eq!(output.len(), 3);
824 let v0: i32 = bincode::deserialize(&output[0]).unwrap();
825 let v1: i32 = bincode::deserialize(&output[1]).unwrap();
826 let v2: i32 = bincode::deserialize(&output[2]).unwrap();
827 assert_eq!(v0, 0);
828 assert_eq!(v1, 1);
829 assert_eq!(v2, 2);
830 }
831
832 #[tokio::test]
833 async fn test_keyby_operator() {
834 let operator = KeyByOperator::new("key-by", FeedbackEventKeyExtractor);
835 let ctx = OperatorContext::new(Watermark::min(), 0);
836
837 let event = FeedbackEvent::new(
838 EventSource::Observatory,
839 EventType::Metric,
840 serde_json::json!({}),
841 );
842
843 let input = bincode::serialize(&event).unwrap();
844 let output = operator.process(input.clone(), &ctx).await.unwrap();
845
846 assert_eq!(output.len(), 1);
847 assert_eq!(output[0], input); }
849
850 #[tokio::test]
851 async fn test_window_operator() {
852 let assigner = TumblingWindowAssigner::new(chrono::Duration::seconds(60));
853 let operator = WindowOperator::new("window", assigner, FeedbackEventTimeExtractor);
854 let ctx = OperatorContext::new(Watermark::min(), 0);
855
856 let event = FeedbackEvent::new(
857 EventSource::Observatory,
858 EventType::Metric,
859 serde_json::json!({}),
860 );
861
862 let input = bincode::serialize(&event).unwrap();
863 let output = operator.process(input.clone(), &ctx).await.unwrap();
864
865 assert_eq!(output.len(), 1);
866 assert_eq!(output[0], input); }
868
869 #[tokio::test]
870 async fn test_aggregate_operator() {
871 let aggregator: SumAggregator<f64> = SumAggregator::new();
872 let operator = AggregateOperator::new("sum", aggregator);
873 let ctx = OperatorContext::new(Watermark::min(), 0);
874
875 let input1 = bincode::serialize(&5.0).unwrap();
877 operator.process(input1, &ctx).await.unwrap();
878
879 let input2 = bincode::serialize(&3.0).unwrap();
881 let output = operator.process(input2, &ctx).await.unwrap();
882
883 assert_eq!(output.len(), 1);
885 let result: f64 = bincode::deserialize(&output[0]).unwrap();
886 assert_eq!(result, 8.0);
887 }
888
889 #[tokio::test]
890 async fn test_operator_chain() {
891 let mut chain = OperatorChain::new();
892
893 chain.add(MapOperator::new("add-one", |x: i32| x + 1));
895 chain.add(FilterOperator::new("even", |x: &i32| x % 2 == 0));
896 chain.add(MapOperator::new("double", |x: i32| x * 2));
897
898 assert_eq!(chain.len(), 3);
899 assert!(!chain.is_empty());
900
901 let ctx = OperatorContext::new(Watermark::min(), 0);
902
903 let input = bincode::serialize(&3).unwrap();
905 let output = chain.process(vec![input], &ctx).await.unwrap();
906 assert_eq!(output.len(), 1);
907 let result: i32 = bincode::deserialize(&output[0]).unwrap();
908 assert_eq!(result, 8);
909
910 let input = bincode::serialize(&4).unwrap();
912 let output = chain.process(vec![input], &ctx).await.unwrap();
913 assert_eq!(output.len(), 0);
914 }
915
916 #[tokio::test]
917 async fn test_operator_context() {
918 let watermark = Watermark::new(12345);
919 let ctx = OperatorContext::new(watermark, 7);
920
921 assert_eq!(ctx.watermark, watermark);
922 assert_eq!(ctx.partition, 7);
923 assert!(ctx.processing_time > 0);
924 }
925
926 #[test]
927 fn test_map_operator_name() {
928 let operator = MapOperator::new("my-map", |x: i32| x + 1);
929 assert_eq!(operator.name(), "my-map");
930 }
931
932 #[test]
933 fn test_filter_operator_name() {
934 let operator = FilterOperator::new("my-filter", |x: &i32| *x > 0);
935 assert_eq!(operator.name(), "my-filter");
936 }
937
938 #[test]
939 fn test_flatmap_operator_name() {
940 let operator = FlatMapOperator::new("my-flatmap", |x: i32| vec![x]);
941 assert_eq!(operator.name(), "my-flatmap");
942 }
943
944 #[test]
945 fn test_keyby_operator_extract_key() {
946 let operator = KeyByOperator::new("my-keyby", FeedbackEventKeyExtractor);
947
948 let mut event = FeedbackEvent::new(
949 EventSource::Observatory,
950 EventType::Metric,
951 serde_json::json!({}),
952 );
953 event.metadata.insert("model_id".to_string(), "test-model".to_string());
954
955 let key = operator.extract_key(&event);
956 match key {
957 EventKey::Model(id) => assert_eq!(id, "test-model"),
958 _ => panic!("Expected Model key"),
959 }
960 }
961
962 #[test]
963 fn test_window_operator_assign_windows() {
964 let assigner = TumblingWindowAssigner::new(chrono::Duration::seconds(60));
965 let operator = WindowOperator::new("my-window", assigner, FeedbackEventTimeExtractor);
966
967 let event = FeedbackEvent::new(
968 EventSource::Observatory,
969 EventType::Metric,
970 serde_json::json!({}),
971 );
972
973 let windows = operator.assign_windows(&event, "test-key");
974 assert!(!windows.is_empty());
975 }
976
977 #[test]
978 fn test_aggregate_operator_clone() {
979 let aggregator: SumAggregator<f64> = SumAggregator::new();
980 let operator = AggregateOperator::new("sum", aggregator);
981 let _cloned = operator.clone();
982 }
983
984 #[test]
985 fn test_operator_chain_default() {
986 let chain = OperatorChain::default();
987 assert!(chain.is_empty());
988 assert_eq!(chain.len(), 0);
989 }
990
991 #[tokio::test]
992 async fn test_deduplication_operator_disabled() {
993 use crate::config::DeduplicationConfig;
994
995 let config = DeduplicationConfig::default(); let operator = DeduplicationOperator::new("dedup", config);
997 let ctx = OperatorContext::new(Watermark::min(), 0);
998
999 let input = bincode::serialize(&42).unwrap();
1000 let output1 = operator.process(input.clone(), &ctx).await.unwrap();
1001 let output2 = operator.process(input.clone(), &ctx).await.unwrap();
1002
1003 assert_eq!(output1.len(), 1);
1005 assert_eq!(output2.len(), 1);
1006 }
1007
1008 #[tokio::test]
1009 async fn test_deduplication_operator_enabled() {
1010 use crate::config::DeduplicationConfig;
1011
1012 let config = DeduplicationConfig::new().enabled();
1013 let operator = DeduplicationOperator::new("dedup", config);
1014 let ctx = OperatorContext::new(Watermark::min(), 0);
1015
1016 let input = bincode::serialize(&42).unwrap();
1017
1018 let output1 = operator.process(input.clone(), &ctx).await.unwrap();
1020 assert_eq!(output1.len(), 1);
1021
1022 let output2 = operator.process(input.clone(), &ctx).await.unwrap();
1024 assert_eq!(output2.len(), 0);
1025 }
1026
1027 #[tokio::test]
1028 async fn test_deduplication_operator_different_events() {
1029 use crate::config::DeduplicationConfig;
1030
1031 let config = DeduplicationConfig::new().enabled();
1032 let operator = DeduplicationOperator::new("dedup", config);
1033 let ctx = OperatorContext::new(Watermark::min(), 0);
1034
1035 let input1 = bincode::serialize(&42).unwrap();
1036 let input2 = bincode::serialize(&43).unwrap();
1037
1038 let output1 = operator.process(input1, &ctx).await.unwrap();
1040 let output2 = operator.process(input2, &ctx).await.unwrap();
1041
1042 assert_eq!(output1.len(), 1);
1043 assert_eq!(output2.len(), 1);
1044 }
1045
1046 #[tokio::test]
1047 async fn test_deduplication_operator_ttl() {
1048 use crate::config::DeduplicationConfig;
1049 use std::time::Duration;
1050
1051 let config = DeduplicationConfig::new()
1052 .enabled()
1053 .with_ttl(Duration::from_millis(100));
1054
1055 let operator = DeduplicationOperator::new("dedup", config);
1056 let ctx = OperatorContext::new(Watermark::min(), 0);
1057
1058 let input = bincode::serialize(&42).unwrap();
1059
1060 let output1 = operator.process(input.clone(), &ctx).await.unwrap();
1062 assert_eq!(output1.len(), 1);
1063
1064 tokio::time::sleep(tokio::time::Duration::from_millis(150)).await;
1066
1067 let output2 = operator.process(input.clone(), &ctx).await.unwrap();
1069 assert_eq!(output2.len(), 1);
1070 }
1071
1072 #[test]
1073 fn test_deduplication_operator_stats() {
1074 use crate::config::DeduplicationConfig;
1075
1076 let config = DeduplicationConfig::new().enabled();
1077 let operator = DeduplicationOperator::new("dedup", config);
1078
1079 let stats = operator.stats();
1080 assert_eq!(stats.cached_signatures, 0);
1081 assert!(stats.enabled);
1082 }
1083
1084 #[test]
1085 fn test_deduplication_operator_clone() {
1086 use crate::config::DeduplicationConfig;
1087
1088 let config = DeduplicationConfig::new().enabled();
1089 let operator = DeduplicationOperator::new("dedup", config);
1090 let _cloned = operator.clone();
1091 }
1092}