zoon_format/
lib.rs

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("%")); // Check for alias usage
307        
308        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        // id 1..3, status "ok".
343        // id is i+ (non consuming), status is constant (non consuming) -> row is implicit.
344        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}