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