logform/formats/
metadata.rs

1use crate::LogInfo;
2use serde_json::json;
3use std::collections::HashMap;
4use std::collections::HashSet;
5
6use super::Format;
7
8pub struct MetadataFormat {
9    key: String,
10    fill_except: HashSet<String>,
11    fill_with: HashSet<String>,
12}
13
14impl MetadataFormat {
15    pub fn new() -> Self {
16        MetadataFormat {
17            key: "metadata".to_string(),
18            fill_except: HashSet::new(),
19            fill_with: HashSet::new(),
20        }
21    }
22
23    pub fn with_key(mut self, key: &str) -> Self {
24        self.key = key.to_string();
25        self
26    }
27
28    pub fn with_fill_except(mut self, keys: Vec<&str>) -> Self {
29        self.fill_except = keys.into_iter().map(String::from).collect();
30        self
31    }
32
33    pub fn with_fill_with(mut self, keys: Vec<&str>) -> Self {
34        self.fill_with = keys.into_iter().map(String::from).collect();
35        self
36    }
37}
38
39impl Format for MetadataFormat {
40    type Input = LogInfo;
41
42    fn transform(&self, mut info: LogInfo) -> Option<Self::Input> {
43        let mut metadata = HashMap::new();
44
45        if !self.fill_with.is_empty() {
46            for key in &self.fill_with {
47                if self.fill_except.contains(key) {
48                    continue;
49                }
50                if let Some(value) = info.meta.remove(key) {
51                    metadata.insert(key.clone(), value);
52                }
53            }
54        } else {
55            // Collect keys to move to avoid cloning the whole map
56            let keys_to_move: Vec<String> = info
57                .meta
58                .keys()
59                .filter(|key| !self.fill_except.contains(*key))
60                .cloned()
61                .collect();
62            for key in keys_to_move {
63                if let Some(value) = info.meta.remove(&key) {
64                    metadata.insert(key, value);
65                }
66            }
67        }
68
69        info.meta.insert(self.key.clone(), json!(metadata));
70        Some(info)
71    }
72}
73
74pub fn metadata() -> MetadataFormat {
75    MetadataFormat::new()
76}
77
78#[cfg(test)]
79mod tests {
80    #[test]
81    fn test_metadata_with_fill_with_and_fill_except() {
82        let metadata_format = MetadataFormat::new()
83            .with_key("metadata")
84            .with_fill_with(vec!["key1", "key2", "key3"])
85            .with_fill_except(vec!["key2"]);
86        let mut info = LogInfo::new("info", "Test message");
87        info.meta.insert("key1".to_string(), "value1".into());
88        info.meta.insert("key2".to_string(), "value2".into());
89        info.meta.insert("key3".to_string(), "value3".into());
90
91        let result = metadata_format.transform(info).unwrap();
92        let metadata = result.meta.get("metadata").unwrap();
93
94        // Only key1 and key3 should be present, key2 excluded
95        assert_eq!(
96            metadata.get("key1"),
97            Some(&Value::String("value1".to_string()))
98        );
99        assert_eq!(
100            metadata.get("key3"),
101            Some(&Value::String("value3".to_string()))
102        );
103        assert!(metadata.get("key2").is_none());
104    }
105
106    #[test]
107    fn test_metadata_with_empty_meta() {
108        let metadata_format = MetadataFormat::new().with_key("metadata");
109        let info = LogInfo::new("info", "Test message");
110        let result = metadata_format.transform(info).unwrap();
111        let metadata = result.meta.get("metadata").unwrap();
112        assert!(metadata.as_object().unwrap().is_empty());
113    }
114
115    #[test]
116    fn test_metadata_with_fill_with_nonexistent_keys() {
117        let metadata_format = MetadataFormat::new()
118            .with_key("metadata")
119            .with_fill_with(vec!["not_present", "also_missing"]);
120        let mut info = LogInfo::new("info", "Test message");
121        info.meta.insert("key1".to_string(), "value1".into());
122        let result = metadata_format.transform(info).unwrap();
123        let metadata = result.meta.get("metadata").unwrap();
124        // Should be empty since none of the fill_with keys exist
125        assert!(metadata.as_object().unwrap().is_empty());
126        // Original meta should remain unchanged
127        assert_eq!(
128            result.meta.get("key1"),
129            Some(&Value::String("value1".to_string()))
130        );
131    }
132
133    #[test]
134    fn test_metadata_format_default_constructor() {
135        let metadata_format = MetadataFormat::new();
136        let mut info = LogInfo::new("info", "Test message");
137        info.meta.insert("key1".to_string(), "value1".into());
138        let result = metadata_format.transform(info).unwrap();
139        // By default, all keys should be moved into the default key
140        let key = &metadata_format.key;
141        let metadata = result.meta.get(key).unwrap();
142        assert_eq!(
143            metadata.get("key1"),
144            Some(&Value::String("value1".to_string()))
145        );
146        // Should be removed from original meta
147        assert!(result.meta.get("key1").is_none());
148    }
149    use super::*;
150    use serde_json::Value;
151
152    #[test]
153    fn test_metadata_with_fill_with() {
154        let metadata_format = MetadataFormat::new()
155            .with_key("metadata")
156            .with_fill_with(vec!["key1"]);
157        let mut info = LogInfo::new("info", "Test message");
158        info.meta.insert("key1".to_string(), "value1".into());
159        info.meta.insert("key2".to_string(), "value2".into());
160
161        let result = metadata_format.transform(info).unwrap();
162        let metadata = result.meta.get("metadata").unwrap();
163
164        assert_eq!(
165            metadata.get("key1"),
166            Some(&Value::String("value1".to_string()))
167        );
168        assert!(metadata.get("key2").is_none());
169        assert!(result.meta.get("key1").is_none());
170        assert_eq!(
171            result.meta.get("key2"),
172            Some(&Value::String("value2".to_string()))
173        );
174    }
175
176    #[test]
177    fn test_metadata_with_fill_except() {
178        let metadata_format = MetadataFormat::new()
179            .with_key("metadata")
180            .with_fill_except(vec!["key1", "key3"]);
181        let mut info = LogInfo::new("info", "Test message");
182        info.meta.insert("key1".to_string(), "value1".into());
183        info.meta.insert("key2".to_string(), "value2".into());
184        info.meta.insert("key3".to_string(), "value3".into());
185
186        let result = metadata_format.transform(info).unwrap();
187        let metadata = result.meta.get("metadata").unwrap();
188
189        assert_eq!(
190            metadata.get("key2"),
191            Some(&Value::String("value2".to_string()))
192        );
193        assert!(metadata.get("key1").is_none());
194        assert!(metadata.get("key3").is_none());
195        assert_eq!(
196            result.meta.get("key1"),
197            Some(&Value::String("value1".to_string()))
198        );
199        assert_eq!(
200            result.meta.get("key3"),
201            Some(&Value::String("value3".to_string()))
202        );
203    }
204}