wp_data_fmt/
kv.rs

1use crate::formatter::DataFormat;
2use std::fmt::Write;
3use wp_model_core::model::{DataField, DataRecord, DataType, types::value::ObjectValue};
4
5pub struct KeyValue {
6    pair_separator: String,
7    key_value_separator: String,
8    quote_strings: bool,
9}
10
11impl Default for KeyValue {
12    fn default() -> Self {
13        Self {
14            pair_separator: ", ".to_string(),
15            key_value_separator: ": ".to_string(),
16            quote_strings: true,
17        }
18    }
19}
20
21impl KeyValue {
22    pub fn new() -> Self {
23        Self::default()
24    }
25    pub fn with_pair_separator(mut self, s: impl Into<String>) -> Self {
26        self.pair_separator = s.into();
27        self
28    }
29    pub fn with_key_value_separator(mut self, s: impl Into<String>) -> Self {
30        self.key_value_separator = s.into();
31        self
32    }
33    pub fn with_quote_strings(mut self, quote: bool) -> Self {
34        self.quote_strings = quote;
35        self
36    }
37
38    fn format_string_value(&self, value: &str) -> String {
39        if self.quote_strings {
40            format!("\"{}\"", value.replace('\"', "\\\""))
41        } else {
42            value.to_string()
43        }
44    }
45}
46
47impl DataFormat for KeyValue {
48    type Output = String;
49
50    fn format_null(&self) -> String {
51        String::new()
52    }
53    fn format_bool(&self, v: &bool) -> String {
54        if *v { "true".into() } else { "false".into() }
55    }
56    fn format_string(&self, v: &str) -> String {
57        self.format_string_value(v)
58    }
59    fn format_i64(&self, v: &i64) -> String {
60        v.to_string()
61    }
62    fn format_f64(&self, v: &f64) -> String {
63        v.to_string()
64    }
65    fn format_ip(&self, v: &std::net::IpAddr) -> String {
66        v.to_string()
67    }
68    fn format_datetime(&self, v: &chrono::NaiveDateTime) -> String {
69        v.to_string()
70    }
71
72    fn format_object(&self, value: &ObjectValue) -> String {
73        let mut output = String::new();
74        output.push('{');
75        for (i, (k, v)) in value.iter().enumerate() {
76            if i > 0 {
77                output.push_str(&self.pair_separator);
78            }
79            write!(
80                output,
81                "{}{}{}",
82                self.format_string(k),
83                self.key_value_separator,
84                self.fmt_value(v.get_value())
85            )
86            .unwrap();
87        }
88        output.push('}');
89        output
90    }
91
92    fn format_array(&self, value: &[DataField]) -> String {
93        let mut output = String::new();
94        output.push('[');
95        for (i, field) in value.iter().enumerate() {
96            if i > 0 {
97                output.push_str(&self.pair_separator);
98            }
99            output.push_str(&self.fmt_value(field.get_value()));
100        }
101        output.push(']');
102        output
103    }
104
105    fn format_field(&self, field: &DataField) -> String {
106        format!(
107            "{}{}{}",
108            field.get_name(),
109            self.key_value_separator,
110            self.fmt_value(field.get_value())
111        )
112    }
113
114    fn format_record(&self, record: &DataRecord) -> String {
115        record
116            .items
117            .iter()
118            .filter(|f| *f.get_meta() != DataType::Ignore)
119            .map(|field| self.format_field(field))
120            .collect::<Vec<_>>()
121            .join(&self.pair_separator)
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use std::net::IpAddr;
129    use std::str::FromStr;
130
131    #[test]
132    fn test_kv_default() {
133        let kv = KeyValue::default();
134        assert_eq!(kv.pair_separator, ", ");
135        assert_eq!(kv.key_value_separator, ": ");
136        assert!(kv.quote_strings);
137    }
138
139    #[test]
140    fn test_kv_new() {
141        let kv = KeyValue::new();
142        assert_eq!(kv.pair_separator, ", ");
143    }
144
145    #[test]
146    fn test_kv_builder_pattern() {
147        let kv = KeyValue::new()
148            .with_pair_separator("; ")
149            .with_key_value_separator("=")
150            .with_quote_strings(false);
151        assert_eq!(kv.pair_separator, "; ");
152        assert_eq!(kv.key_value_separator, "=");
153        assert!(!kv.quote_strings);
154    }
155
156    #[test]
157    fn test_format_null() {
158        let kv = KeyValue::default();
159        assert_eq!(kv.format_null(), "");
160    }
161
162    #[test]
163    fn test_format_bool() {
164        let kv = KeyValue::default();
165        assert_eq!(kv.format_bool(&true), "true");
166        assert_eq!(kv.format_bool(&false), "false");
167    }
168
169    #[test]
170    fn test_format_string_with_quotes() {
171        let kv = KeyValue::default();
172        assert_eq!(kv.format_string("hello"), "\"hello\"");
173        assert_eq!(kv.format_string("world"), "\"world\"");
174    }
175
176    #[test]
177    fn test_format_string_without_quotes() {
178        let kv = KeyValue::new().with_quote_strings(false);
179        assert_eq!(kv.format_string("hello"), "hello");
180    }
181
182    #[test]
183    fn test_format_string_escape_quotes() {
184        let kv = KeyValue::default();
185        assert_eq!(kv.format_string("say \"hi\""), "\"say \\\"hi\\\"\"");
186    }
187
188    #[test]
189    fn test_format_i64() {
190        let kv = KeyValue::default();
191        assert_eq!(kv.format_i64(&0), "0");
192        assert_eq!(kv.format_i64(&42), "42");
193        assert_eq!(kv.format_i64(&-100), "-100");
194    }
195
196    #[test]
197    fn test_format_f64() {
198        let kv = KeyValue::default();
199        assert_eq!(kv.format_f64(&3.24), "3.24");
200        assert_eq!(kv.format_f64(&0.0), "0");
201    }
202
203    #[test]
204    fn test_format_ip() {
205        let kv = KeyValue::default();
206        let ip = IpAddr::from_str("192.168.1.1").unwrap();
207        assert_eq!(kv.format_ip(&ip), "192.168.1.1");
208    }
209
210    #[test]
211    fn test_format_datetime() {
212        let kv = KeyValue::default();
213        let dt = chrono::NaiveDateTime::parse_from_str("2024-01-15 10:30:45", "%Y-%m-%d %H:%M:%S")
214            .unwrap();
215        let result = kv.format_datetime(&dt);
216        assert!(result.contains("2024"));
217    }
218
219    #[test]
220    fn test_format_field() {
221        let kv = KeyValue::default();
222        let field = DataField::from_chars("name", "Alice");
223        let result = kv.format_field(&field);
224        assert_eq!(result, "name: \"Alice\"");
225    }
226
227    #[test]
228    fn test_format_field_with_custom_separator() {
229        let kv = KeyValue::new().with_key_value_separator("=");
230        let field = DataField::from_digit("age", 30);
231        let result = kv.format_field(&field);
232        assert_eq!(result, "age=30");
233    }
234
235    #[test]
236    fn test_format_record() {
237        let kv = KeyValue::default();
238        let record = DataRecord {
239            items: vec![
240                DataField::from_chars("name", "Alice"),
241                DataField::from_digit("age", 30),
242            ],
243        };
244        let result = kv.format_record(&record);
245        assert!(result.contains("name: \"Alice\""));
246        assert!(result.contains("age: 30"));
247        assert!(result.contains(", "));
248    }
249
250    #[test]
251    fn test_format_record_custom_separators() {
252        let kv = KeyValue::new()
253            .with_pair_separator(" | ")
254            .with_key_value_separator("=")
255            .with_quote_strings(false);
256        let record = DataRecord {
257            items: vec![
258                DataField::from_chars("a", "x"),
259                DataField::from_chars("b", "y"),
260            ],
261        };
262        let result = kv.format_record(&record);
263        assert_eq!(result, "a=x | b=y");
264    }
265
266    #[test]
267    fn test_format_array() {
268        let kv = KeyValue::default();
269        let arr = vec![DataField::from_digit("", 1), DataField::from_digit("", 2)];
270        let result = kv.format_array(&arr);
271        assert!(result.starts_with('['));
272        assert!(result.ends_with(']'));
273        assert!(result.contains("1"));
274        assert!(result.contains("2"));
275    }
276}