toon_format/encode/
mod.rs

1//! Encoder Implementation
2pub mod primitives;
3pub mod writer;
4use indexmap::IndexMap;
5
6use crate::{
7    constants::MAX_DEPTH,
8    types::{
9        EncodeOptions,
10        IntoJsonValue,
11        JsonValue as Value,
12        ToonError,
13        ToonResult,
14    },
15    utils::{
16        normalize,
17        validation::validate_depth,
18        QuotingContext,
19    },
20};
21
22/// Encode a JSON value to TOON format with custom options.
23///
24/// This function accepts either `JsonValue` or `serde_json::Value` and converts
25/// automatically.
26///
27/// # Examples
28///
29/// ```
30/// use toon_format::{encode, EncodeOptions, Delimiter};
31/// use serde_json::json;
32///
33/// let data = json!({"tags": ["a", "b", "c"]});
34/// let options = EncodeOptions::new().with_delimiter(Delimiter::Pipe);
35/// let toon = encode(&data, &options)?;
36/// assert!(toon.contains("|"));
37/// # Ok::<(), toon_format::ToonError>(())
38/// ```
39pub fn encode<V: IntoJsonValue>(value: V, options: &EncodeOptions) -> ToonResult<String> {
40    let json_value = value.into_json_value();
41    encode_impl(&json_value, options)
42}
43
44fn encode_impl(value: &Value, options: &EncodeOptions) -> ToonResult<String> {
45    let normalized: Value = normalize(value.clone());
46    let mut writer = writer::Writer::new(options.clone());
47
48    match &normalized {
49        Value::Array(arr) => {
50            write_array(&mut writer, None, arr, 0)?;
51        }
52        Value::Object(obj) => {
53            write_object(&mut writer, obj, 0)?;
54        }
55        _ => {
56            write_primitive_value(&mut writer, &normalized, QuotingContext::ObjectValue)?;
57        }
58    }
59
60    Ok(writer.finish())
61}
62
63/// Encode a JSON value to TOON format with default options.
64///
65/// This function accepts either `JsonValue` or `serde_json::Value` and converts
66/// automatically.
67///
68/// # Examples
69///
70/// ```
71/// use toon_format::encode_default;
72/// use serde_json::json;
73///
74/// // Simple object
75/// let data = json!({"name": "Alice", "age": 30});
76/// let toon = encode_default(&data)?;
77/// assert!(toon.contains("name: Alice"));
78/// assert!(toon.contains("age: 30"));
79///
80/// // Primitive array
81/// let data = json!({"tags": ["reading", "gaming", "coding"]});
82/// let toon = encode_default(&data)?;
83/// assert_eq!(toon, "tags[3]: reading,gaming,coding");
84///
85/// // Tabular array
86/// let data = json!({
87///     "users": [
88///         {"id": 1, "name": "Alice"},
89///         {"id": 2, "name": "Bob"}
90///     ]
91/// });
92/// let toon = encode_default(&data)?;
93/// assert!(toon.contains("users[2]{id,name}:"));
94/// # Ok::<(), toon_format::ToonError>(())
95/// ```
96pub fn encode_default<V: IntoJsonValue>(value: V) -> ToonResult<String> {
97    encode(value, &EncodeOptions::default())
98}
99
100/// Encode a JSON object to TOON format (errors if not an object).
101///
102/// This function accepts either `JsonValue` or `serde_json::Value` and converts
103/// automatically.
104///
105/// # Examples
106///
107/// ```
108/// use toon_format::{encode_object, EncodeOptions};
109/// use serde_json::json;
110///
111/// let data = json!({"name": "Alice", "age": 30});
112/// let toon = encode_object(&data, &EncodeOptions::default())?;
113/// assert!(toon.contains("name: Alice"));
114///
115/// // Will error if not an object
116/// assert!(encode_object(&json!(42), &EncodeOptions::default()).is_err());
117/// # Ok::<(), toon_format::ToonError>(())
118/// ```
119pub fn encode_object<V: IntoJsonValue>(value: V, options: &EncodeOptions) -> ToonResult<String> {
120    let json_value = value.into_json_value();
121    if !json_value.is_object() {
122        return Err(ToonError::TypeMismatch {
123            expected: "object".to_string(),
124            found: value_type_name(&json_value).to_string(),
125        });
126    }
127    encode_impl(&json_value, options)
128}
129
130/// Encode a JSON array to TOON format (errors if not an array).
131///
132/// This function accepts either `JsonValue` or `serde_json::Value` and converts
133/// automatically.
134///
135/// # Examples
136///
137/// ```
138/// use toon_format::{encode_array, EncodeOptions};
139/// use serde_json::json;
140///
141/// let data = json!(["a", "b", "c"]);
142/// let toon = encode_array(&data, &EncodeOptions::default())?;
143/// assert_eq!(toon, "[3]: a,b,c");
144///
145/// // Will error if not an array
146/// assert!(encode_array(&json!({"key": "value"}), &EncodeOptions::default()).is_err());
147/// # Ok::<(), toon_format::ToonError>(())
148/// ```
149pub fn encode_array<V: IntoJsonValue>(value: V, options: &EncodeOptions) -> ToonResult<String> {
150    let json_value = value.into_json_value();
151    if !json_value.is_array() {
152        return Err(ToonError::TypeMismatch {
153            expected: "array".to_string(),
154            found: value_type_name(&json_value).to_string(),
155        });
156    }
157    encode_impl(&json_value, options)
158}
159
160fn value_type_name(value: &Value) -> &'static str {
161    match value {
162        Value::Null => "null",
163        Value::Bool(_) => "boolean",
164        Value::Number(_) => "number",
165        Value::String(_) => "string",
166        Value::Array(_) => "array",
167        Value::Object(_) => "object",
168    }
169}
170
171fn write_object(
172    writer: &mut writer::Writer,
173    obj: &IndexMap<String, Value>,
174    depth: usize,
175) -> ToonResult<()> {
176    validate_depth(depth, MAX_DEPTH)?;
177
178    let keys: Vec<&String> = obj.keys().collect();
179
180    for (i, key) in keys.iter().enumerate() {
181        if i > 0 {
182            writer.write_newline()?;
183        }
184
185        if depth > 0 {
186            writer.write_indent(depth)?;
187        }
188
189        let value = &obj[*key];
190
191        match value {
192            Value::Array(arr) => {
193                write_array(writer, Some(key), arr, depth)?;
194            }
195            Value::Object(nested_obj) => {
196                writer.write_key(key)?;
197                writer.write_char(':')?;
198                writer.write_newline()?;
199                write_object(writer, nested_obj, depth + 1)?;
200            }
201            _ => {
202                writer.write_key(key)?;
203                writer.write_char(':')?;
204                writer.write_char(' ')?;
205                write_primitive_value(writer, value, QuotingContext::ObjectValue)?;
206            }
207        }
208    }
209
210    Ok(())
211}
212
213fn write_array(
214    writer: &mut writer::Writer,
215    key: Option<&str>,
216    arr: &[Value],
217    depth: usize,
218) -> ToonResult<()> {
219    validate_depth(depth, MAX_DEPTH)?;
220
221    if arr.is_empty() {
222        writer.write_empty_array_with_key(key)?;
223        return Ok(());
224    }
225
226    // Choose encoding format: tabular > primitive inline > nested list
227    if let Some(keys) = is_tabular_array(arr) {
228        encode_tabular_array(writer, key, arr, &keys, depth)?;
229    } else if is_primitive_array(arr) {
230        encode_primitive_array(writer, key, arr, depth)?;
231    } else {
232        encode_nested_array(writer, key, arr, depth)?;
233    }
234
235    Ok(())
236}
237
238/// Check if an array can be encoded as tabular format (uniform objects with
239/// primitive values).
240fn is_tabular_array(arr: &[Value]) -> Option<Vec<String>> {
241    if arr.is_empty() {
242        return None;
243    }
244
245    let first = arr.first()?;
246    if !first.is_object() {
247        return None;
248    }
249
250    let first_obj = first.as_object()?;
251    let keys: Vec<String> = first_obj.keys().cloned().collect();
252
253    // All values must be primitives for tabular format
254    for value in first_obj.values() {
255        if !is_primitive(value) {
256            return None;
257        }
258    }
259
260    // All objects must have the same keys and primitive values
261    for val in arr.iter().skip(1) {
262        if let Some(obj) = val.as_object() {
263            let obj_keys: Vec<String> = obj.keys().cloned().collect();
264            if keys != obj_keys {
265                return None;
266            }
267            for value in obj.values() {
268                if !is_primitive(value) {
269                    return None;
270                }
271            }
272        } else {
273            return None;
274        }
275    }
276
277    Some(keys)
278}
279
280/// Check if a value is a primitive (not array or object).
281fn is_primitive(value: &Value) -> bool {
282    matches!(
283        value,
284        Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
285    )
286}
287
288/// Check if all array elements are primitives.
289fn is_primitive_array(arr: &[Value]) -> bool {
290    arr.iter().all(is_primitive)
291}
292
293fn encode_primitive_array(
294    writer: &mut writer::Writer,
295    key: Option<&str>,
296    arr: &[Value],
297    depth: usize,
298) -> ToonResult<()> {
299    writer.write_array_header(key, arr.len(), None, depth)?;
300    writer.write_char(' ')?;
301    writer.push_active_delimiter(writer.options.delimiter);
302
303    for (i, val) in arr.iter().enumerate() {
304        if i > 0 {
305            writer.write_delimiter()?;
306        }
307        write_primitive_value(writer, val, QuotingContext::ArrayValue)?;
308    }
309    writer.pop_active_delimiter();
310
311    Ok(())
312}
313
314fn write_primitive_value(
315    writer: &mut writer::Writer,
316    value: &Value,
317    context: QuotingContext,
318) -> ToonResult<()> {
319    match value {
320        Value::Null => writer.write_str("null"),
321        Value::Bool(b) => writer.write_str(&b.to_string()),
322        Value::Number(n) => writer.write_str(&n.to_string()),
323        Value::String(s) => {
324            if writer.needs_quoting(s, context) {
325                writer.write_quoted_string(s)
326            } else {
327                writer.write_str(s)
328            }
329        }
330        _ => Err(ToonError::InvalidInput(
331            "Expected primitive value".to_string(),
332        )),
333    }
334}
335
336fn encode_tabular_array(
337    writer: &mut writer::Writer,
338    key: Option<&str>,
339    arr: &[Value],
340    keys: &[String],
341    depth: usize,
342) -> ToonResult<()> {
343    writer.write_array_header(key, arr.len(), Some(keys), depth)?;
344    writer.write_newline()?;
345
346    writer.push_active_delimiter(writer.options.delimiter);
347
348    for (row_index, obj_val) in arr.iter().enumerate() {
349        if let Some(obj) = obj_val.as_object() {
350            writer.write_indent(depth + 1)?;
351
352            for (i, key) in keys.iter().enumerate() {
353                if i > 0 {
354                    writer.write_delimiter()?;
355                }
356
357                if let Some(val) = obj.get(key) {
358                    write_primitive_value(writer, val, QuotingContext::ArrayValue)?;
359                } else {
360                    writer.write_str("null")?;
361                }
362            }
363
364            if row_index < arr.len() - 1 {
365                writer.write_newline()?;
366            }
367        }
368    }
369
370    Ok(())
371}
372
373fn encode_nested_array(
374    writer: &mut writer::Writer,
375    key: Option<&str>,
376    arr: &[Value],
377    depth: usize,
378) -> ToonResult<()> {
379    writer.write_array_header(key, arr.len(), None, depth)?;
380    writer.write_newline()?;
381    writer.push_active_delimiter(writer.options.delimiter);
382
383    for (i, val) in arr.iter().enumerate() {
384        writer.write_indent(depth + 1)?;
385        writer.write_char('-')?;
386        writer.write_char(' ')?;
387
388        match val {
389            Value::Array(inner_arr) => {
390                write_array(writer, None, inner_arr, depth + 1)?;
391            }
392            Value::Object(obj) => {
393                let keys: Vec<&String> = obj.keys().collect();
394                if let Some(first_key) = keys.first() {
395                    let first_val = &obj[*first_key];
396
397                    writer.write_key(first_key)?;
398                    writer.write_char(':')?;
399                    writer.write_char(' ')?;
400                    match first_val {
401                        Value::Array(arr) => {
402                            write_array(writer, None, arr, depth + 1)?;
403                        }
404                        Value::Object(nested_obj) => {
405                            writer.write_newline()?;
406                            write_object(writer, nested_obj, depth + 2)?;
407                        }
408                        _ => {
409                            write_primitive_value(writer, first_val, QuotingContext::ObjectValue)?;
410                        }
411                    }
412
413                    for key in keys.iter().skip(1) {
414                        writer.write_newline()?;
415                        writer.write_indent(depth + 1)?;
416                        writer.write_key(key)?;
417                        writer.write_char(':')?;
418                        writer.write_char(' ')?;
419
420                        let value = &obj[*key];
421                        match value {
422                            Value::Array(arr) => {
423                                write_array(writer, None, arr, depth + 2)?;
424                            }
425                            Value::Object(nested_obj) => {
426                                writer.write_newline()?;
427                                write_object(writer, nested_obj, depth + 3)?;
428                            }
429                            _ => {
430                                write_primitive_value(writer, value, QuotingContext::ObjectValue)?;
431                            }
432                        }
433                    }
434                }
435            }
436            _ => {
437                write_primitive_value(writer, val, QuotingContext::ArrayValue)?;
438            }
439        }
440
441        if i < arr.len() - 1 {
442            writer.write_newline()?;
443        }
444    }
445    writer.pop_active_delimiter();
446
447    Ok(())
448}
449
450#[cfg(test)]
451mod tests {
452    use core::f64;
453
454    use serde_json::json;
455
456    use super::*;
457
458    #[test]
459    fn test_encode_null() {
460        let value = json!(null);
461        assert_eq!(encode_default(&value).unwrap(), "null");
462    }
463
464    #[test]
465    fn test_encode_bool() {
466        assert_eq!(encode_default(json!(true)).unwrap(), "true");
467        assert_eq!(encode_default(json!(false)).unwrap(), "false");
468    }
469
470    #[test]
471    fn test_encode_number() {
472        assert_eq!(encode_default(json!(42)).unwrap(), "42");
473        assert_eq!(
474            encode_default(json!(f64::consts::PI)).unwrap(),
475            "3.141592653589793"
476        );
477        assert_eq!(encode_default(json!(-5)).unwrap(), "-5");
478    }
479
480    #[test]
481    fn test_encode_string() {
482        assert_eq!(encode_default(json!("hello")).unwrap(), "hello");
483        assert_eq!(encode_default(json!("hello world")).unwrap(), "hello world");
484    }
485
486    #[test]
487    fn test_encode_simple_object() {
488        let obj = json!({"name": "Alice", "age": 30});
489        let result = encode_default(&obj).unwrap();
490        assert!(result.contains("name: Alice"));
491        assert!(result.contains("age: 30"));
492    }
493
494    #[test]
495    fn test_encode_primitive_array() {
496        let obj = json!({"tags": ["reading", "gaming", "coding"]});
497        let result = encode_default(&obj).unwrap();
498        assert_eq!(result, "tags[3]: reading,gaming,coding");
499    }
500
501    #[test]
502    fn test_encode_tabular_array() {
503        let obj = json!({
504            "users": [
505                {"id": 1, "name": "Alice"},
506                {"id": 2, "name": "Bob"}
507            ]
508        });
509        let result = encode_default(&obj).unwrap();
510        assert!(result.contains("users[2]{id,name}:"));
511        assert!(result.contains("1,Alice"));
512        assert!(result.contains("2,Bob"));
513    }
514
515    #[test]
516    fn test_encode_empty_array() {
517        let obj = json!({"items": []});
518        let result = encode_default(&obj).unwrap();
519        assert_eq!(result, "items[0]:");
520    }
521
522    #[test]
523    fn test_encode_nested_object() {
524        let obj = json!({
525            "user": {
526                "name": "Alice",
527                "age": 30
528            }
529        });
530        let result = encode_default(&obj).unwrap();
531        assert!(result.contains("user:"));
532        assert!(result.contains("name: Alice"));
533        assert!(result.contains("age: 30"));
534    }
535}