Skip to main content

datasynth_core/plugins/
timestamp_enricher.rs

1//! Example `TransformPlugin` that enriches records with generation metadata.
2//!
3//! Adds a UTC timestamp and plugin version to every record that passes through.
4
5use crate::error::SynthError;
6use crate::traits::plugin::{GeneratedRecord, TransformPlugin};
7
8/// A transform plugin that adds `_generated_at` (ISO 8601 UTC) and
9/// `_plugin_version` fields to each record.
10pub struct TimestampEnricher;
11
12impl TimestampEnricher {
13    /// Create a new `TimestampEnricher`.
14    pub fn new() -> Self {
15        Self
16    }
17}
18
19impl Default for TimestampEnricher {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl TransformPlugin for TimestampEnricher {
26    fn name(&self) -> &str {
27        "timestamp_enricher"
28    }
29
30    fn transform(&self, records: Vec<GeneratedRecord>) -> Result<Vec<GeneratedRecord>, SynthError> {
31        let now = crate::clock::now().to_rfc3339();
32        let version = serde_json::Value::String("1.0.0".to_string());
33
34        let enriched = records
35            .into_iter()
36            .map(|mut record| {
37                record
38                    .fields
39                    .insert("_generated_at".to_string(), serde_json::json!(now));
40                record
41                    .fields
42                    .insert("_plugin_version".to_string(), version.clone());
43                record
44            })
45            .collect();
46
47        Ok(enriched)
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn test_timestamp_enricher_adds_fields() {
57        let enricher = TimestampEnricher::new();
58
59        let records = vec![
60            GeneratedRecord::new("invoice").with_field("id", serde_json::json!("INV001")),
61            GeneratedRecord::new("invoice").with_field("id", serde_json::json!("INV002")),
62        ];
63
64        let result = enricher
65            .transform(records)
66            .expect("transform should succeed");
67        assert_eq!(result.len(), 2);
68
69        for record in &result {
70            assert!(
71                record.fields.contains_key("_generated_at"),
72                "should have _generated_at field"
73            );
74            assert_eq!(
75                record.get_str("_plugin_version"),
76                Some("1.0.0"),
77                "should have _plugin_version = 1.0.0"
78            );
79            // Original field should be preserved.
80            assert!(
81                record.get_str("id").is_some(),
82                "original id field should be preserved"
83            );
84        }
85    }
86
87    #[test]
88    fn test_timestamp_enricher_empty_input() {
89        let enricher = TimestampEnricher::new();
90        let result = enricher
91            .transform(vec![])
92            .expect("transform should succeed");
93        assert!(result.is_empty());
94    }
95
96    #[test]
97    fn test_timestamp_enricher_preserves_existing_fields() {
98        let enricher = TimestampEnricher::new();
99
100        let records = vec![GeneratedRecord::new("order")
101            .with_field("customer", serde_json::json!("CUST001"))
102            .with_field("amount", serde_json::json!(1500.0))];
103
104        let result = enricher
105            .transform(records)
106            .expect("transform should succeed");
107        let record = &result[0];
108
109        assert_eq!(record.get_str("customer"), Some("CUST001"));
110        assert_eq!(record.get("amount").and_then(|v| v.as_f64()), Some(1500.0));
111        assert!(record.fields.contains_key("_generated_at"));
112        assert_eq!(record.get_str("_plugin_version"), Some("1.0.0"));
113    }
114}