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 },
19};
20
21pub fn encode<V: IntoJsonValue>(value: V, options: &EncodeOptions) -> ToonResult<String> {
39 let json_value = value.into_json_value();
40 encode_impl(&json_value, options)
41}
42
43fn encode_impl(value: &Value, options: &EncodeOptions) -> ToonResult<String> {
44 let normalized: Value = normalize(value.clone());
45 let mut writer = writer::Writer::new(options.clone());
46
47 match &normalized {
48 Value::Array(arr) => {
49 write_array(&mut writer, None, arr, 0)?;
50 }
51 Value::Object(obj) => {
52 write_object(&mut writer, obj, 0)?;
53 }
54 _ => {
55 write_primitive_value(&mut writer, &normalized)?;
56 }
57 }
58
59 Ok(writer.finish())
60}
61
62pub fn encode_default<V: IntoJsonValue>(value: V) -> ToonResult<String> {
96 encode(value, &EncodeOptions::default())
97}
98
99pub fn encode_object<V: IntoJsonValue>(value: V, options: &EncodeOptions) -> ToonResult<String> {
119 let json_value = value.into_json_value();
120 if !json_value.is_object() {
121 return Err(ToonError::TypeMismatch {
122 expected: "object".to_string(),
123 found: value_type_name(&json_value).to_string(),
124 });
125 }
126 encode_impl(&json_value, options)
127}
128
129pub fn encode_array<V: IntoJsonValue>(value: V, options: &EncodeOptions) -> ToonResult<String> {
149 let json_value = value.into_json_value();
150 if !json_value.is_array() {
151 return Err(ToonError::TypeMismatch {
152 expected: "array".to_string(),
153 found: value_type_name(&json_value).to_string(),
154 });
155 }
156 encode_impl(&json_value, options)
157}
158
159fn value_type_name(value: &Value) -> &'static str {
160 match value {
161 Value::Null => "null",
162 Value::Bool(_) => "boolean",
163 Value::Number(_) => "number",
164 Value::String(_) => "string",
165 Value::Array(_) => "array",
166 Value::Object(_) => "object",
167 }
168}
169
170fn write_object(
171 writer: &mut writer::Writer,
172 obj: &IndexMap<String, Value>,
173 depth: usize,
174) -> ToonResult<()> {
175 validate_depth(depth, MAX_DEPTH)?;
176
177 let keys: Vec<&String> = obj.keys().collect();
178
179 for (i, key) in keys.iter().enumerate() {
180 if i > 0 {
181 writer.write_newline()?;
182 }
183
184 if depth > 0 {
185 writer.write_indent(depth)?;
186 }
187
188 let value = &obj[*key];
189
190 match value {
191 Value::Array(arr) => {
192 write_array(writer, Some(key), arr, depth)?;
193 }
194 Value::Object(nested_obj) => {
195 writer.write_key(key)?;
196 writer.write_char(':')?;
197 writer.write_newline()?;
198 write_object(writer, nested_obj, depth + 1)?;
199 }
200 _ => {
201 writer.write_key(key)?;
202 writer.write_char(':')?;
203 writer.write_char(' ')?;
204 write_primitive_value(writer, value)?;
205 }
206 }
207 }
208
209 Ok(())
210}
211
212fn write_array(
213 writer: &mut writer::Writer,
214 key: Option<&str>,
215 arr: &[Value],
216 depth: usize,
217) -> ToonResult<()> {
218 validate_depth(depth, MAX_DEPTH)?;
219
220 if arr.is_empty() {
221 writer.write_empty_array_with_key(key)?;
222 return Ok(());
223 }
224
225 if let Some(keys) = is_tabular_array(arr) {
227 encode_tabular_array(writer, key, arr, &keys, depth)?;
228 } else if is_primitive_array(arr) {
229 encode_primitive_array(writer, key, arr, depth)?;
230 } else {
231 encode_nested_array(writer, key, arr, depth)?;
232 }
233
234 Ok(())
235}
236
237fn is_tabular_array(arr: &[Value]) -> Option<Vec<String>> {
240 if arr.is_empty() {
241 return None;
242 }
243
244 let first = arr.first()?;
245 if !first.is_object() {
246 return None;
247 }
248
249 let first_obj = first.as_object()?;
250 let keys: Vec<String> = first_obj.keys().cloned().collect();
251
252 for value in first_obj.values() {
254 if !is_primitive(value) {
255 return None;
256 }
257 }
258
259 for val in arr.iter().skip(1) {
261 if let Some(obj) = val.as_object() {
262 let obj_keys: Vec<String> = obj.keys().cloned().collect();
263 if keys != obj_keys {
264 return None;
265 }
266 for value in obj.values() {
267 if !is_primitive(value) {
268 return None;
269 }
270 }
271 } else {
272 return None;
273 }
274 }
275
276 Some(keys)
277}
278
279fn is_primitive(value: &Value) -> bool {
281 matches!(
282 value,
283 Value::Null | Value::Bool(_) | Value::Number(_) | Value::String(_)
284 )
285}
286
287fn is_primitive_array(arr: &[Value]) -> bool {
289 arr.iter().all(is_primitive)
290}
291
292fn encode_primitive_array(
293 writer: &mut writer::Writer,
294 key: Option<&str>,
295 arr: &[Value],
296 depth: usize,
297) -> ToonResult<()> {
298 writer.write_array_header(key, arr.len(), None, depth)?;
299 writer.write_char(' ')?;
300
301 for (i, val) in arr.iter().enumerate() {
302 if i > 0 {
303 writer.write_delimiter()?;
304 }
305 write_primitive_value(writer, val)?;
306 }
307
308 Ok(())
309}
310
311fn write_primitive_value(writer: &mut writer::Writer, value: &Value) -> ToonResult<()> {
312 match value {
313 Value::Null => writer.write_str("null"),
314 Value::Bool(b) => writer.write_str(&b.to_string()),
315 Value::Number(n) => writer.write_str(&n.to_string()),
316 Value::String(s) => {
317 if writer.needs_quoting(s) {
318 writer.write_quoted_string(s)
319 } else {
320 writer.write_str(s)
321 }
322 }
323 _ => Err(ToonError::InvalidInput(
324 "Expected primitive value".to_string(),
325 )),
326 }
327}
328
329fn encode_tabular_array(
330 writer: &mut writer::Writer,
331 key: Option<&str>,
332 arr: &[Value],
333 keys: &[String],
334 depth: usize,
335) -> ToonResult<()> {
336 writer.write_array_header(key, arr.len(), Some(keys), depth)?;
337 writer.write_newline()?;
338
339 for (row_index, obj_val) in arr.iter().enumerate() {
340 if let Some(obj) = obj_val.as_object() {
341 writer.write_indent(depth + 1)?;
342
343 for (i, key) in keys.iter().enumerate() {
344 if i > 0 {
345 writer.write_delimiter()?;
346 }
347
348 if let Some(val) = obj.get(key) {
349 write_primitive_value(writer, val)?;
350 } else {
351 writer.write_str("null")?;
352 }
353 }
354
355 if row_index < arr.len() - 1 {
356 writer.write_newline()?;
357 }
358 }
359 }
360
361 Ok(())
362}
363
364fn encode_nested_array(
365 writer: &mut writer::Writer,
366 key: Option<&str>,
367 arr: &[Value],
368 depth: usize,
369) -> ToonResult<()> {
370 writer.write_array_header(key, arr.len(), None, depth)?;
371 writer.write_newline()?;
372
373 for (i, val) in arr.iter().enumerate() {
374 writer.write_indent(depth + 1)?;
375 writer.write_char('-')?;
376 writer.write_char(' ')?;
377
378 match val {
379 Value::Array(inner_arr) => {
380 write_array(writer, None, inner_arr, depth + 1)?;
381 }
382 Value::Object(obj) => {
383 let keys: Vec<&String> = obj.keys().collect();
384 if let Some(first_key) = keys.first() {
385 let first_val = &obj[*first_key];
386
387 writer.write_key(first_key)?;
388 writer.write_char(':')?;
389 writer.write_char(' ')?;
390 match first_val {
391 Value::Array(arr) => {
392 write_array(writer, None, arr, depth + 1)?;
393 }
394 Value::Object(nested_obj) => {
395 writer.write_newline()?;
396 write_object(writer, nested_obj, depth + 2)?;
397 }
398 _ => {
399 write_primitive_value(writer, first_val)?;
400 }
401 }
402
403 for key in keys.iter().skip(1) {
404 writer.write_newline()?;
405 writer.write_indent(depth + 2)?;
406 writer.write_key(key)?;
407 writer.write_char(':')?;
408 writer.write_char(' ')?;
409
410 let value = &obj[*key];
411 match value {
412 Value::Array(arr) => {
413 write_array(writer, None, arr, depth + 2)?;
414 }
415 Value::Object(nested_obj) => {
416 writer.write_newline()?;
417 write_object(writer, nested_obj, depth + 3)?;
418 }
419 _ => {
420 write_primitive_value(writer, value)?;
421 }
422 }
423 }
424 }
425 }
426 _ => {
427 write_primitive_value(writer, val)?;
428 }
429 }
430
431 if i < arr.len() - 1 {
432 writer.write_newline()?;
433 }
434 }
435
436 Ok(())
437}
438
439#[cfg(test)]
440mod tests {
441 use core::f64;
442
443 use serde_json::json;
444
445 use super::*;
446
447 #[test]
448 fn test_encode_null() {
449 let value = json!(null);
450 assert_eq!(encode_default(&value).unwrap(), "null");
451 }
452
453 #[test]
454 fn test_encode_bool() {
455 assert_eq!(encode_default(json!(true)).unwrap(), "true");
456 assert_eq!(encode_default(json!(false)).unwrap(), "false");
457 }
458
459 #[test]
460 fn test_encode_number() {
461 assert_eq!(encode_default(json!(42)).unwrap(), "42");
462 assert_eq!(
463 encode_default(json!(f64::consts::PI)).unwrap(),
464 "3.141592653589793"
465 );
466 assert_eq!(encode_default(json!(-5)).unwrap(), "-5");
467 }
468
469 #[test]
470 fn test_encode_string() {
471 assert_eq!(encode_default(json!("hello")).unwrap(), "hello");
472 assert_eq!(
473 encode_default(json!("hello world")).unwrap(),
474 "\"hello world\""
475 );
476 }
477
478 #[test]
479 fn test_encode_simple_object() {
480 let obj = json!({"name": "Alice", "age": 30});
481 let result = encode_default(&obj).unwrap();
482 assert!(result.contains("name: Alice"));
483 assert!(result.contains("age: 30"));
484 }
485
486 #[test]
487 fn test_encode_primitive_array() {
488 let obj = json!({"tags": ["reading", "gaming", "coding"]});
489 let result = encode_default(&obj).unwrap();
490 assert_eq!(result, "tags[3]: reading,gaming,coding");
491 }
492
493 #[test]
494 fn test_encode_tabular_array() {
495 let obj = json!({
496 "users": [
497 {"id": 1, "name": "Alice"},
498 {"id": 2, "name": "Bob"}
499 ]
500 });
501 let result = encode_default(&obj).unwrap();
502 assert!(result.contains("users[2]{id,name}:"));
503 assert!(result.contains("1,Alice"));
504 assert!(result.contains("2,Bob"));
505 }
506
507 #[test]
508 fn test_encode_empty_array() {
509 let obj = json!({"items": []});
510 let result = encode_default(&obj).unwrap();
511 assert_eq!(result, "items[0]:");
512 }
513
514 #[test]
515 fn test_encode_nested_object() {
516 let obj = json!({
517 "user": {
518 "name": "Alice",
519 "age": 30
520 }
521 });
522 let result = encode_default(&obj).unwrap();
523 assert!(result.contains("user:"));
524 assert!(result.contains("name: Alice"));
525 assert!(result.contains("age: 30"));
526 }
527}