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