1pub mod primitives;
2pub mod writer;
3use indexmap::IndexMap;
4
5use crate::{
6 constants::MAX_DEPTH,
7 error::{
8 ToonError,
9 ToonResult,
10 },
11 types::{
12 EncodeOptions,
13 IntoJsonValue,
14 JsonValue as Value,
15 },
16 utils::{
17 normalize,
18 validation::validate_depth,
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)?;
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)?;
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
302 for (i, val) in arr.iter().enumerate() {
303 if i > 0 {
304 writer.write_delimiter()?;
305 }
306 write_primitive_value(writer, val)?;
307 }
308
309 Ok(())
310}
311
312fn write_primitive_value(writer: &mut writer::Writer, value: &Value) -> ToonResult<()> {
313 match value {
314 Value::Null => writer.write_str("null"),
315 Value::Bool(b) => writer.write_str(&b.to_string()),
316 Value::Number(n) => writer.write_str(&n.to_string()),
317 Value::String(s) => {
318 if writer.needs_quoting(s) {
319 writer.write_quoted_string(s)
320 } else {
321 writer.write_str(s)
322 }
323 }
324 _ => Err(ToonError::InvalidInput(
325 "Expected primitive value".to_string(),
326 )),
327 }
328}
329
330fn encode_tabular_array(
331 writer: &mut writer::Writer,
332 key: Option<&str>,
333 arr: &[Value],
334 keys: &[String],
335 depth: usize,
336) -> ToonResult<()> {
337 writer.write_array_header(key, arr.len(), Some(keys), depth)?;
338 writer.write_newline()?;
339
340 for (row_index, obj_val) in arr.iter().enumerate() {
341 if let Some(obj) = obj_val.as_object() {
342 writer.write_indent(depth + 1)?;
343
344 for (i, key) in keys.iter().enumerate() {
345 if i > 0 {
346 writer.write_delimiter()?;
347 }
348
349 if let Some(val) = obj.get(key) {
350 write_primitive_value(writer, val)?;
351 } else {
352 writer.write_str("null")?;
353 }
354 }
355
356 if row_index < arr.len() - 1 {
357 writer.write_newline()?;
358 }
359 }
360 }
361
362 Ok(())
363}
364
365fn encode_nested_array(
366 writer: &mut writer::Writer,
367 key: Option<&str>,
368 arr: &[Value],
369 depth: usize,
370) -> ToonResult<()> {
371 writer.write_array_header(key, arr.len(), None, depth)?;
372 writer.write_newline()?;
373
374 for (i, val) in arr.iter().enumerate() {
375 writer.write_indent(depth + 1)?;
376 writer.write_char('-')?;
377 writer.write_char(' ')?;
378
379 match val {
380 Value::Array(inner_arr) => {
381 write_array(writer, None, inner_arr, depth + 1)?;
382 }
383 Value::Object(obj) => {
384 let keys: Vec<&String> = obj.keys().collect();
385 if let Some(first_key) = keys.first() {
386 let first_val = &obj[*first_key];
387
388 writer.write_key(first_key)?;
389 writer.write_char(':')?;
390 writer.write_char(' ')?;
391 match first_val {
392 Value::Array(arr) => {
393 write_array(writer, None, arr, depth + 1)?;
394 }
395 Value::Object(nested_obj) => {
396 writer.write_newline()?;
397 write_object(writer, nested_obj, depth + 2)?;
398 }
399 _ => {
400 write_primitive_value(writer, first_val)?;
401 }
402 }
403
404 for key in keys.iter().skip(1) {
405 writer.write_newline()?;
406 writer.write_indent(depth + 2)?;
407 writer.write_key(key)?;
408 writer.write_char(':')?;
409 writer.write_char(' ')?;
410
411 let value = &obj[*key];
412 match value {
413 Value::Array(arr) => {
414 write_array(writer, None, arr, depth + 2)?;
415 }
416 Value::Object(nested_obj) => {
417 writer.write_newline()?;
418 write_object(writer, nested_obj, depth + 3)?;
419 }
420 _ => {
421 write_primitive_value(writer, value)?;
422 }
423 }
424 }
425 }
426 }
427 _ => {
428 write_primitive_value(writer, val)?;
429 }
430 }
431
432 if i < arr.len() - 1 {
433 writer.write_newline()?;
434 }
435 }
436
437 Ok(())
438}
439
440#[cfg(test)]
441mod tests {
442 use serde_json::json;
443
444 use super::*;
445
446 #[test]
447 fn test_encode_null() {
448 let value = json!(null);
449 assert_eq!(encode_default(&value).unwrap(), "null");
450 }
451
452 #[test]
453 fn test_encode_bool() {
454 assert_eq!(encode_default(&json!(true)).unwrap(), "true");
455 assert_eq!(encode_default(&json!(false)).unwrap(), "false");
456 }
457
458 #[test]
459 fn test_encode_number() {
460 assert_eq!(encode_default(&json!(42)).unwrap(), "42");
461 assert_eq!(encode_default(&json!(3.14)).unwrap(), "3.14");
462 assert_eq!(encode_default(&json!(-5)).unwrap(), "-5");
463 }
464
465 #[test]
466 fn test_encode_string() {
467 assert_eq!(encode_default(&json!("hello")).unwrap(), "hello");
468 assert_eq!(
469 encode_default(&json!("hello world")).unwrap(),
470 "\"hello world\""
471 );
472 }
473
474 #[test]
475 fn test_encode_simple_object() {
476 let obj = json!({"name": "Alice", "age": 30});
477 let result = encode_default(&obj).unwrap();
478 assert!(result.contains("name: Alice"));
479 assert!(result.contains("age: 30"));
480 }
481
482 #[test]
483 fn test_encode_primitive_array() {
484 let obj = json!({"tags": ["reading", "gaming", "coding"]});
485 let result = encode_default(&obj).unwrap();
486 assert_eq!(result, "tags[3]: reading,gaming,coding");
487 }
488
489 #[test]
490 fn test_encode_tabular_array() {
491 let obj = json!({
492 "users": [
493 {"id": 1, "name": "Alice"},
494 {"id": 2, "name": "Bob"}
495 ]
496 });
497 let result = encode_default(&obj).unwrap();
498 assert!(result.contains("users[2]{id,name}:"));
499 assert!(result.contains("1,Alice"));
500 assert!(result.contains("2,Bob"));
501 }
502
503 #[test]
504 fn test_encode_empty_array() {
505 let obj = json!({"items": []});
506 let result = encode_default(&obj).unwrap();
507 assert_eq!(result, "items[0]:");
508 }
509
510 #[test]
511 fn test_encode_nested_object() {
512 let obj = json!({
513 "user": {
514 "name": "Alice",
515 "age": 30
516 }
517 });
518 let result = encode_default(&obj).unwrap();
519 assert!(result.contains("user:"));
520 assert!(result.contains("name: Alice"));
521 assert!(result.contains("age: 30"));
522 }
523}