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