toon_format/decode/
mod.rs

1//! Decoder Implementation
2pub mod expansion;
3pub mod parser;
4pub mod scanner;
5pub mod validation;
6
7use serde_json::Value;
8
9use crate::types::{
10    DecodeOptions,
11    ToonResult,
12};
13
14/// Decode a TOON string to a JSON value with custom options.
15///
16/// # Examples
17///
18/// ```
19/// use serde_json::json;
20/// use toon_format::{
21///     decode,
22///     DecodeOptions,
23///     Delimiter,
24/// };
25///
26/// let input = "name: Alice\nage: 30";
27/// let options = DecodeOptions::new().with_strict(false);
28/// let result = decode(input, &options)?;
29/// assert_eq!(result["name"], json!("Alice"));
30/// # Ok::<(), toon_format::ToonError>(())
31/// ```
32pub fn decode(input: &str, options: &DecodeOptions) -> ToonResult<Value> {
33    let mut parser = parser::Parser::new(input, options.clone())?;
34    let value = parser.parse()?;
35
36    // Apply path expansion if enabled (v1.5 feature)
37    use crate::types::PathExpansionMode;
38    if options.expand_paths != PathExpansionMode::Off {
39        let json_value = crate::types::JsonValue::from(value);
40        let expanded =
41            expansion::expand_paths_recursive(json_value, options.expand_paths, options.strict)?;
42        Ok(Value::from(expanded))
43    } else {
44        Ok(value)
45    }
46}
47
48/// Decode with strict validation enabled (validates array lengths,
49/// indentation).
50///
51/// # Examples
52///
53/// ```
54/// use serde_json::json;
55/// use toon_format::decode_strict;
56///
57/// // Valid array length
58/// let result = decode_strict("items[2]: a,b")?;
59/// assert_eq!(result["items"], json!(["a", "b"]));
60///
61/// // Invalid array length (will error)
62/// assert!(decode_strict("items[3]: a,b").is_err());
63/// # Ok::<(), toon_format::ToonError>(())
64/// ```
65pub fn decode_strict(input: &str) -> ToonResult<Value> {
66    decode(input, &DecodeOptions::new().with_strict(true))
67}
68
69/// Decode with strict validation and additional options.
70///
71/// # Examples
72///
73/// ```
74/// use serde_json::json;
75/// use toon_format::{
76///     decode_strict_with_options,
77///     DecodeOptions,
78/// };
79///
80/// let options = DecodeOptions::new()
81///     .with_strict(true)
82///     .with_delimiter(toon_format::Delimiter::Pipe);
83/// let result = decode_strict_with_options("items[2|]: a|b", &options)?;
84/// assert_eq!(result["items"], json!(["a", "b"]));
85/// # Ok::<(), toon_format::ToonError>(())
86/// ```
87pub fn decode_strict_with_options(input: &str, options: &DecodeOptions) -> ToonResult<Value> {
88    let opts = options.clone().with_strict(true);
89    decode(input, &opts)
90}
91
92/// Decode without type coercion (strings remain strings).
93///
94/// # Examples
95///
96/// ```
97/// use serde_json::json;
98/// use toon_format::decode_no_coerce;
99///
100/// // Without coercion: quoted strings that look like numbers stay as strings
101/// let result = decode_no_coerce("value: \"123\"")?;
102/// assert_eq!(result["value"], json!("123"));
103///
104/// // With default coercion: unquoted "true" becomes boolean
105/// let result = toon_format::decode_default("value: true")?;
106/// assert_eq!(result["value"], json!(true));
107/// # Ok::<(), toon_format::ToonError>(())
108/// ```
109pub fn decode_no_coerce(input: &str) -> ToonResult<Value> {
110    decode(input, &DecodeOptions::new().with_coerce_types(false))
111}
112
113/// Decode without type coercion and with additional options.
114///
115/// # Examples
116///
117/// ```
118/// use serde_json::json;
119/// use toon_format::{
120///     decode_no_coerce_with_options,
121///     DecodeOptions,
122/// };
123///
124/// let options = DecodeOptions::new()
125///     .with_coerce_types(false)
126///     .with_strict(false);
127/// let result = decode_no_coerce_with_options("value: \"123\"", &options)?;
128/// assert_eq!(result["value"], json!("123"));
129/// # Ok::<(), toon_format::ToonError>(())
130/// ```
131pub fn decode_no_coerce_with_options(input: &str, options: &DecodeOptions) -> ToonResult<Value> {
132    let opts = options.clone().with_coerce_types(false);
133    decode(input, &opts)
134}
135
136/// Decode with default options (strict mode, type coercion enabled).
137///
138/// # Examples
139///
140/// ```
141/// use serde_json::json;
142/// use toon_format::decode_default;
143///
144/// // Simple object
145/// let input = "name: Alice\nage: 30";
146/// let result = decode_default(input)?;
147/// assert_eq!(result["name"], json!("Alice"));
148/// assert_eq!(result["age"], json!(30));
149///
150/// // Primitive array
151/// let input = "tags[3]: reading,gaming,coding";
152/// let result = decode_default(input)?;
153/// assert_eq!(result["tags"], json!(["reading", "gaming", "coding"]));
154///
155/// // Tabular array
156/// let input = "users[2]{id,name}:\n  1,Alice\n  2,Bob";
157/// let result = decode_default(input)?;
158/// assert_eq!(result["users"][0]["name"], json!("Alice"));
159/// # Ok::<(), toon_format::ToonError>(())
160/// ```
161pub fn decode_default(input: &str) -> ToonResult<Value> {
162    decode(input, &DecodeOptions::default())
163}
164
165#[cfg(test)]
166mod tests {
167    use core::f64;
168
169    use serde_json::json;
170
171    use super::*;
172
173    #[test]
174    fn test_decode_null() {
175        assert_eq!(decode_default("null").unwrap(), json!(null));
176    }
177
178    #[test]
179    fn test_decode_bool() {
180        assert_eq!(decode_default("true").unwrap(), json!(true));
181        assert_eq!(decode_default("false").unwrap(), json!(false));
182    }
183
184    #[test]
185    fn test_decode_number() {
186        assert_eq!(decode_default("42").unwrap(), json!(42));
187        assert_eq!(
188            decode_default("3.141592653589793").unwrap(),
189            json!(f64::consts::PI)
190        );
191        assert_eq!(decode_default("-5").unwrap(), json!(-5));
192    }
193
194    #[test]
195    fn test_decode_string() {
196        assert_eq!(decode_default("hello").unwrap(), json!("hello"));
197        assert_eq!(
198            decode_default("\"hello world\"").unwrap(),
199            json!("hello world")
200        );
201    }
202
203    #[test]
204    fn test_decode_simple_object() {
205        let input = "name: Alice\nage: 30";
206        let result = decode_default(input).unwrap();
207        assert_eq!(result["name"], json!("Alice"));
208        assert_eq!(result["age"], json!(30));
209    }
210
211    #[test]
212    fn test_decode_primitive_array() {
213        let input = "tags[3]: reading,gaming,coding";
214        let result = decode_default(input).unwrap();
215        assert_eq!(result["tags"], json!(["reading", "gaming", "coding"]));
216    }
217
218    #[test]
219    fn test_decode_tabular_array() {
220        let input = "users[2]{id,name,role}:\n  1,Alice,admin\n  2,Bob,user";
221        let result = decode_default(input).unwrap();
222        assert_eq!(
223            result["users"],
224            json!([
225                {"id": 1, "name": "Alice", "role": "admin"},
226                {"id": 2, "name": "Bob", "role": "user"}
227            ])
228        );
229    }
230
231    #[test]
232    fn test_decode_empty_array() {
233        let input = "items[0]:";
234        let result = decode_default(input).unwrap();
235        assert_eq!(result["items"], json!([]));
236    }
237
238    #[test]
239    fn test_decode_quoted_strings() {
240        let input = "tags[3]: \"true\",\"42\",\"-3.14\"";
241        let result = decode_default(input).unwrap();
242        assert_eq!(result["tags"], json!(["true", "42", "-3.14"]));
243    }
244}