toon_format/
lib.rs

1//! # TOON Format for Rust
2//!
3//! Token-Oriented Object Notation (TOON) is a compact, human-readable format
4//! designed for passing structured data to Large Language Models with
5//! significantly reduced token usage.
6//!
7//! This crate reserves the `toon-format` namespace for the official Rust
8//! implementation. Full implementation coming soon!
9//!
10//! ## Resources
11//!
12//! - [TOON Specification](https://github.com/johannschopplich/toon/blob/main/SPEC.md)
13//! - [Main Repository](https://github.com/johannschopplich/toon)
14//! - [Other Implementations](https://github.com/johannschopplich/toon#other-implementations)
15//!
16//! ## Example Usage (Future)
17//!
18//! ```ignore
19//! use toon_format::{encode, decode};
20//!
21//! let data = json!({"name": "Alice", "age": 30});
22//! let toon_string = encode(&data)?;
23//! let decoded = decode(&toon_string)?;
24//! # Ok::<(), toon_format::ToonError>(())
25//! ```
26#![warn(rustdoc::missing_crate_level_docs)]
27
28pub mod constants;
29pub mod decode;
30pub mod encode;
31#[cfg(feature = "cli")]
32pub mod tui;
33pub mod types;
34pub mod utils;
35
36pub use decode::{
37    decode,
38    decode_default,
39    decode_no_coerce,
40    decode_no_coerce_with_options,
41    decode_strict,
42    decode_strict_with_options,
43};
44pub use encode::{
45    encode,
46    encode_array,
47    encode_default,
48    encode_object,
49};
50pub use types::{
51    DecodeOptions,
52    Delimiter,
53    EncodeOptions,
54    Indent,
55    ToonError,
56};
57pub use utils::{
58    literal::{
59        is_keyword,
60        is_literal_like,
61    },
62    normalize,
63    string::{
64        escape_string,
65        is_valid_unquoted_key,
66        needs_quoting,
67    },
68};
69
70#[cfg(test)]
71mod tests {
72    use serde_json::{
73        json,
74        Value,
75    };
76
77    use crate::{
78        constants::is_keyword,
79        decode::{
80            decode_default,
81            decode_strict,
82        },
83        encode::{
84            encode,
85            encode_default,
86        },
87        types::{
88            Delimiter,
89            EncodeOptions,
90        },
91        utils::{
92            escape_string,
93            is_literal_like,
94            needs_quoting,
95            normalize,
96        },
97    };
98
99    #[test]
100    fn test_round_trip_simple() {
101        let original = json!({"name": "Alice", "age": 30});
102        let encoded = encode_default(&original).unwrap();
103        let decoded: Value = decode_default(&encoded).unwrap();
104        assert_eq!(original, decoded);
105    }
106
107    #[test]
108    fn test_round_trip_array() {
109        let original = json!({"tags": ["reading", "gaming", "coding"]});
110        let encoded = encode_default(&original).unwrap();
111        let decoded: Value = decode_default(&encoded).unwrap();
112        assert_eq!(original, decoded);
113    }
114
115    #[test]
116    fn test_round_trip_tabular() {
117        let original = json!({
118            "users": [
119                {"id": 1, "name": "Alice", "role": "admin"},
120                {"id": 2, "name": "Bob", "role": "user"}
121            ]
122        });
123        let encoded = encode_default(&original).unwrap();
124        let decoded: Value = decode_default(&encoded).unwrap();
125        assert_eq!(original, decoded);
126    }
127
128    #[test]
129    fn test_custom_delimiter() {
130        let original = json!({"tags": ["a", "b", "c"]});
131        let opts = EncodeOptions::new().with_delimiter(Delimiter::Pipe);
132        let encoded = encode(&original, &opts).unwrap();
133        assert!(encoded.contains("|"));
134
135        let decoded: Value = decode_default(&encoded).unwrap();
136        assert_eq!(original, decoded);
137    }
138
139    #[test]
140    fn test_decode_strict_helper() {
141        let input = "items[2]: a,b";
142        assert!(decode_strict::<Value>(input).is_ok());
143
144        let input = "items[3]: a,b";
145        assert!(decode_strict::<Value>(input).is_err());
146    }
147
148    #[test]
149    fn test_normalize_exported() {
150        let value = json!(f64::NAN);
151        let normalized = normalize(value.into());
152        assert_eq!(serde_json::Value::from(normalized), json!(null));
153    }
154
155    #[test]
156    fn test_utilities_exported() {
157        assert!(is_keyword("null"));
158        assert!(is_literal_like("true"));
159        assert_eq!(escape_string("hello\nworld"), "hello\\nworld");
160        assert!(needs_quoting("true", Delimiter::Comma.as_char()));
161    }
162
163    use serde::{
164        Deserialize,
165        Serialize,
166    };
167
168    #[derive(Debug, Serialize, Deserialize, PartialEq)]
169    struct TestUser {
170        name: String,
171        age: u32,
172        active: bool,
173    }
174
175    #[test]
176    fn test_encode_decode_simple_struct() {
177        use crate::{
178            decode_default,
179            encode_default,
180        };
181
182        let user = TestUser {
183            name: "Alice".to_string(),
184            age: 30,
185            active: true,
186        };
187
188        let toon = encode_default(&user).unwrap();
189        assert!(toon.contains("name: Alice"));
190        assert!(toon.contains("age: 30"));
191        assert!(toon.contains("active: true"));
192
193        let decoded: TestUser = decode_default(&toon).unwrap();
194        assert_eq!(user, decoded);
195    }
196
197    #[derive(Debug, Serialize, Deserialize, PartialEq)]
198    struct TestProduct {
199        id: u64,
200        name: String,
201        tags: Vec<String>,
202    }
203
204    #[test]
205    fn test_encode_decode_with_array() {
206        use crate::{
207            decode_default,
208            encode_default,
209        };
210
211        let product = TestProduct {
212            id: 42,
213            name: "Widget".to_string(),
214            tags: vec!["electronics".to_string(), "gadgets".to_string()],
215        };
216
217        let toon = encode_default(&product).unwrap();
218        let decoded: TestProduct = decode_default(&toon).unwrap();
219        assert_eq!(product, decoded);
220    }
221
222    #[test]
223    fn test_encode_decode_vec_of_structs() {
224        use crate::{
225            decode_default,
226            encode_default,
227        };
228
229        let users = vec![
230            TestUser {
231                name: "Alice".to_string(),
232                age: 30,
233                active: true,
234            },
235            TestUser {
236                name: "Bob".to_string(),
237                age: 25,
238                active: false,
239            },
240        ];
241
242        let toon = encode_default(&users).unwrap();
243        let decoded: Vec<TestUser> = decode_default(&toon).unwrap();
244        assert_eq!(users, decoded);
245    }
246
247    #[derive(Debug, Serialize, Deserialize, PartialEq)]
248    struct Nested {
249        outer: OuterStruct,
250    }
251
252    #[derive(Debug, Serialize, Deserialize, PartialEq)]
253    struct OuterStruct {
254        inner: InnerStruct,
255        value: i32,
256    }
257
258    #[derive(Debug, Serialize, Deserialize, PartialEq)]
259    struct InnerStruct {
260        data: String,
261    }
262
263    #[test]
264    fn test_encode_decode_nested_structs() {
265        use crate::{
266            decode_default,
267            encode_default,
268        };
269
270        let nested = Nested {
271            outer: OuterStruct {
272                inner: InnerStruct {
273                    data: "test".to_string(),
274                },
275                value: 42,
276            },
277        };
278
279        let toon = encode_default(&nested).unwrap();
280        let decoded: Nested = decode_default(&toon).unwrap();
281        assert_eq!(nested, decoded);
282    }
283
284    #[test]
285    fn test_round_trip_list_item_tabular_v3() {
286        use crate::{
287            decode_default,
288            encode_default,
289        };
290
291        let original = json!({
292            "items": [
293                {
294                    "users": [
295                        {"id": 1, "name": "Alice", "role": "admin"},
296                        {"id": 2, "name": "Bob", "role": "user"}
297                    ],
298                    "status": "active",
299                    "count": 2
300                }
301            ]
302        });
303
304        let encoded = encode_default(&original).unwrap();
305        let decoded: Value = decode_default(&encoded).unwrap();
306
307        assert_eq!(original, decoded);
308    }
309
310    #[test]
311    fn test_round_trip_complex_list_item_tabular_v3() {
312        use crate::{
313            decode_default,
314            encode_default,
315        };
316
317        let original = json!({
318            "data": [
319                {
320                    "records": [
321                        {"id": 1, "value": "x", "score": 100},
322                        {"id": 2, "value": "y", "score": 200}
323                    ],
324                    "total": 2,
325                    "status": "active"
326                },
327                {
328                    "records": [
329                        {"id": 3, "value": "z", "score": 300}
330                    ],
331                    "total": 1,
332                    "status": "pending"
333                }
334            ]
335        });
336
337        let encoded = encode_default(&original).unwrap();
338        let decoded: Value = decode_default(&encoded).unwrap();
339
340        assert_eq!(original, decoded);
341    }
342
343    #[test]
344    fn test_round_trip_mixed_list_items_v3() {
345        use crate::{
346            decode_default,
347            encode_default,
348        };
349
350        let original = json!({
351            "entries": [
352                {
353                    "type": "simple",
354                    "value": 42
355                },
356                {
357                    "people": [
358                        {"name": "Alice", "age": 30},
359                        {"name": "Bob", "age": 25}
360                    ],
361                    "type": "complex"
362                },
363                {
364                    "tags": ["a", "b", "c"],
365                    "type": "array"
366                }
367            ]
368        });
369
370        let encoded = encode_default(&original).unwrap();
371        let decoded: Value = decode_default(&encoded).unwrap();
372
373        assert_eq!(original, decoded);
374    }
375}