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}