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 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#[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}