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    // Tests for direct serde serialization/deserialization
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}