1#[allow(deprecated)]
2use crate::formatter::DataFormat;
3use crate::formatter::{RecordFormatter, ValueFormatter};
4use std::fmt::Write;
5use wp_model_core::model::{DataRecord, DataType, FieldStorage, types::value::ObjectValue};
6
7pub struct KeyValue {
8 pair_separator: String,
9 key_value_separator: String,
10 quote_strings: bool,
11}
12
13impl Default for KeyValue {
14 fn default() -> Self {
15 Self {
16 pair_separator: ", ".to_string(),
17 key_value_separator: ": ".to_string(),
18 quote_strings: true,
19 }
20 }
21}
22
23impl KeyValue {
24 pub fn new() -> Self {
25 Self::default()
26 }
27 pub fn with_pair_separator(mut self, s: impl Into<String>) -> Self {
28 self.pair_separator = s.into();
29 self
30 }
31 pub fn with_key_value_separator(mut self, s: impl Into<String>) -> Self {
32 self.key_value_separator = s.into();
33 self
34 }
35 pub fn with_quote_strings(mut self, quote: bool) -> Self {
36 self.quote_strings = quote;
37 self
38 }
39
40 fn format_string_value(&self, value: &str) -> String {
41 if self.quote_strings {
42 format!("\"{}\"", value.replace('\"', "\\\""))
43 } else {
44 value.to_string()
45 }
46 }
47}
48
49#[allow(deprecated)]
50impl DataFormat for KeyValue {
51 type Output = String;
52
53 fn format_null(&self) -> String {
54 String::new()
55 }
56 fn format_bool(&self, v: &bool) -> String {
57 if *v { "true".into() } else { "false".into() }
58 }
59 fn format_string(&self, v: &str) -> String {
60 self.format_string_value(v)
61 }
62 fn format_i64(&self, v: &i64) -> String {
63 v.to_string()
64 }
65 fn format_f64(&self, v: &f64) -> String {
66 v.to_string()
67 }
68 fn format_ip(&self, v: &std::net::IpAddr) -> String {
69 v.to_string()
70 }
71 fn format_datetime(&self, v: &chrono::NaiveDateTime) -> String {
72 v.to_string()
73 }
74
75 fn format_object(&self, value: &ObjectValue) -> String {
76 let mut output = String::new();
77 output.push('{');
78 for (i, (k, v)) in value.iter().enumerate() {
79 if i > 0 {
80 output.push_str(&self.pair_separator);
81 }
82 write!(
83 output,
84 "{}{}{}",
85 self.format_string(k),
86 self.key_value_separator,
87 self.fmt_value(v.get_value())
88 )
89 .unwrap();
90 }
91 output.push('}');
92 output
93 }
94
95 fn format_array(&self, value: &[FieldStorage]) -> String {
96 let mut output = String::new();
97 output.push('[');
98 for (i, field) in value.iter().enumerate() {
99 if i > 0 {
100 output.push_str(&self.pair_separator);
101 }
102 output.push_str(&self.fmt_value(field.get_value()));
103 }
104 output.push(']');
105 output
106 }
107
108 fn format_field(&self, field: &FieldStorage) -> String {
109 format!(
110 "{}{}{}",
111 field.get_name(),
112 self.key_value_separator,
113 self.fmt_value(field.get_value())
114 )
115 }
116
117 fn format_record(&self, record: &DataRecord) -> String {
118 record
119 .items
120 .iter()
121 .filter(|f| *f.get_meta() != DataType::Ignore)
122 .map(|field| self.format_field(field))
123 .collect::<Vec<_>>()
124 .join(&self.pair_separator)
125 }
126}
127
128#[cfg(test)]
129#[allow(deprecated)]
130mod tests {
131 use super::*;
132 use std::net::IpAddr;
133 use std::str::FromStr;
134 use wp_model_core::model::DataField;
135
136 #[test]
137 fn test_kv_default() {
138 let kv = KeyValue::default();
139 assert_eq!(kv.pair_separator, ", ");
140 assert_eq!(kv.key_value_separator, ": ");
141 assert!(kv.quote_strings);
142 }
143
144 #[test]
145 fn test_kv_new() {
146 let kv = KeyValue::new();
147 assert_eq!(kv.pair_separator, ", ");
148 }
149
150 #[test]
151 fn test_kv_builder_pattern() {
152 let kv = KeyValue::new()
153 .with_pair_separator("; ")
154 .with_key_value_separator("=")
155 .with_quote_strings(false);
156 assert_eq!(kv.pair_separator, "; ");
157 assert_eq!(kv.key_value_separator, "=");
158 assert!(!kv.quote_strings);
159 }
160
161 #[test]
162 fn test_format_null() {
163 let kv = KeyValue::default();
164 assert_eq!(kv.format_null(), "");
165 }
166
167 #[test]
168 fn test_format_bool() {
169 let kv = KeyValue::default();
170 assert_eq!(kv.format_bool(&true), "true");
171 assert_eq!(kv.format_bool(&false), "false");
172 }
173
174 #[test]
175 fn test_format_string_with_quotes() {
176 let kv = KeyValue::default();
177 assert_eq!(kv.format_string("hello"), "\"hello\"");
178 assert_eq!(kv.format_string("world"), "\"world\"");
179 }
180
181 #[test]
182 fn test_format_string_without_quotes() {
183 let kv = KeyValue::new().with_quote_strings(false);
184 assert_eq!(kv.format_string("hello"), "hello");
185 }
186
187 #[test]
188 fn test_format_string_escape_quotes() {
189 let kv = KeyValue::default();
190 assert_eq!(kv.format_string("say \"hi\""), "\"say \\\"hi\\\"\"");
191 }
192
193 #[test]
194 fn test_format_i64() {
195 let kv = KeyValue::default();
196 assert_eq!(kv.format_i64(&0), "0");
197 assert_eq!(kv.format_i64(&42), "42");
198 assert_eq!(kv.format_i64(&-100), "-100");
199 }
200
201 #[test]
202 fn test_format_f64() {
203 let kv = KeyValue::default();
204 assert_eq!(kv.format_f64(&3.24), "3.24");
205 assert_eq!(kv.format_f64(&0.0), "0");
206 }
207
208 #[test]
209 fn test_format_ip() {
210 let kv = KeyValue::default();
211 let ip = IpAddr::from_str("192.168.1.1").unwrap();
212 assert_eq!(kv.format_ip(&ip), "192.168.1.1");
213 }
214
215 #[test]
216 fn test_format_datetime() {
217 let kv = KeyValue::default();
218 let dt = chrono::NaiveDateTime::parse_from_str("2024-01-15 10:30:45", "%Y-%m-%d %H:%M:%S")
219 .unwrap();
220 let result = kv.format_datetime(&dt);
221 assert!(result.contains("2024"));
222 }
223
224 #[test]
225 fn test_format_field() {
226 let kv = KeyValue::default();
227 let field = FieldStorage::from_owned(DataField::from_chars("name", "Alice"));
228 let result = kv.format_field(&field);
229 assert_eq!(result, "name: \"Alice\"");
230 }
231
232 #[test]
233 fn test_format_field_with_custom_separator() {
234 let kv = KeyValue::new().with_key_value_separator("=");
235 let field = FieldStorage::from_owned(DataField::from_digit("age", 30));
236 let result = kv.format_field(&field);
237 assert_eq!(result, "age=30");
238 }
239
240 #[test]
241 fn test_format_record() {
242 let kv = KeyValue::default();
243 let record = DataRecord {
244 id: Default::default(),
245 items: vec![
246 FieldStorage::from_owned(DataField::from_chars("name", "Alice")),
247 FieldStorage::from_owned(DataField::from_digit("age", 30)),
248 ],
249 };
250 let result = kv.format_record(&record);
251 assert!(result.contains("name: \"Alice\""));
252 assert!(result.contains("age: 30"));
253 assert!(result.contains(", "));
254 }
255
256 #[test]
257 fn test_format_record_custom_separators() {
258 let kv = KeyValue::new()
259 .with_pair_separator(" | ")
260 .with_key_value_separator("=")
261 .with_quote_strings(false);
262 let record = DataRecord {
263 id: Default::default(),
264 items: vec![
265 FieldStorage::from_owned(DataField::from_chars("a", "x")),
266 FieldStorage::from_owned(DataField::from_chars("b", "y")),
267 ],
268 };
269 let result = kv.format_record(&record);
270 assert_eq!(result, "a=x | b=y");
271 }
272
273 #[test]
274 fn test_format_array() {
275 let kv = KeyValue::default();
276 let arr = vec![
277 FieldStorage::from_owned(DataField::from_digit("", 1)),
278 FieldStorage::from_owned(DataField::from_digit("", 2)),
279 ];
280 let result = kv.format_array(&arr);
281 assert!(result.starts_with('['));
282 assert!(result.ends_with(']'));
283 assert!(result.contains("1"));
284 assert!(result.contains("2"));
285 }
286
287 fn make_record_with_obj() -> DataRecord {
288 let mut obj = ObjectValue::new();
289 obj.insert(
290 "ssl_cipher".to_string(),
291 FieldStorage::from_owned(DataField::from_chars("ssl_cipher", "ECDHE")),
292 );
293 DataRecord {
294 id: Default::default(),
295 items: vec![
296 FieldStorage::from_owned(DataField::from_digit("status", 200)),
297 FieldStorage::from_owned(DataField::from_obj("extends", obj)),
298 FieldStorage::from_owned(DataField::from_digit("length", 50)),
299 ],
300 }
301 }
302
303 #[test]
304 fn test_format_record_with_obj_no_newlines() {
305 let kv = KeyValue::default();
306 let record = make_record_with_obj();
307 let result = kv.format_record(&record);
308 assert!(
309 !result.contains('\n'),
310 "record output should not contain newlines: {}",
311 result
312 );
313 assert!(result.contains("ECDHE"));
314 assert!(result.contains("status: 200"));
315 }
316
317 #[test]
318 fn test_fmt_record_with_obj_no_newlines() {
319 let kv = KeyValue::default();
320 let record = make_record_with_obj();
321 let result = kv.fmt_record(&record);
322 assert!(
323 !result.contains('\n'),
324 "record output should not contain newlines: {}",
325 result
326 );
327 }
328
329 #[test]
330 fn test_old_new_api_consistency_nested() {
331 let kv = KeyValue::default();
332 let record = make_record_with_obj();
333 assert_eq!(kv.format_record(&record), kv.fmt_record(&record));
334 }
335}
336
337#[allow(clippy::items_after_test_module)]
342impl ValueFormatter for KeyValue {
343 type Output = String;
344
345 fn format_value(&self, value: &wp_model_core::model::Value) -> String {
346 use wp_model_core::model::Value;
347 match value {
348 Value::Null => String::new(),
349 Value::Bool(v) => if *v { "true" } else { "false" }.to_string(),
350 Value::Chars(v) => self.format_string_value(v),
351 Value::Digit(v) => v.to_string(),
352 Value::Float(v) => v.to_string(),
353 Value::IpAddr(v) => v.to_string(),
354 Value::Time(v) => v.to_string(),
355 Value::Obj(obj) => {
356 let mut output = String::new();
357 output.push('{');
358 for (i, (k, field)) in obj.iter().enumerate() {
359 if i > 0 {
360 output.push_str(&self.pair_separator);
361 }
362 write!(
363 output,
364 "{}{}{}",
365 self.format_string_value(k),
366 self.key_value_separator,
367 self.format_value(field.get_value())
368 )
369 .unwrap();
370 }
371 output.push('}');
372 output
373 }
374 Value::Array(arr) => {
375 let mut output = String::new();
376 output.push('[');
377 for (i, field) in arr.iter().enumerate() {
378 if i > 0 {
379 output.push_str(&self.pair_separator);
380 }
381 output.push_str(&self.format_value(field.get_value()));
382 }
383 output.push(']');
384 output
385 }
386 _ => value.to_string(),
387 }
388 }
389}
390
391impl RecordFormatter for KeyValue {
392 fn fmt_field(&self, field: &FieldStorage) -> String {
393 format!(
394 "{}{}{}",
395 field.get_name(),
396 self.key_value_separator,
397 self.format_value(field.get_value())
398 )
399 }
400
401 fn fmt_record(&self, record: &DataRecord) -> String {
402 record
403 .items
404 .iter()
405 .filter(|f| *f.get_meta() != DataType::Ignore)
406 .map(|field| self.fmt_field(field))
407 .collect::<Vec<_>>()
408 .join(&self.pair_separator)
409 }
410}