1use std::io::{Read, Write};
2
3use indexmap::IndexMap;
4
5use crate::format::{FormatOptions, FormatReader, FormatWriter};
6use crate::value::Value;
7
8fn from_yaml_value(v: serde_yaml::Value) -> Value {
10 match v {
11 serde_yaml::Value::Null => Value::Null,
12 serde_yaml::Value::Bool(b) => Value::Bool(b),
13 serde_yaml::Value::Number(n) => {
14 if let Some(i) = n.as_i64() {
15 Value::Integer(i)
16 } else if let Some(f) = n.as_f64() {
17 Value::Float(f)
18 } else {
19 Value::Float(n.as_f64().unwrap_or(f64::NAN))
20 }
21 }
22 serde_yaml::Value::String(s) => Value::String(s),
23 serde_yaml::Value::Sequence(seq) => {
24 Value::Array(seq.into_iter().map(from_yaml_value).collect())
25 }
26 serde_yaml::Value::Mapping(map) => {
27 let obj: IndexMap<String, Value> = map
28 .into_iter()
29 .map(|(k, v)| {
30 let key = match k {
31 serde_yaml::Value::String(s) => s,
32 serde_yaml::Value::Number(n) => n.to_string(),
33 serde_yaml::Value::Bool(b) => b.to_string(),
34 serde_yaml::Value::Null => "null".to_string(),
35 other => serde_yaml::to_string(&other)
36 .unwrap_or_default()
37 .trim()
38 .to_string(),
39 };
40 (key, from_yaml_value(v))
41 })
42 .collect();
43 Value::Object(obj)
44 }
45 serde_yaml::Value::Tagged(tagged) => from_yaml_value(tagged.value),
46 }
47}
48
49fn to_yaml_value(v: &Value) -> serde_yaml::Value {
51 match v {
52 Value::Null => serde_yaml::Value::Null,
53 Value::Bool(b) => serde_yaml::Value::Bool(*b),
54 Value::Integer(n) => serde_yaml::Value::Number(serde_yaml::Number::from(*n)),
55 Value::Float(f) => {
56 if f.is_nan() || f.is_infinite() {
57 serde_yaml::Value::Null
58 } else {
59 serde_yaml::Value::Number(serde_yaml::Number::from(*f))
60 }
61 }
62 Value::String(s) => serde_yaml::Value::String(s.clone()),
63 Value::Array(arr) => serde_yaml::Value::Sequence(arr.iter().map(to_yaml_value).collect()),
64 Value::Object(map) => {
65 let mapping: serde_yaml::Mapping = map
66 .iter()
67 .map(|(k, v)| (serde_yaml::Value::String(k.clone()), to_yaml_value(v)))
68 .collect();
69 serde_yaml::Value::Mapping(mapping)
70 }
71 }
72}
73
74pub struct YamlReader;
76
77impl FormatReader for YamlReader {
78 fn read(&self, input: &str) -> anyhow::Result<Value> {
79 let yaml_val: serde_yaml::Value =
80 serde_yaml::from_str(input).map_err(|e: serde_yaml::Error| {
81 if let Some(loc) = e.location() {
82 let line = loc.line();
83 let column = loc.column();
84 let line_text = input
85 .lines()
86 .nth(line.saturating_sub(1))
87 .unwrap_or("")
88 .to_string();
89 crate::error::DkitError::ParseErrorAt {
90 format: "YAML".to_string(),
91 source: Box::new(e),
92 line,
93 column,
94 line_text,
95 }
96 } else {
97 crate::error::DkitError::ParseError {
98 format: "YAML".to_string(),
99 source: Box::new(e),
100 }
101 }
102 })?;
103 Ok(from_yaml_value(yaml_val))
104 }
105
106 fn read_from_reader(&self, mut reader: impl Read) -> anyhow::Result<Value> {
107 let mut input = String::new();
108 reader
109 .read_to_string(&mut input)
110 .map_err(|e| crate::error::DkitError::ParseError {
111 format: "YAML".to_string(),
112 source: Box::new(e),
113 })?;
114 self.read(&input)
115 }
116}
117
118#[derive(Default)]
120pub struct YamlWriter {
121 options: FormatOptions,
122}
123
124impl YamlWriter {
125 pub fn new(options: FormatOptions) -> Self {
126 Self { options }
127 }
128}
129
130impl FormatWriter for YamlWriter {
131 fn write(&self, value: &Value) -> anyhow::Result<String> {
132 let yaml_val = to_yaml_value(value);
133
134 if self.options.flow_style {
135 let json_val = yaml_to_json_style(&yaml_val);
137 let output = serde_json::to_string(&json_val).map_err(|e| {
138 crate::error::DkitError::WriteError {
139 format: "YAML".to_string(),
140 source: Box::new(e),
141 }
142 })?;
143 Ok(output)
144 } else {
145 let output = serde_yaml::to_string(&yaml_val).map_err(|e| {
146 crate::error::DkitError::WriteError {
147 format: "YAML".to_string(),
148 source: Box::new(e),
149 }
150 })?;
151 Ok(output)
152 }
153 }
154
155 fn write_to_writer(&self, value: &Value, mut writer: impl Write) -> anyhow::Result<()> {
156 let output = self.write(value)?;
157 writer
158 .write_all(output.as_bytes())
159 .map_err(|e| crate::error::DkitError::WriteError {
160 format: "YAML".to_string(),
161 source: Box::new(e),
162 })?;
163 Ok(())
164 }
165}
166
167fn yaml_to_json_style(v: &serde_yaml::Value) -> serde_json::Value {
169 match v {
170 serde_yaml::Value::Null => serde_json::Value::Null,
171 serde_yaml::Value::Bool(b) => serde_json::Value::Bool(*b),
172 serde_yaml::Value::Number(n) => {
173 if let Some(i) = n.as_i64() {
174 serde_json::Value::Number(i.into())
175 } else if let Some(f) = n.as_f64() {
176 serde_json::Number::from_f64(f)
177 .map(serde_json::Value::Number)
178 .unwrap_or(serde_json::Value::Null)
179 } else {
180 serde_json::Value::Null
181 }
182 }
183 serde_yaml::Value::String(s) => serde_json::Value::String(s.clone()),
184 serde_yaml::Value::Sequence(seq) => {
185 serde_json::Value::Array(seq.iter().map(yaml_to_json_style).collect())
186 }
187 serde_yaml::Value::Mapping(map) => {
188 let obj: serde_json::Map<String, serde_json::Value> = map
189 .iter()
190 .map(|(k, v)| {
191 let key = match k {
192 serde_yaml::Value::String(s) => s.clone(),
193 other => serde_yaml::to_string(other)
194 .unwrap_or_default()
195 .trim()
196 .to_string(),
197 };
198 (key, yaml_to_json_style(v))
199 })
200 .collect();
201 serde_json::Value::Object(obj)
202 }
203 serde_yaml::Value::Tagged(tagged) => yaml_to_json_style(&tagged.value),
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
214 fn test_convert_null() {
215 let v = from_yaml_value(serde_yaml::Value::Null);
216 assert_eq!(v, Value::Null);
217 }
218
219 #[test]
220 fn test_convert_bool() {
221 assert_eq!(
222 from_yaml_value(serde_yaml::Value::Bool(true)),
223 Value::Bool(true)
224 );
225 assert_eq!(
226 from_yaml_value(serde_yaml::Value::Bool(false)),
227 Value::Bool(false)
228 );
229 }
230
231 #[test]
232 fn test_convert_integer() {
233 let yaml_val: serde_yaml::Value = serde_yaml::from_str("42").unwrap();
234 let v = from_yaml_value(yaml_val);
235 assert_eq!(v, Value::Integer(42));
236 }
237
238 #[test]
239 fn test_convert_float() {
240 let yaml_val: serde_yaml::Value = serde_yaml::from_str("3.14").unwrap();
241 let v = from_yaml_value(yaml_val);
242 assert_eq!(v, Value::Float(3.14));
243 }
244
245 #[test]
246 fn test_convert_string() {
247 let v = from_yaml_value(serde_yaml::Value::String("hello".to_string()));
248 assert_eq!(v, Value::String("hello".to_string()));
249 }
250
251 #[test]
252 fn test_convert_sequence() {
253 let yaml_val: serde_yaml::Value = serde_yaml::from_str("[1, two, null]").unwrap();
254 let v = from_yaml_value(yaml_val);
255 let arr = v.as_array().unwrap();
256 assert_eq!(arr.len(), 3);
257 assert_eq!(arr[0], Value::Integer(1));
258 assert_eq!(arr[1], Value::String("two".to_string()));
259 assert_eq!(arr[2], Value::Null);
260 }
261
262 #[test]
263 fn test_convert_mapping() {
264 let yaml = "name: dkit\nversion: 1";
265 let yaml_val: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
266 let v = from_yaml_value(yaml_val);
267 let obj = v.as_object().unwrap();
268 assert_eq!(obj.get("name"), Some(&Value::String("dkit".to_string())));
269 assert_eq!(obj.get("version"), Some(&Value::Integer(1)));
270 }
271
272 #[test]
273 fn test_convert_nested() {
274 let yaml = r#"
275users:
276 - name: Alice
277 age: 30
278 - name: Bob
279 age: 25
280"#;
281 let yaml_val: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
282 let v = from_yaml_value(yaml_val);
283 let users = v
284 .as_object()
285 .unwrap()
286 .get("users")
287 .unwrap()
288 .as_array()
289 .unwrap();
290 assert_eq!(users.len(), 2);
291 assert_eq!(
292 users[0].as_object().unwrap().get("name"),
293 Some(&Value::String("Alice".to_string()))
294 );
295 assert_eq!(
296 users[0].as_object().unwrap().get("age"),
297 Some(&Value::Integer(30))
298 );
299 }
300
301 #[test]
302 fn test_convert_numeric_key() {
303 let yaml = "123: value";
304 let yaml_val: serde_yaml::Value = serde_yaml::from_str(yaml).unwrap();
305 let v = from_yaml_value(yaml_val);
306 let obj = v.as_object().unwrap();
307 assert_eq!(obj.get("123"), Some(&Value::String("value".to_string())));
308 }
309
310 #[test]
313 fn test_roundtrip_primitives() {
314 let values = vec![
315 Value::Null,
316 Value::Bool(false),
317 Value::Integer(100),
318 Value::Float(2.718),
319 Value::String("test".to_string()),
320 ];
321 for v in values {
322 let yaml = to_yaml_value(&v);
323 let back = from_yaml_value(yaml);
324 assert_eq!(back, v);
325 }
326 }
327
328 #[test]
329 fn test_roundtrip_complex() {
330 let mut map = IndexMap::new();
331 map.insert(
332 "key".to_string(),
333 Value::Array(vec![Value::Integer(1), Value::Null]),
334 );
335 let original = Value::Object(map);
336 let yaml = to_yaml_value(&original);
337 let back = from_yaml_value(yaml);
338 assert_eq!(back, original);
339 }
340
341 #[test]
344 fn test_reader_simple_mapping() {
345 let reader = YamlReader;
346 let v = reader.read("name: dkit\ncount: 42").unwrap();
347 let obj = v.as_object().unwrap();
348 assert_eq!(obj.get("name"), Some(&Value::String("dkit".to_string())));
349 assert_eq!(obj.get("count"), Some(&Value::Integer(42)));
350 }
351
352 #[test]
353 fn test_reader_sequence() {
354 let reader = YamlReader;
355 let v = reader.read("- 1\n- 2\n- 3").unwrap();
356 assert_eq!(v.as_array().unwrap().len(), 3);
357 }
358
359 #[test]
360 fn test_reader_invalid_yaml() {
361 let reader = YamlReader;
362 let result = reader.read(":\n :\n - ][");
363 assert!(result.is_err());
364 }
365
366 #[test]
367 fn test_reader_empty_mapping() {
368 let reader = YamlReader;
369 let v = reader.read("{}").unwrap();
370 assert!(v.as_object().unwrap().is_empty());
371 }
372
373 #[test]
374 fn test_reader_empty_sequence() {
375 let reader = YamlReader;
376 let v = reader.read("[]").unwrap();
377 assert!(v.as_array().unwrap().is_empty());
378 }
379
380 #[test]
381 fn test_reader_from_reader() {
382 let reader = YamlReader;
383 let input = "x: 1".as_bytes();
384 let v = reader.read_from_reader(input).unwrap();
385 assert_eq!(v.as_object().unwrap().get("x"), Some(&Value::Integer(1)));
386 }
387
388 #[test]
389 fn test_reader_multiline_string() {
390 let reader = YamlReader;
391 let yaml = "description: |\n line1\n line2\n";
392 let v = reader.read(yaml).unwrap();
393 let desc = v
394 .as_object()
395 .unwrap()
396 .get("description")
397 .unwrap()
398 .as_str()
399 .unwrap();
400 assert!(desc.contains("line1"));
401 assert!(desc.contains("line2"));
402 }
403
404 #[test]
405 fn test_reader_anchor_alias() {
406 let reader = YamlReader;
407 let yaml =
408 "defaults: &defaults\n timeout: 30\nserver:\n host: localhost\n timeout: *defaults";
409 let v = reader.read(yaml).unwrap();
410 let server = v.as_object().unwrap().get("server").unwrap();
411 assert_eq!(
412 server.as_object().unwrap().get("host"),
413 Some(&Value::String("localhost".to_string()))
414 );
415 let defaults = v.as_object().unwrap().get("defaults").unwrap();
417 assert_eq!(
418 defaults.as_object().unwrap().get("timeout"),
419 Some(&Value::Integer(30))
420 );
421 }
422
423 #[test]
426 fn test_writer_default() {
427 let writer = YamlWriter::default();
428 let v = Value::Object({
429 let mut m = IndexMap::new();
430 m.insert("a".to_string(), Value::Integer(1));
431 m
432 });
433 let output = writer.write(&v).unwrap();
434 assert!(output.contains("a:"));
435 assert!(output.contains('1'));
436 }
437
438 #[test]
439 fn test_writer_flow_style() {
440 let writer = YamlWriter::new(FormatOptions {
441 flow_style: true,
442 ..Default::default()
443 });
444 let v = Value::Object({
445 let mut m = IndexMap::new();
446 m.insert("a".to_string(), Value::Integer(1));
447 m
448 });
449 let output = writer.write(&v).unwrap();
450 assert!(output.contains('{'));
452 assert!(output.contains('}'));
453 }
454
455 #[test]
456 fn test_writer_null() {
457 let writer = YamlWriter::default();
458 let output = writer.write(&Value::Null).unwrap();
459 assert!(output.contains("null"));
460 }
461
462 #[test]
463 fn test_writer_to_writer() {
464 let writer = YamlWriter::default();
465 let mut buf = Vec::new();
466 writer
467 .write_to_writer(&Value::Integer(42), &mut buf)
468 .unwrap();
469 let output = String::from_utf8(buf).unwrap();
470 assert!(output.contains("42"));
471 }
472
473 #[test]
474 fn test_writer_nan_becomes_null() {
475 let writer = YamlWriter::default();
476 let output = writer.write(&Value::Float(f64::NAN)).unwrap();
477 assert!(output.contains("null"));
478 }
479
480 #[test]
483 fn test_full_roundtrip() {
484 let yaml_input = "name: dkit\nversion: 1\ntags:\n- rust\n- cli\n";
485 let reader = YamlReader;
486 let writer = YamlWriter::default();
487
488 let value = reader.read(yaml_input).unwrap();
489 let yaml_output = writer.write(&value).unwrap();
490 let value2 = reader.read(&yaml_output).unwrap();
491
492 assert_eq!(value, value2);
493 }
494
495 #[test]
498 fn test_unicode_string() {
499 let reader = YamlReader;
500 let v = reader.read("emoji: 🎉\nkorean: 한글").unwrap();
501 let obj = v.as_object().unwrap();
502 assert_eq!(obj.get("emoji"), Some(&Value::String("🎉".to_string())));
503 assert_eq!(obj.get("korean"), Some(&Value::String("한글".to_string())));
504 }
505
506 #[test]
507 fn test_negative_numbers() {
508 let reader = YamlReader;
509 let v = reader.read("neg_int: -42\nneg_float: -3.14").unwrap();
510 let obj = v.as_object().unwrap();
511 assert_eq!(obj.get("neg_int"), Some(&Value::Integer(-42)));
512 assert_eq!(obj.get("neg_float"), Some(&Value::Float(-3.14)));
513 }
514
515 #[test]
516 fn test_deeply_nested() {
517 let yaml = "a:\n b:\n c:\n d: 1";
518 let reader = YamlReader;
519 let v = reader.read(yaml).unwrap();
520 let d = v
521 .as_object()
522 .unwrap()
523 .get("a")
524 .unwrap()
525 .as_object()
526 .unwrap()
527 .get("b")
528 .unwrap()
529 .as_object()
530 .unwrap()
531 .get("c")
532 .unwrap()
533 .as_object()
534 .unwrap()
535 .get("d")
536 .unwrap();
537 assert_eq!(d, &Value::Integer(1));
538 }
539
540 #[test]
541 fn test_boolean_values() {
542 let reader = YamlReader;
543 let v = reader.read("yes_val: true\nno_val: false").unwrap();
544 let obj = v.as_object().unwrap();
545 assert_eq!(obj.get("yes_val"), Some(&Value::Bool(true)));
546 assert_eq!(obj.get("no_val"), Some(&Value::Bool(false)));
547 }
548
549 #[test]
550 fn test_null_variants() {
551 let reader = YamlReader;
552 let v = reader.read("a: null\nb: ~\nc:").unwrap();
553 let obj = v.as_object().unwrap();
554 assert_eq!(obj.get("a"), Some(&Value::Null));
555 assert_eq!(obj.get("b"), Some(&Value::Null));
556 assert_eq!(obj.get("c"), Some(&Value::Null));
557 }
558}