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}