wp_data_fmt/
proto.rs

1use crate::formatter::DataFormat;
2use wp_model_core::model::{DataField, DataRecord, DataType, Value, types::value::ObjectValue};
3
4#[derive(Default)]
5pub struct ProtoTxt;
6
7impl ProtoTxt {
8    pub fn new() -> Self {
9        Self
10    }
11}
12
13impl DataFormat for ProtoTxt {
14    type Output = String;
15    fn format_null(&self) -> String {
16        String::new()
17    }
18    fn format_bool(&self, v: &bool) -> String {
19        v.to_string()
20    }
21    fn format_string(&self, v: &str) -> String {
22        format!("\"{}\"", v.replace('"', "\\\""))
23    }
24    fn format_i64(&self, v: &i64) -> String {
25        v.to_string()
26    }
27    fn format_f64(&self, v: &f64) -> String {
28        v.to_string()
29    }
30    fn format_ip(&self, v: &std::net::IpAddr) -> String {
31        self.format_string(&v.to_string())
32    }
33    fn format_datetime(&self, v: &chrono::NaiveDateTime) -> String {
34        self.format_string(&v.to_string())
35    }
36    fn format_object(&self, value: &ObjectValue) -> String {
37        let mut out = String::new();
38        for (k, v) in value.iter() {
39            out.push_str(&format!("{}: {}\n", k, self.fmt_value(v.get_value())));
40        }
41        out
42    }
43    fn format_array(&self, value: &[DataField]) -> String {
44        let items: Vec<String> = value.iter().map(|f| self.format_field(f)).collect();
45        format!("[{}]", items.join(", "))
46    }
47    fn format_field(&self, field: &DataField) -> String {
48        if *field.get_meta() == DataType::Ignore {
49            String::new()
50        } else {
51            match field.get_value() {
52                Value::Obj(_) | Value::Array(_) => format!(
53                    "{}: {}",
54                    field.get_name(),
55                    self.fmt_value(field.get_value())
56                ),
57                _ => format!(
58                    "{}: {}",
59                    field.get_name(),
60                    self.fmt_value(field.get_value())
61                ),
62            }
63        }
64    }
65    fn format_record(&self, record: &DataRecord) -> String {
66        let items = record
67            .items
68            .iter()
69            .filter(|f| *f.get_meta() != DataType::Ignore)
70            .map(|f| self.format_field(f))
71            .collect::<Vec<_>>();
72        // 生成标准的 proto-text 格式:消息用花括号包围
73        format!("{{ {} }}", items.join(" "))
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use std::net::IpAddr;
81    use std::str::FromStr;
82
83    #[test]
84    fn test_proto_new() {
85        let proto = ProtoTxt::new();
86        assert_eq!(proto.format_null(), "");
87    }
88
89    #[test]
90    fn test_proto_default() {
91        let proto = ProtoTxt;
92        assert_eq!(proto.format_null(), "");
93    }
94
95    #[test]
96    fn test_format_null() {
97        let proto = ProtoTxt;
98        assert_eq!(proto.format_null(), "");
99    }
100
101    #[test]
102    fn test_format_bool() {
103        let proto = ProtoTxt;
104        assert_eq!(proto.format_bool(&true), "true");
105        assert_eq!(proto.format_bool(&false), "false");
106    }
107
108    #[test]
109    fn test_format_string() {
110        let proto = ProtoTxt;
111        assert_eq!(proto.format_string("hello"), "\"hello\"");
112        assert_eq!(proto.format_string(""), "\"\"");
113    }
114
115    #[test]
116    fn test_format_string_escape_quotes() {
117        let proto = ProtoTxt;
118        assert_eq!(proto.format_string("say \"hi\""), "\"say \\\"hi\\\"\"");
119    }
120
121    #[test]
122    fn test_format_i64() {
123        let proto = ProtoTxt;
124        assert_eq!(proto.format_i64(&0), "0");
125        assert_eq!(proto.format_i64(&42), "42");
126        assert_eq!(proto.format_i64(&-100), "-100");
127    }
128
129    #[test]
130    fn test_format_f64() {
131        let proto = ProtoTxt;
132        assert_eq!(proto.format_f64(&3.24), "3.24");
133        assert_eq!(proto.format_f64(&0.0), "0");
134    }
135
136    #[test]
137    fn test_format_ip() {
138        let proto = ProtoTxt;
139        let ip = IpAddr::from_str("192.168.1.1").unwrap();
140        assert_eq!(proto.format_ip(&ip), "\"192.168.1.1\"");
141    }
142
143    #[test]
144    fn test_format_datetime() {
145        let proto = ProtoTxt;
146        let dt = chrono::NaiveDateTime::parse_from_str("2024-01-15 10:30:45", "%Y-%m-%d %H:%M:%S")
147            .unwrap();
148        let result = proto.format_datetime(&dt);
149        assert!(result.starts_with('"'));
150        assert!(result.ends_with('"'));
151        assert!(result.contains("2024"));
152    }
153
154    #[test]
155    fn test_format_field() {
156        let proto = ProtoTxt;
157        let field = DataField::from_chars("name", "Alice");
158        let result = proto.format_field(&field);
159        assert_eq!(result, "name: \"Alice\"");
160    }
161
162    #[test]
163    fn test_format_field_digit() {
164        let proto = ProtoTxt;
165        let field = DataField::from_digit("age", 30);
166        let result = proto.format_field(&field);
167        assert_eq!(result, "age: 30");
168    }
169
170    #[test]
171    fn test_format_record() {
172        let proto = ProtoTxt;
173        let record = DataRecord {
174            items: vec![
175                DataField::from_chars("name", "Alice"),
176                DataField::from_digit("age", 30),
177            ],
178        };
179        let result = proto.format_record(&record);
180        assert!(result.starts_with("{ "));
181        assert!(result.ends_with(" }"));
182        assert!(result.contains("name: \"Alice\""));
183        assert!(result.contains("age: 30"));
184    }
185
186    #[test]
187    fn test_format_array() {
188        let proto = ProtoTxt;
189        let arr = vec![DataField::from_digit("x", 1), DataField::from_digit("y", 2)];
190        let result = proto.format_array(&arr);
191        assert!(result.starts_with('['));
192        assert!(result.ends_with(']'));
193    }
194}