1mod encode;
2mod decode;
3
4pub use encode::encode;
5pub use decode::decode;
6
7#[derive(Debug, PartialEq)]
8pub enum ZoonError {
9 InvalidFormat(String),
10 UnsupportedType(String),
11 ParseError(String),
12}
13
14impl std::fmt::Display for ZoonError {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 match self {
17 ZoonError::InvalidFormat(s) => write!(f, "invalid format: {}", s),
18 ZoonError::UnsupportedType(s) => write!(f, "unsupported type: {}", s),
19 ZoonError::ParseError(s) => write!(f, "parse error: {}", s),
20 }
21 }
22}
23
24impl std::error::Error for ZoonError {}
25
26pub type Result<T> = std::result::Result<T, ZoonError>;
27
28#[cfg(test)]
29mod tests {
30 use super::*;
31 use serde::{Deserialize, Serialize};
32
33 #[derive(Debug, PartialEq, Serialize, Deserialize)]
34 struct User {
35 id: i32,
36 name: String,
37 role: String,
38 active: bool,
39 }
40
41 #[test]
42 fn test_tabular_encode() {
43 let users = vec![
44 User { id: 1, name: "Alice".into(), role: "Admin".into(), active: true },
45 User { id: 2, name: "Bob".into(), role: "User".into(), active: false },
46 ];
47
48 let encoded = encode(&users).unwrap();
49 assert!(encoded.starts_with("# "));
50 assert!(encoded.contains("Alice"));
51 assert!(encoded.contains("Bob"));
52 }
53
54 #[test]
55 fn test_inline_encode() {
56 #[derive(Serialize)]
57 struct Config {
58 host: String,
59 port: i32,
60 ssl: bool,
61 }
62
63 let cfg = Config { host: "localhost".into(), port: 3000, ssl: true };
64 let encoded = encode(&cfg).unwrap();
65 assert!(encoded.contains("host=localhost"));
66 assert!(encoded.contains("port:3000"));
67 assert!(encoded.contains("ssl:y"));
68 }
69
70 #[test]
71 fn test_decode_tabular() {
72 let input = "# id:i name:s role:s active:b\n1 Alice Admin 1\n2 Bob User 0";
73 let users: Vec<User> = decode(input).unwrap();
74 assert_eq!(users.len(), 2);
75 assert_eq!(users[0].name, "Alice");
76 assert_eq!(users[1].active, false);
77 }
78
79 #[test]
80 fn test_null_handling() {
81 let input = "name=test value:~";
82 #[derive(Deserialize, Debug, PartialEq)]
83 struct Data {
84 name: String,
85 value: Option<i32>,
86 }
87 let data: Data = decode(input).unwrap();
88 assert_eq!(data.value, None);
89 }
90
91 #[test]
92 fn test_empty_input() {
93 let result: Vec<User> = decode("").unwrap();
94 assert!(result.is_empty());
95 }
96
97 #[test]
98 fn test_primitive_types() {
99 #[derive(Debug, PartialEq, Serialize, Deserialize)]
100 struct Item {
101 n: String,
102 c: i32,
103 f: bool,
104 }
105
106 let data = vec![
107 Item { n: "A B".into(), c: 10, f: true },
108 Item { n: "C".into(), c: 0, f: false },
109 ];
110
111 let encoded = encode(&data).unwrap();
112 assert!(encoded.contains("A_B"));
113 assert!(encoded.contains("10"));
114 assert!(encoded.contains("1") || encoded.contains("true"));
115 }
116
117 #[test]
118 fn test_nested_map() {
119 use std::collections::HashMap;
120
121 let mut inner_a = HashMap::new();
122 inner_a.insert("x".to_string(), 1);
123 let mut inner_b = HashMap::new();
124 inner_b.insert("y".to_string(), 2);
125
126 let mut data: HashMap<String, HashMap<String, i32>> = HashMap::new();
127 data.insert("a".to_string(), inner_a);
128 data.insert("b".to_string(), inner_b);
129
130 let encoded = encode(&data).unwrap();
131 assert!(encoded.contains("a:{x:1}"));
132 }
133
134 #[test]
135 fn test_special_characters() {
136 #[derive(Debug, Serialize, Deserialize)]
137 struct Data {
138 text: String,
139 }
140
141 let data = vec![Data { text: "Hello_World".into() }];
142 let encoded = encode(&data).unwrap();
143
144 let decoded: Vec<Data> = decode(&encoded).unwrap();
145 assert_eq!(decoded[0].text, "Hello World");
146 }
147
148 #[test]
149 fn test_float_handling() {
150 #[derive(Debug, Serialize, Deserialize)]
151 struct Metric {
152 name: String,
153 value: f64,
154 }
155
156 let data = vec![
157 Metric { name: "cpu".into(), value: 0.75 },
158 Metric { name: "mem".into(), value: 0.92 },
159 ];
160
161 let encoded = encode(&data).unwrap();
162 assert!(encoded.contains("0.75"));
163 }
164
165 #[test]
166 fn test_single_object() {
167 #[derive(Debug, PartialEq, Serialize, Deserialize)]
168 struct ServerConfig {
169 host: String,
170 port: i32,
171 ssl: bool,
172 }
173
174 let cfg = ServerConfig { host: "api.example.com".into(), port: 8080, ssl: false };
175 let encoded = encode(&cfg).unwrap();
176 assert!(encoded.contains("host=api.example.com"));
177 assert!(encoded.contains("port:8080"));
178 assert!(encoded.contains("ssl:n"));
179 }
180
181 #[test]
182 fn test_decode_with_booleans() {
183 let input = "# name:s active:b\nAlice 1\nBob 0";
184 #[derive(Deserialize, Debug)]
185 struct Row {
186 name: String,
187 active: bool,
188 }
189 let rows: Vec<Row> = decode(input).unwrap();
190 assert_eq!(rows[0].active, true);
191 assert_eq!(rows[1].active, false);
192 }
193
194 #[test]
195 fn test_decode_numbers() {
196 let input = "# name:s price:i\nWidget 1999\nGadget 2950";
197 #[derive(Deserialize, Debug)]
198 struct Product {
199 name: String,
200 price: i32,
201 }
202 let products: Vec<Product> = decode(input).unwrap();
203 assert_eq!(products[0].price, 1999);
204 assert_eq!(products[1].price, 2950);
205 }
206
207 #[test]
208 fn test_roundtrip_simple() {
209 let users = vec![
210 User { id: 1, name: "Alice".into(), role: "Admin".into(), active: true },
211 User { id: 2, name: "Bob".into(), role: "User".into(), active: false },
212 ];
213 let encoded = encode(&users).unwrap();
214 assert!(encoded.contains("Alice"));
215 assert!(encoded.contains("Bob"));
216 }
217
218 #[test]
219 fn test_roundtrip_with_numbers() {
220 #[derive(Serialize, Debug)]
221 struct Product {
222 name: String,
223 price: f64,
224 stock: i32,
225 }
226 let data = vec![
227 Product { name: "Widget".into(), price: 19.99, stock: 100 },
228 Product { name: "Gadget".into(), price: 29.50, stock: 50 },
229 ];
230 let encoded = encode(&data).unwrap();
231 assert!(encoded.contains("19.99"));
232 assert!(encoded.contains("29.5"));
233 }
234
235 #[test]
236 fn test_roundtrip_with_booleans() {
237 #[derive(Serialize, Deserialize, Debug)]
238 struct Row {
239 name: String,
240 active: bool,
241 }
242 let data = vec![
243 Row { name: "Alice".into(), active: true },
244 Row { name: "Bob".into(), active: false },
245 ];
246 let encoded = encode(&data).unwrap();
247 assert!(encoded.contains("1") || encoded.contains("true"));
248 }
249
250 #[test]
251 fn test_roundtrip_with_nulls() {
252 #[derive(Serialize, Debug)]
253 struct Row {
254 name: String,
255 email: Option<String>,
256 }
257 let data = vec![
258 Row { name: "Alice".into(), email: Some("alice@example.com".into()) },
259 Row { name: "Bob".into(), email: None },
260 ];
261 let encoded = encode(&data).unwrap();
262 assert!(encoded.contains("~"));
263 }
264
265 #[test]
266 fn test_token_reduction() {
267 #[derive(Serialize)]
268 struct Row {
269 id: i32,
270 name: String,
271 status: String,
272 level: i32,
273 }
274 let data: Vec<Row> = (1..=10).map(|i| Row {
275 id: i,
276 name: "User".into(),
277 status: "active".into(),
278 level: 1,
279 }).collect();
280 let encoded = encode(&data).unwrap();
281 assert!(encoded.len() < 300);
282 }
283
284 #[test]
285 fn test_aliases() {
286 #[derive(Serialize, Deserialize, Debug, PartialEq)]
287 struct Status {
288 state: String,
289 }
290 #[derive(Serialize, Deserialize, Debug, PartialEq)]
291 struct Infra {
292 postgres: Status,
293 redis: Status,
294 }
295 #[derive(Serialize, Deserialize, Debug, PartialEq)]
296 struct System {
297 infrastructure: Infra,
298 }
299
300 let data = vec![
301 System { infrastructure: Infra { postgres: Status { state: "up".into() }, redis: Status { state: "up".into() } } },
302 System { infrastructure: Infra { postgres: Status { state: "down".into() }, redis: Status { state: "down".into() } } },
303 ];
304
305 let encoded = encode(&data).unwrap();
306 assert!(encoded.contains("%")); let decoded: Vec<System> = decode(&encoded).unwrap();
309 assert_eq!(decoded[0].infrastructure.postgres.state, "up");
310 assert_eq!(decoded[1].infrastructure.redis.state, "down");
311 }
312
313 #[test]
314 fn test_constants() {
315 #[derive(Serialize, Deserialize, Debug, PartialEq)]
316 struct Log {
317 level: String,
318 msg: String,
319 region: String,
320 }
321 let data = vec![
322 Log { level: "INFO".into(), msg: "Start".into(), region: "us-east-1".into() },
323 Log { level: "INFO".into(), msg: "Process".into(), region: "us-east-1".into() },
324 Log { level: "INFO".into(), msg: "End".into(), region: "us-east-1".into() },
325 ];
326 let encoded = encode(&data).unwrap();
327 assert!(encoded.contains("@level=INFO"));
328 assert!(encoded.contains("@region=us-east-1"));
329
330 let decoded: Vec<Log> = decode(&encoded).unwrap();
331 assert_eq!(decoded[1].level, "INFO");
332 assert_eq!(decoded[2].region, "us-east-1");
333 }
334
335 #[test]
336 fn test_explicit_row_count() {
337 #[derive(Serialize, Deserialize, Debug, PartialEq)]
338 struct Metric {
339 id: i32,
340 status: String,
341 }
342 let data: Vec<Metric> = (1..=3).map(|i| Metric { id: i, status: "ok".into() }).collect();
345
346 let encoded = encode(&data).unwrap();
347 assert!(encoded.contains("+3"));
348
349 let decoded: Vec<Metric> = decode(&encoded).unwrap();
350 assert_eq!(decoded.len(), 3);
351 assert_eq!(decoded[2].id, 3);
352 assert_eq!(decoded[0].status, "ok");
353 }
354}