1pub 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
22pub 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
63pub fn encode_default<V: IntoJsonValue>(value: V) -> ToonResult<String> {
97 encode(value, &EncodeOptions::default())
98}
99
100pub 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
130pub 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 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
238fn 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 for value in first_obj.values() {
255 if !is_primitive(value) {
256 return None;
257 }
258 }
259
260 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
280fn is_primitive(value: &Value) -> bool {
282 matches!(
283 value,
284 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
285 )
286}
287
288fn 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}