Skip to main content

toon_format/decode/
mod.rs

1//! Decoder Implementation
2pub mod expansion;
3#[cfg(feature = "layout")]
4pub(crate) mod layout_builder;
5pub mod parser;
6pub mod scanner;
7pub mod validation;
8
9use serde_json::Value;
10
11use crate::types::{
12    DecodeOptions,
13    ToonResult,
14};
15
16/// Decode a TOON string into any deserializable type.
17///
18/// This function accepts any type implementing `serde::Deserialize`, including:
19/// - Custom structs with `#[derive(Deserialize)]`
20/// - `serde_json::Value`
21/// - Built-in types (Vec, HashMap, etc.)
22///
23/// # Examples
24///
25/// **With custom structs:**
26/// ```
27/// use serde::Deserialize;
28/// use toon_format::{
29///     decode,
30///     DecodeOptions,
31/// };
32///
33/// #[derive(Deserialize, Debug, PartialEq)]
34/// struct User {
35///     name: String,
36///     age: u32,
37/// }
38///
39/// let toon = "name: Alice\nage: 30";
40/// let user: User = decode(toon, &DecodeOptions::default())?;
41/// assert_eq!(user.name, "Alice");
42/// assert_eq!(user.age, 30);
43/// # Ok::<(), toon_format::ToonError>(())
44/// ```
45///
46/// **With JSON values:**
47/// ```
48/// use serde_json::{
49///     json,
50///     Value,
51/// };
52/// use toon_format::{
53///     decode,
54///     DecodeOptions,
55/// };
56///
57/// let input = "name: Alice\nage: 30";
58/// let result: Value = decode(input, &DecodeOptions::default())?;
59/// assert_eq!(result["name"], json!("Alice"));
60/// # Ok::<(), toon_format::ToonError>(())
61/// ```
62pub fn decode<T: serde::de::DeserializeOwned>(
63    input: &str,
64    options: &DecodeOptions,
65) -> ToonResult<T> {
66    let mut parser = parser::Parser::new(input, options.clone())?;
67    let value = parser.parse()?;
68    let final_value = apply_path_expansion(value, options)?;
69    serde_json::from_value(final_value)
70        .map_err(|e| crate::types::ToonError::DeserializationError(e.to_string()))
71}
72
73/// Apply path expansion to a decoded value if enabled in `options`.
74///
75/// Shared by [`decode`] and [`decode_with_layout`] so both paths see the same
76/// post-parse transformation.
77fn apply_path_expansion(value: Value, options: &DecodeOptions) -> ToonResult<Value> {
78    use crate::types::PathExpansionMode;
79    if options.expand_paths != PathExpansionMode::Off {
80        let json_value = crate::types::JsonValue::from(value);
81        let expanded =
82            expansion::expand_paths_recursive(json_value, options.expand_paths, options.strict)?;
83        Ok(Value::from(expanded))
84    } else {
85        Ok(value)
86    }
87}
88
89/// Decode with strict validation enabled (validates array lengths,
90/// indentation).
91///
92/// # Examples
93///
94/// ```
95/// use serde_json::{
96///     json,
97///     Value,
98/// };
99/// use toon_format::decode_strict;
100///
101/// // Valid array length
102/// let result: Value = decode_strict("items[2]: a,b")?;
103/// assert_eq!(result["items"], json!(["a", "b"]));
104///
105/// // Invalid array length (will error)
106/// assert!(decode_strict::<Value>("items[3]: a,b").is_err());
107/// # Ok::<(), toon_format::ToonError>(())
108/// ```
109pub fn decode_strict<T: serde::de::DeserializeOwned>(input: &str) -> ToonResult<T> {
110    decode(input, &DecodeOptions::new().with_strict(true))
111}
112
113/// Decode with strict validation and additional options.
114///
115/// # Examples
116///
117/// ```
118/// use serde_json::{
119///     json,
120///     Value,
121/// };
122/// use toon_format::{
123///     decode_strict_with_options,
124///     DecodeOptions,
125/// };
126///
127/// let options = DecodeOptions::new()
128///     .with_strict(true)
129///     .with_delimiter(toon_format::Delimiter::Pipe);
130/// let result: Value = decode_strict_with_options("items[2|]: a|b", &options)?;
131/// assert_eq!(result["items"], json!(["a", "b"]));
132/// # Ok::<(), toon_format::ToonError>(())
133/// ```
134pub fn decode_strict_with_options<T: serde::de::DeserializeOwned>(
135    input: &str,
136    options: &DecodeOptions,
137) -> ToonResult<T> {
138    let opts = options.clone().with_strict(true);
139    decode(input, &opts)
140}
141
142/// Decode without type coercion (strings remain strings).
143///
144/// # Examples
145///
146/// ```
147/// use serde_json::{
148///     json,
149///     Value,
150/// };
151/// use toon_format::decode_no_coerce;
152///
153/// // Without coercion: quoted strings that look like numbers stay as strings
154/// let result: Value = decode_no_coerce("value: \"123\"")?;
155/// assert_eq!(result["value"], json!("123"));
156///
157/// // With default coercion: unquoted "true" becomes boolean
158/// let result: Value = toon_format::decode_default("value: true")?;
159/// assert_eq!(result["value"], json!(true));
160/// # Ok::<(), toon_format::ToonError>(())
161/// ```
162pub fn decode_no_coerce<T: serde::de::DeserializeOwned>(input: &str) -> ToonResult<T> {
163    decode(input, &DecodeOptions::new().with_coerce_types(false))
164}
165
166/// Decode without type coercion and with additional options.
167///
168/// # Examples
169///
170/// ```
171/// use serde_json::{
172///     json,
173///     Value,
174/// };
175/// use toon_format::{
176///     decode_no_coerce_with_options,
177///     DecodeOptions,
178/// };
179///
180/// let options = DecodeOptions::new()
181///     .with_coerce_types(false)
182///     .with_strict(false);
183/// let result: Value = decode_no_coerce_with_options("value: \"123\"", &options)?;
184/// assert_eq!(result["value"], json!("123"));
185/// # Ok::<(), toon_format::ToonError>(())
186/// ```
187pub fn decode_no_coerce_with_options<T: serde::de::DeserializeOwned>(
188    input: &str,
189    options: &DecodeOptions,
190) -> ToonResult<T> {
191    let opts = options.clone().with_coerce_types(false);
192    decode(input, &opts)
193}
194
195/// Decode with default options (strict mode, type coercion enabled).
196///
197/// Works with any type implementing `serde::Deserialize`.
198///
199/// # Examples
200///
201/// **With structs:**
202/// ```
203/// use serde::Deserialize;
204/// use toon_format::decode_default;
205///
206/// #[derive(Deserialize)]
207/// struct Person {
208///     name: String,
209///     age: u32,
210/// }
211///
212/// let input = "name: Alice\nage: 30";
213/// let person: Person = decode_default(input)?;
214/// assert_eq!(person.name, "Alice");
215/// # Ok::<(), toon_format::ToonError>(())
216/// ```
217///
218/// **With JSON values:**
219/// ```
220/// use serde_json::{
221///     json,
222///     Value,
223/// };
224/// use toon_format::decode_default;
225///
226/// let input = "tags[3]: reading,gaming,coding";
227/// let result: Value = decode_default(input)?;
228/// assert_eq!(result["tags"], json!(["reading", "gaming", "coding"]));
229/// # Ok::<(), toon_format::ToonError>(())
230/// ```
231pub fn decode_default<T: serde::de::DeserializeOwned>(input: &str) -> ToonResult<T> {
232    decode(input, &DecodeOptions::default())
233}
234
235/// Decode a TOON document and return the value alongside layout metadata
236/// describing how the document was actually written on the wire.
237///
238/// Available only when the `layout` cargo feature is enabled.
239///
240/// **Experimental.** This API supports independent exploration of schema
241/// and tooling use cases and is not part of the TOON specification.
242/// See [`crate::layout`] for the metadata types.
243///
244/// # Pointer semantics with `expand_paths`
245///
246/// JSON pointers in the returned [`Layout`](crate::layout::Layout) reflect
247/// the document's *pre-expansion* structure — i.e. the key names the parser
248/// actually saw on the wire. When `options.expand_paths` is `Safe`, the
249/// returned value is restructured (e.g. `a.b: 1` becomes `{"a": {"b": 1}}`)
250/// but layout pointers still address the original keys. For unambiguous
251/// pointer-to-value lookups, prefer `PathExpansionMode::Off` (the default).
252///
253/// # Examples
254///
255/// ```
256/// use toon_format::{
257///     decode_with_layout,
258///     DecodeOptions,
259///     NodeLayout,
260/// };
261///
262/// let toon = "users[2]{id,name}:\n  1,Alice\n  2,Bob";
263/// let (_value, layout) = decode_with_layout(toon, &DecodeOptions::default())?;
264///
265/// assert!(matches!(
266///     layout.get("/users"),
267///     Some(NodeLayout::Tabular { .. })
268/// ));
269/// # Ok::<(), toon_format::ToonError>(())
270/// ```
271#[cfg(feature = "layout")]
272pub fn decode_with_layout(
273    input: &str,
274    options: &DecodeOptions,
275) -> ToonResult<(Value, crate::layout::Layout)> {
276    let mut parser = parser::Parser::new(input, options.clone())?.with_layout();
277    let value = parser.parse()?;
278    let final_value = apply_path_expansion(value, options)?;
279    let layout = parser.take_layout().unwrap_or_default();
280    Ok((final_value, layout))
281}
282
283#[cfg(test)]
284mod tests {
285    use core::f64;
286
287    use serde_json::json;
288
289    use super::*;
290
291    #[test]
292    fn test_decode_null() {
293        assert_eq!(decode_default::<Value>("null").unwrap(), json!(null));
294    }
295
296    #[test]
297    fn test_decode_bool() {
298        assert_eq!(decode_default::<Value>("true").unwrap(), json!(true));
299        assert_eq!(decode_default::<Value>("false").unwrap(), json!(false));
300    }
301
302    #[test]
303    fn test_decode_number() {
304        assert_eq!(decode_default::<Value>("42").unwrap(), json!(42));
305        assert_eq!(
306            decode_default::<Value>("3.141592653589793").unwrap(),
307            json!(f64::consts::PI)
308        );
309        assert_eq!(decode_default::<Value>("-5").unwrap(), json!(-5));
310    }
311
312    #[test]
313    fn test_decode_string() {
314        assert_eq!(decode_default::<Value>("hello").unwrap(), json!("hello"));
315        assert_eq!(
316            decode_default::<Value>("\"hello world\"").unwrap(),
317            json!("hello world")
318        );
319    }
320
321    #[test]
322    fn test_decode_simple_object() {
323        let input = "name: Alice\nage: 30";
324        let result: Value = decode_default(input).unwrap();
325        assert_eq!(result["name"], json!("Alice"));
326        assert_eq!(result["age"], json!(30));
327    }
328
329    #[test]
330    fn test_decode_primitive_array() {
331        let input = "tags[3]: reading,gaming,coding";
332        let result: Value = decode_default(input).unwrap();
333        assert_eq!(result["tags"], json!(["reading", "gaming", "coding"]));
334    }
335
336    #[test]
337    fn test_decode_tabular_array() {
338        let input = "users[2]{id,name,role}:\n  1,Alice,admin\n  2,Bob,user";
339        let result: Value = decode_default(input).unwrap();
340        assert_eq!(
341            result["users"],
342            json!([
343                {"id": 1, "name": "Alice", "role": "admin"},
344                {"id": 2, "name": "Bob", "role": "user"}
345            ])
346        );
347    }
348
349    #[test]
350    fn test_decode_empty_array() {
351        let input = "items[0]:";
352        let result: Value = decode_default(input).unwrap();
353        assert_eq!(result["items"], json!([]));
354    }
355
356    #[test]
357    fn test_decode_quoted_strings() {
358        let input = "tags[3]: \"true\",\"42\",\"-3.14\"";
359        let result: Value = decode_default(input).unwrap();
360        assert_eq!(result["tags"], json!(["true", "42", "-3.14"]));
361    }
362}