Skip to main content

wp_data_fmt/
proto.rs

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