Skip to main content

dynamic/
yaml.rs

1use crate::{Dynamic, ZOnce};
2
3use anyhow::{Result, anyhow};
4use indexmap::IndexMap;
5use parking_lot::RwLock;
6use smol_str::SmolStr;
7use std::sync::Arc;
8
9/// 序列化方向:把一个 `Dynamic` 写成 YAML 文本。
10///
11/// 输出风格刻意保持"LLM 友好":
12/// - 默认 block style,序列用 `- item`,映射用 `key: value`。
13/// - 字符串尽量裸出;只有会改变语义或被误读时才加双引号。
14/// - 多行字符串用 `|` literal block scalar,保留换行,避免 `\n` 转义噪声。
15/// - 不输出 anchor / alias / 显式 `!!str` 标签,这些东西 LLM 经常搞错。
16pub trait ToYaml {
17    fn to_yaml(&self, buf: &mut String);
18}
19
20/// 反序列化方向:从 YAML 文本里解析出一个 `Dynamic`。
21///
22/// 只覆盖 LLM 常用 YAML 子集:标量(整数/浮点/字符串/bool/null)、block 风格
23/// mapping / sequence、有限的 flow 风格(嵌套用得不多)、注释。anchors / 复杂 tag
24/// / 折叠 scalar(`>`)/ 集合类型提示(`!!set` 等)不在支持范围内,遇到直接报错。
25pub trait FromYaml: Sized {
26    fn from_yaml(buf: &[u8]) -> Result<(Self, usize)>;
27}
28
29// ---- 序列化 -----------------------------------------------------------
30
31/// 判断字符串是否"必须"加引号,避免和 YAML 标量字面量冲突。
32///
33/// 规则:
34/// - 空串;
35/// - 首字符是 reserved indicator (`!`, `&`, `*`, `?`, `|`, `>`, `'`, `"`, `%`,
36///   `@`, `\``, `#`, `,`, `[`, `]`, `{`, `}`, `:`, `-`);
37/// - 包含 `: ` / ` #`(在普通位置会触发 mapping / 注释);
38/// - 看起来像数字、bool、null、~ 等保留字;
39/// - 包含控制字符;
40/// - 是单独的 `---` / `...` 这种文档分隔符。
41///
42/// LLM 经常输出类似 `"true"` 或 `"123"` 这样的"看起来是数字但其实是字符串"的
43/// 值,因此需要在"字符串内容本身合法但和标量字面量撞"时也强制加引号。
44fn yaml_string_needs_quoting(s: &str) -> bool {
45    if s.is_empty() {
46        return true;
47    }
48    // 文档分隔符不能裸出
49    if s == "---" || s == "..." {
50        return true;
51    }
52    let bytes = s.as_bytes();
53    // 首字符限定
54    const RESERVED_FIRST: &[u8] = b"!\"&'*|>%@`,[]{}#?:";
55    if RESERVED_FIRST.contains(&bytes[0]) {
56        return true;
57    }
58    // 数字 / null / bool 字面量全比较一遍,大小写敏感 (YAML 1.2)。
59    if matches!(
60        s,
61        "true"
62            | "false"
63            | "null"
64            | "Null"
65            | "NULL"
66            | "~"
67            | "yes"
68            | "no"
69            | "on"
70            | "off"
71            | "True"
72            | "False"
73            | "TRUE"
74            | "FALSE"
75            | "YES"
76            | "NO"
77            | "ON"
78            | "OFF"
79    ) {
80        return true;
81    }
82    // 像数字的字符串也需要引号,避免下游解析误读。
83    if looks_like_number(s) {
84        return true;
85    }
86    // 走一遍内容,处理 ": "、首尾空格、tab、控制字符、"#" 前有空格
87    for (idx, b) in bytes.iter().enumerate() {
88        match *b {
89            b'\n' | b'\t' => return true,
90            0..=0x1f => return true,
91            b':' if idx + 1 < bytes.len() && (bytes[idx + 1] == b' ' || bytes[idx + 1] == b'\t') => return true,
92            b'#' if idx > 0 && (bytes[idx - 1] == b' ' || bytes[idx - 1] == b'\t') => return true,
93            _ => {}
94        }
95    }
96    // 首字符是 `-` 或 `?` 时为了避免被解析成 sequence / complex key,也加引号。
97    // (已在 RESERVED_FIRST 包含)
98    // 末尾空格 / tab 需要引号,否则会被 trim 掉。
99    if matches!(bytes.last(), Some(b' ' | b'\t')) {
100        return true;
101    }
102    false
103}
104
105fn looks_like_number(s: &str) -> bool {
106    // 包含 `.` / `e` / `E` 当作浮点尝试;否则当整数。允许前导 `+` / `-`。
107    if s.eq_ignore_ascii_case("inf") || s.eq_ignore_ascii_case("nan") || s.eq_ignore_ascii_case(".inf") || s.eq_ignore_ascii_case(".nan") {
108        return true;
109    }
110    if s.contains('.') || s.contains('e') || s.contains('E') {
111        return s.parse::<f64>().is_ok()
112    } else {
113        return s.parse::<i64>().is_ok()
114    }
115}
116
117/// 把字符串内容里必须转义的字符按 YAML 双引号 scalar 的规则写出。
118fn yaml_quote_string(s: &str, buf: &mut String) {
119    buf.push('"');
120    for ch in s.chars() {
121        match ch {
122            '\\' => buf.push_str("\\\\"),
123            '"' => buf.push_str("\\\""),
124            '\n' => buf.push_str("\\n"),
125            '\r' => buf.push_str("\\r"),
126            '\t' => buf.push_str("\\t"),
127            '\0' => buf.push_str("\\0"),
128            '\x08' => buf.push_str("\\b"),
129            '\x0c' => buf.push_str("\\f"),
130            c if (c as u32) < 0x20 => {
131                use std::fmt::Write;
132                let _ = write!(buf, "\\u{:04x}", c as u32);
133            }
134            c => buf.push(c),
135        }
136    }
137    buf.push('"');
138}
139
140/// 多行字符串用 `|` literal block scalar 输出。
141/// `|` 之后的 chomping indicator 用 `+` 表示保留末尾所有换行;LLM 解析 YAML 时
142/// 这个最不会出错,代价是多一两行。块缩进以首个非空行的缩进为基准。
143fn yaml_block_string(s: &str, indent: usize, buf: &mut String) {
144    let pad = " ".repeat(indent);
145    buf.push_str("|+\n");
146    let mut count = 0usize;
147    for ch in s.chars() {
148        buf.push_str(&pad);
149        if ch == '\n' {
150            buf.push('\n');
151            count = 0;
152        } else {
153            buf.push(ch);
154            count += 1;
155        }
156    }
157    if count > 0 {
158        buf.push('\n');
159    }
160}
161
162/// 把一个字符串写出。优先裸出;必要时双引号;含换行时用 `|` block。
163fn yaml_write_string(s: &str, indent: usize, buf: &mut String) {
164    if s.contains('\n') {
165        yaml_block_string(s, indent, buf);
166        return;
167    }
168    if yaml_string_needs_quoting(s) {
169        yaml_quote_string(s, buf);
170    } else {
171        buf.push_str(s);
172    }
173}
174
175fn yaml_write_key(key: &str, buf: &mut String) {
176    // key 不强制缩进风格;统一处理成"裸出优先,不行就加引号"。
177    if yaml_string_needs_quoting(key) {
178        yaml_quote_string(key, buf);
179    } else {
180        buf.push_str(key);
181    }
182}
183
184impl ToYaml for &str {
185    fn to_yaml(&self, buf: &mut String) {
186        yaml_write_string(self, 0, buf);
187    }
188}
189
190impl ToYaml for i64 {
191    fn to_yaml(&self, buf: &mut String) {
192        buf.push_str(&self.to_string());
193    }
194}
195
196impl ToYaml for Dynamic {
197    fn to_yaml(&self, buf: &mut String) {
198        self.to_yaml_indent(0, buf);
199    }
200}
201
202impl Dynamic {
203    /// 把当前值以 YAML 写出,根节点的缩进是 `indent`(通常传 0)。
204    /// 顶层若不是 mapping / sequence,需要补一行 `---` 开头,避免直接写裸字符串
205    /// 看起来像单 key 的文档。这是 LLM 阅读时一个常见的边界 bug。
206    fn to_yaml_indent(&self, indent: usize, buf: &mut String) {
207        match self {
208            Self::Iter { .. } => {}
209            Self::Null => buf.push_str("null"),
210            Self::Bool(b) => buf.push_str(if *b { "true" } else { "false" }),
211            Self::F16(bits) => buf.push_str(&crate::f16_to_f64(*bits).to_string()),
212            Self::F32(f) => buf.push_str(&f.to_string()),
213            Self::F64(f) => buf.push_str(&f.to_string()),
214            Self::I8(i) => buf.push_str(&i.to_string()),
215            Self::I16(i) => buf.push_str(&i.to_string()),
216            Self::I32(i) => buf.push_str(&i.to_string()),
217            Self::I64(i) => buf.push_str(&i.to_string()),
218            Self::U8(i) => buf.push_str(&i.to_string()),
219            Self::U16(i) => buf.push_str(&i.to_string()),
220            Self::U32(i) => buf.push_str(&i.to_string()),
221            Self::U64(i) => buf.push_str(&i.to_string()),
222            Self::String(s) => yaml_write_string(s.as_str(), indent, buf),
223            Self::StringBuf(s) => yaml_write_string(s.as_str(), indent, buf),
224            Self::Bytes(vec) => yaml_write_seq(vec.iter().map(|b| Dynamic::U8(*b)), indent, buf),
225            Self::VecI8(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I8(*v)), indent, buf),
226            Self::VecU16(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U16(*v)), indent, buf),
227            Self::VecI16(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I16(*v)), indent, buf),
228            Self::VecU32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U32(*v)), indent, buf),
229            Self::VecI32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I32(*v)), indent, buf),
230            Self::VecF32(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::F32(*v)), indent, buf),
231            Self::VecU64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::U64(*v)), indent, buf),
232            Self::VecI64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::I64(*v)), indent, buf),
233            Self::VecF64(vec) => yaml_write_seq(vec.iter().map(|v| Dynamic::F64(*v)), indent, buf),
234            Self::List(items) => {
235                let items = items.read().clone();
236                yaml_write_seq(items.into_iter(), indent, buf);
237            }
238            Self::Map(map) => {
239                let map = map.read().clone();
240                yaml_write_map(map.into_iter(), indent, buf);
241            }
242            Self::StructView { .. } | Self::StructOwned { .. } => {
243                let keys = self.keys();
244                if keys.is_empty() {
245                    buf.push_str("{}\n");
246                    return;
247                }
248                for key in keys.iter() {
249                    buf.push_str(&" ".repeat(indent));
250                    yaml_write_key(key.as_str(), buf);
251                    buf.push(':');
252                    let value = self.get_dynamic(key).unwrap_or(Dynamic::Null);
253                    if is_yaml_block(&value) {
254                        buf.push('\n');
255                        value.to_yaml_indent(indent + 2, buf);
256                    } else {
257                        buf.push(' ');
258                        value.to_yaml_indent(0, buf);
259                        buf.push('\n');
260                    }
261                }
262            }
263            Self::Custom(value) => {
264                buf.push_str("{@custom: ");
265                yaml_write_string(value.custom_type_name(), indent, buf);
266                buf.push_str("}\n");
267            }
268        }
269    }
270}
271
272/// 序列和映射的 child 是否需要展开到下一行(block style),还是能塞进当前行
273/// (flow scalar)。标量、null、bool、纯数字字符串这些可以直接 inline;mapping /
274/// sequence 必须换行展开。
275fn is_yaml_block(value: &Dynamic) -> bool {
276    matches!(value, Dynamic::Map(_) | Dynamic::List(_) | Dynamic::Bytes(_) | Dynamic::StructView { .. } | Dynamic::StructOwned { .. })
277        || matches!(value, Dynamic::VecI8(_) | Dynamic::VecU16(_) | Dynamic::VecI16(_) | Dynamic::VecU32(_) | Dynamic::VecI32(_) | Dynamic::VecF32(_) | Dynamic::VecU64(_) | Dynamic::VecI64(_) | Dynamic::VecF64(_))
278}
279
280fn yaml_write_seq<I: Iterator<Item = Dynamic>>(items: I, indent: usize, buf: &mut String) {
281    let pad = " ".repeat(indent);
282    let mut first = true;
283    for item in items {
284        // 第一个 item 用 pad (无 newline);后续 item 用 "\n + pad" 分隔。
285        buf.push_str(if first { &pad } else { "\n" });
286        if !first {
287            buf.push_str(&pad);
288        }
289        first = false;
290        buf.push_str("- ");
291        // 序列里的 Map 项用紧凑形式:首条 `key: value` 跟 `- ` 同行,
292        // 后续 key 缩进对齐。这是 LLM 最容易看懂的列表项写法。
293        if let Some(pairs) = map_first_pair_inline(&item) {
294            yaml_write_key(pairs.0.as_str(), buf);
295            buf.push(':');
296            if is_yaml_block(&pairs.1) {
297                buf.push('\n');
298                pairs.1.to_yaml_indent(indent + 4, buf);
299            } else {
300                buf.push(' ');
301                pairs.1.to_yaml_indent(0, buf);
302            }
303            if let Some(rest) = map_remaining_pairs(&item) {
304                // 第一条 sibling key 必须换到新行;yaml_write_map 自己
305                // 负责 pad 和后续换行,不要再手动 push pad(否则会跟 map
306                // 内部的首条 pad 叠加成双倍缩进)。
307                buf.push('\n');
308                yaml_write_map(rest, indent + 2, buf);
309            }
310        } else if is_yaml_block(&item) {
311            let one_more = indent + 2;
312            buf.push('\n');
313            item.to_yaml_indent(one_more, buf);
314        } else {
315            item.to_yaml_indent(0, buf);
316        }
317    }
318    if first {
319        // 空序列:用 flow 形式 `[]`,但必须先 push pad 对齐到当前 block 的缩进,
320        // 否则会落到第 0 列,被 block parser 当成顶层 sequence 截断(整个 round-trip
321        // 都崩)。`indent` 反映的是调用方在 yaml_write_map 里为 block value 准备的
322        // `indent + 2` 缩进,这里把 `[]` 摆到该缩进上即可。
323        buf.push_str(&pad);
324        buf.push_str("[]");
325    }
326}
327
328fn yaml_write_map<I: Iterator<Item = (SmolStr, Dynamic)>>(entries: I, indent: usize, buf: &mut String) {
329    let pad = " ".repeat(indent);
330    let mut first = true;
331    for (key, value) in entries {
332        if first {
333            // 嵌套 map(indent>0)的第一条 entry 需要缩进;
334            // 根 map(indent==0)由调用方负责前导换行,不加 pad。
335            if indent > 0 {
336                buf.push_str(&pad);
337            }
338        } else {
339            buf.push('\n');
340            buf.push_str(&pad);
341        }
342        first = false;
343        yaml_write_key(key.as_str(), buf);
344        buf.push(':');
345        if is_yaml_block(&value) {
346            buf.push('\n');
347            value.to_yaml_indent(indent + 2, buf);
348        } else {
349            buf.push(' ');
350            value.to_yaml_indent(0, buf);
351        }
352    }
353    if indent == 0 {
354        buf.push('\n');
355    }
356}
357
358/// 如果 `value` 是一个至少有 1 个 entry 的 map,返回第一个 (key, value) 对;
359/// 否则返回 None。给序列里的 map 紧凑写法用。
360fn map_first_pair_inline(value: &Dynamic) -> Option<(SmolStr, Dynamic)> {
361    if let Dynamic::Map(m) = value {
362        let m = m.read();
363        let first = m.iter().next()?;
364        Some((first.0.clone(), first.1.clone()))
365    } else {
366        None
367    }
368}
369
370/// 返回 map 剩余 (除第一对外) 的 entry 迭代器。
371fn map_remaining_pairs(value: &Dynamic) -> Option<impl Iterator<Item = (SmolStr, Dynamic)>> {
372    if let Dynamic::Map(m) = value {
373        let m = m.read().clone();
374        let mut iter = m.into_iter();
375        iter.next();
376        Some(iter)
377    } else {
378        None
379    }
380}
381
382// ---- 反序列化 ---------------------------------------------------------
383
384/// 跳过空白和注释。YAML 注释从 `#` 开始,到行尾结束。
385/// 跟 json 不同,空白是空格 / tab / 换行 + 注释。
386fn skip_white(buf: &[u8], mut pos: usize) -> Result<usize> {
387    while pos < buf.len() {
388        match buf[pos] {
389            b' ' | b'\t' | b'\r' | b'\n' => pos += 1,
390            b'#' => {
391                while pos < buf.len() && buf[pos] != b'\n' {
392                    pos += 1;
393                }
394            }
395            _ => break,
396        }
397    }
398    Ok(pos)
399}
400
401/// 返回当前行(从行首到 pos)的缩进空格数。空 buffer 返回 0。
402fn line_indent(buf: &[u8], pos: usize) -> usize {
403    let mut p = pos;
404    while p > 0 && buf[p - 1] != b'\n' {
405        p -= 1;
406    }
407    let mut count = 0usize;
408    // 数到非空字符为止;`while p < pos` 是错的,pos 本身可能就是首字符,
409    // 那时一次都不进入循环。
410    while p < buf.len() && buf[p] == b' ' {
411        count += 1;
412        p += 1;
413    }
414    count
415}
416
417/// 看 pos 处是不是 `\n` 或文件末尾。
418fn at_line_end(buf: &[u8], pos: usize) -> bool {
419    pos >= buf.len() || buf[pos] == b'\n'
420}
421
422/// 判断一行是否完全为空 (只有空白 / 注释 / 换行)。
423fn line_is_blank(buf: &[u8], mut pos: usize) -> bool {
424    while pos < buf.len() && buf[pos] != b'\n' {
425        match buf[pos] {
426            b' ' | b'\t' | b'\r' => pos += 1,
427            b'#' => return true,
428            _ => return false,
429        }
430    }
431    true
432}
433
434/// 看接下来的 token 是否以 `---` (文档开始) 开头。
435/// 这里只把 `---` 当一个"可选的前缀"吃掉,不影响实际语义。
436fn skip_doc_marker(buf: &[u8], mut pos: usize) -> usize {
437    pos = skip_white(buf, pos).unwrap_or(pos);
438    if buf.get(pos..pos + 3) == Some(b"---") && (pos + 3 == buf.len() || matches!(buf[pos + 3], b' ' | b'\t' | b'\n' | b'\r')) {
439        pos += 3;
440    }
441    skip_white(buf, pos).unwrap_or(pos)
442}
443
444/// 解析一个 YAML scalar 字面量(到行尾或遇到 `#` 注释为止)。
445/// 返回 (去掉 trailing comment 后的内容, 消耗的字节数)。
446///
447/// 这里不处理 multi-line scalar (`|` / `>`);要支持的话需要在 mapping / sequence
448/// 解析中专门检测。当前实现里,parser 一旦进入 scalar 就只读本行,后面的多行
449/// 内容会被当成"意外的额外内容"报错 —— 这是有意的取舍,LLM 输出通常不会写
450/// 折叠 scalar。
451fn read_scalar(buf: &[u8], pos: usize) -> Result<(&str, usize)> {
452    let start = pos;
453    let mut end = pos;
454    let mut in_flow_bracket = 0i32; // [ ] { } 计数,处理 inline 集合里的 scalar
455    let mut in_double_quote = false;
456    let mut in_single_quote = false;
457    while end < buf.len() {
458        let b = buf[end];
459        if in_double_quote {
460            if b == b'\\' && end + 1 < buf.len() {
461                end += 2;
462                continue;
463            }
464            if b == b'"' {
465                in_double_quote = false;
466                end += 1;
467                continue;
468            }
469            end += 1;
470            continue;
471        }
472        if in_single_quote {
473            if b == b'\'' {
474                if end + 1 < buf.len() && buf[end + 1] == b'\'' {
475                    // '' 是单引号里的转义,跳两个字符。
476                    end += 2;
477                    continue;
478                }
479                in_single_quote = false;
480                end += 1;
481                continue;
482            }
483            end += 1;
484            continue;
485        }
486        match b {
487            b'"' => {
488                in_double_quote = true;
489                end += 1;
490            }
491            b'\'' => {
492                in_single_quote = true;
493                end += 1;
494            }
495            b'[' | b'{' => {
496                in_flow_bracket += 1;
497                end += 1;
498            }
499            b']' | b'}' => {
500                // `]` / `}` 在 plain scalar 上下文里就是终止符,
501                // 不管是不是在 flow 集合里 —— 调用方会在外层恢复对它们的处理。
502                break;
503            }
504            b',' => {
505                // `,` 是 flow 集合分隔符,plain scalar 遇到直接终止。
506                // 用户如果真的需要带 `,` 的裸字符串,自己加引号。
507                break;
508            }
509            b':' if in_flow_bracket == 0 && end + 1 < buf.len() && (buf[end + 1] == b' ' || buf[end + 1] == b'\t' || buf[end + 1] == b'\n') => {
510                // mapping 的 key:value 分隔,跳出。
511                break;
512            }
513            b'#' if in_flow_bracket == 0 => break,
514            b'\n' if in_flow_bracket == 0 => break,
515            _ => end += 1,
516        }
517    }
518    // 裁掉 trailing 空白。
519    while end > start && matches!(buf[end - 1], b' ' | b'\t' | b'\r') {
520        end -= 1;
521    }
522    if end == start {
523        return Err(anyhow!("yaml scalar 为空 @{}", start));
524    }
525    std::str::from_utf8(&buf[start..end]).map(|s| (s, end - start)).map_err(|e| anyhow!("yaml scalar 含非法 UTF-8: {e}"))
526}
527
528/// 把 scalar token 解析成 Dynamic:支持整数 / 浮点 / bool / null / 普通字符串。
529fn scalar_to_dynamic(raw: &str) -> Dynamic {
530    // null / ~
531    if matches!(raw, "" | "null" | "Null" | "NULL" | "~") {
532        return Dynamic::Null;
533    }
534    // bool (YAML 1.2 只有 true/false,大写也接受。LLM 经常写 yes/no,这里
535    // 也兼容一下,但这是非标准的,使用时要谨慎)。
536    match raw {
537        "true" | "True" | "TRUE" => return Dynamic::Bool(true),
538        "false" | "False" | "FALSE" => return Dynamic::Bool(false),
539        _ => {}
540    }
541    // 整数优先,失败再试浮点。
542    if let Ok(v) = raw.parse::<i64>() {
543        return Dynamic::I64(v);
544    }
545    if let Ok(v) = raw.parse::<f64>() {
546        if v.is_finite() {
547            return Dynamic::F64(v);
548        }
549    }
550    // 字符串:处理双引号 / 单引号包裹 + 转义。
551    if raw.starts_with('"') && raw.ends_with('"') && raw.len() >= 2 {
552        return Dynamic::String(unescape_double_quoted(&raw[1..raw.len() - 1]).into());
553    }
554    if raw.starts_with('\'') && raw.ends_with('\'') && raw.len() >= 2 {
555        return Dynamic::String(unescape_single_quoted(&raw[1..raw.len() - 1]).into());
556    }
557    Dynamic::String(raw.into())
558}
559
560fn unescape_double_quoted(s: &str) -> String {
561    let mut out = String::with_capacity(s.len());
562    let mut chars = s.chars();
563    while let Some(c) = chars.next() {
564        if c == '\\' {
565            match chars.next() {
566                Some('n') => out.push('\n'),
567                Some('t') => out.push('\t'),
568                Some('r') => out.push('\r'),
569                Some('\\') => out.push('\\'),
570                Some('"') => out.push('"'),
571                Some('0') => out.push('\0'),
572                Some('/') => out.push('/'),
573                Some('u') => {
574                    let hex: String = chars.by_ref().take(4).collect();
575                    if let Ok(code) = u32::from_str_radix(&hex, 16) {
576                        if let Some(ch) = char::from_u32(code) {
577                            out.push(ch);
578                        }
579                    }
580                }
581                Some(other) => {
582                    out.push('\\');
583                    out.push(other);
584                }
585                None => out.push('\\'),
586            }
587        } else {
588            out.push(c);
589        }
590    }
591    out
592}
593
594fn unescape_single_quoted(s: &str) -> String {
595    // 单引号 scalar 里,所有内容都是字面量,只有 '' 代表单个 '。
596    s.replace("''", "'")
597}
598
599/// `from_yaml` 入口。跟 `from_json` 一样返回 (value, consumed)。
600impl FromYaml for Dynamic {
601    fn from_yaml(buf: &[u8]) -> Result<(Self, usize)> {
602        let pos = skip_doc_marker(buf, 0);
603        parse_node(buf, pos, 0, false)
604    }
605}
606
607/// 解析一个 YAML node。`min_indent` 是当前 block 的最小缩进,`in_block` 表示
608/// 我们正在某个 block 集合(mapping / sequence)的内部 —— 这种情况下一旦遇到
609/// 缩进小于 `min_indent` 的行,就应该结束。
610fn parse_node(buf: &[u8], pos: usize, min_indent: usize, in_block: bool) -> Result<(Dynamic, usize)> {
611    let pos = skip_white(buf, pos)?;
612    if pos >= buf.len() {
613        return Err(anyhow!("yaml 文档提前结束"));
614    }
615    // 先看是否是流集合 `{ ... }` / `[ ... ]`。
616    if buf[pos] == b'{' {
617        return parse_flow_map(buf, pos);
618    }
619    if buf[pos] == b'[' {
620        return parse_flow_seq(buf, pos);
621    }
622    // 否则是 block 风格。看当前行缩进以及接下来的 token 形态。
623    let indent = line_indent(buf, pos);
624    if indent < min_indent {
625        return Err(anyhow!("yaml 缩进不足: {} < {}", indent, min_indent));
626    }
627    if buf[pos] == b'-' && (pos + 1 == buf.len() || matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
628        return parse_block_seq(buf, pos, indent);
629    }
630    // mapping 第一个 key:value 可能跟 caller 同一行(在 indent 等于 caller
631    // 缩进的情况下),所以不强制从行首开始。
632    if let Some((key, colon)) = try_read_key(buf, pos) {
633        // 把 pos 推到 `:` 之后;parse_block_map 不再重新扫描 key。
634        let value_pos = skip_white(buf, colon + 1)?;
635        return parse_block_map(buf, value_pos, indent, key);
636    }
637    // 走到这里说明是孤立的 scalar —— 只有当 caller 不要求 block 形态时才能
638    // 接受,否则应当报错(避免 mapping 里 `- foo` 被误读成 `foo`)。
639    if in_block {
640        return Err(anyhow!("yaml 期望 block 结构但遇到孤立标量 @{}", pos));
641    }
642    let (raw, consumed) = read_scalar(buf, pos)?;
643    Ok((scalar_to_dynamic(raw), pos + consumed))
644}
645
646/// 在 pos 处尝试读取一个 `key:` 形式的 mapping key。
647/// 成功时返回 (key, ':' 位置)。不修改 pos。
648fn try_read_key(buf: &[u8], pos: usize) -> Option<(SmolStr, usize)> {
649    let start = pos;
650    // 单 / 双引号包裹的 key 直接到对应引号收尾。
651    if buf[pos] == b'"' || buf[pos] == b'\'' {
652        let quote = buf[pos];
653        let mut p = pos + 1;
654        while p < buf.len() && buf[p] != quote {
655            if buf[p] == b'\\' && quote == b'"' && p + 1 < buf.len() {
656                p += 2;
657                continue;
658            }
659            p += 1;
660        }
661        if p >= buf.len() {
662            return None;
663        }
664        let key = &buf[start + 1..p];
665        let after = p + 1;
666        let after = skip_white(buf, after).ok()?;
667        if after >= buf.len() || buf[after] != b':' {
668            return None;
669        }
670        let key_str = std::str::from_utf8(key).ok()?;
671        return Some((SmolStr::from(if quote == b'"' { unescape_double_quoted(key_str) } else { unescape_single_quoted(key_str) }), after));
672    }
673    // 普通 key:扫到 `:` 或"明显不能作为 key 字符"为止。空白 / `,` / `}` / `]` /
674// `#` / `\n` 都属于 separator,提前终止。这样在 `{a: 1, b: 2}` 这种 inline 上下文里
675// 才不会把后面的 `, b: 2` 吞进 key。
676    let mut p = pos;
677    while p < buf.len() {
678        match buf[p] {
679            b':' if p + 1 < buf.len() && (matches!(buf[p + 1], b' ' | b'\t' | b'\n') || p + 1 == buf.len()) => break,
680            b' ' | b'\t' | b'\r' | b'\n' | b'#' | b',' | b'}' | b']' => return None,
681            _ => p += 1,
682        }
683    }
684    if p >= buf.len() || buf[p] != b':' {
685        return None;
686    }
687    let key_bytes = &buf[start..p];
688    let key = std::str::from_utf8(key_bytes).ok()?.trim();
689    if key.is_empty() {
690        return None;
691    }
692    Some((SmolStr::from(key), p))
693}
694
695/// 在 block 风格中解析一个 mapping。`parent_indent` 是 caller 期望的最浅缩进,
696/// `pos` 指向 `:` 之后的第一个字符(可能就是 value 起点,可能是换行,
697/// 也可能是注释/行尾)。`first_key` 是 caller 已经读到的 key。
698/// 返回值会一直读到缩进小于 `parent_indent` 行为止。
699fn parse_block_map(buf: &[u8], mut pos: usize, parent_indent: usize, first_key: SmolStr) -> Result<(Dynamic, usize)> {
700    let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
701    let mut current_key = first_key;
702    let mut expect_value = true;
703    loop {
704        pos = skip_white(buf, pos)?;
705        // 跳过空行 / 纯注释行。
706        while pos < buf.len() && line_is_blank(buf, pos) {
707            let mut p = pos;
708            while p < buf.len() && buf[p] != b'\n' {
709                p += 1;
710            }
711            if p < buf.len() {
712                p += 1;
713            }
714            pos = p;
715        }
716        if pos >= buf.len() {
717            break;
718        }
719        let indent = line_indent(buf, pos);
720        if indent < parent_indent {
721            break;
722        }
723        if expect_value {
724            // 因为 pos 已经在 `:` 之后,这里不需要再 try_read_key。
725            // 但 compact block sequence 的边界:上一层调用方可能在 pos 处
726            // 直接撞上更深的兄弟 key (例如 `- name: alice` 后面的 `    age: 30`),
727            // try_read_key 会匹配,这种情况应当让 current_key 没值并切换。
728            if indent == parent_indent {
729                if let Some((next_key, colon)) = try_read_key(buf, pos) {
730                    map.insert(current_key.clone(), Dynamic::Null);
731                    current_key = next_key;
732                    pos = colon + 1;
733                    expect_value = true;
734                    continue;
735                }
736            }
737            // 解析 value。先看 value 是跟 `:` 同行 (inline),还是已经跳到下一行。
738            // pos 在 `:` 之后;如果当前字符是 `\n`,那 value 一定在下一行;
739            // 如果不是 `\n`,要看是不是到了行尾 (即空格 / tab / 行尾)。
740            let after_value = if pos < buf.len() && buf[pos] == b'\n' {
741                // 已经在 `:` 紧跟的换行处,跳到下一行起点。
742                pos + 1
743            } else {
744                skip_white(buf, pos)?
745            };
746            let on_next_line = pos < buf.len() && buf[pos] == b'\n';
747            if on_next_line || at_line_end(buf, after_value) {
748                // 值在下一行,缩进必须更深一档。
749                let p = after_value;
750                let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
751                if next_indent <= parent_indent {
752                    map.insert(current_key.clone(), Dynamic::Null);
753                    expect_value = false;
754                    pos = p;
755                    continue;
756                }
757                if let Some((next_key, colon)) = try_read_key(buf, p) {
758                    // 续行 mapping:把 next_key 接到 current_key 的 mapping 里。
759                    let value_pos = skip_white(buf, colon + 1)?;
760                    let (value, consumed) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
761                    map.insert(current_key.clone(), value);
762                    pos = consumed;
763                } else {
764                    // 真正的 block value (sequence 或 deeper mapping)。
765                    let (value, consumed) = parse_node(buf, p, next_indent, true)?;
766                    map.insert(current_key.clone(), value);
767                    pos = consumed;
768                }
769            } else {
770                // 同行 inline value。
771                let (value, consumed) = parse_node(buf, after_value, parent_indent, false)?;
772                map.insert(current_key.clone(), value);
773                pos = consumed;
774            }
775            expect_value = false;
776            continue;
777        }
778        // 期待下一个 key。
779        if let Some((next_key, colon)) = try_read_key(buf, pos) {
780            current_key = next_key;
781            pos = colon + 1;
782            expect_value = true;
783        } else {
784            break;
785        }
786    }
787    Ok((Dynamic::Map(Arc::new(RwLock::new(map))), pos))
788}
789
790/// 解析 block 风格的 sequence。`- ` 开头,每项一个。
791fn parse_block_seq(buf: &[u8], mut pos: usize, parent_indent: usize) -> Result<(Dynamic, usize)> {
792    let mut items: Vec<Dynamic> = Vec::new();
793    loop {
794        pos = skip_white(buf, pos)?;
795        while pos < buf.len() && line_is_blank(buf, pos) {
796            let mut p = pos;
797            while p < buf.len() && buf[p] != b'\n' {
798                p += 1;
799            }
800            if p < buf.len() {
801                p += 1;
802            }
803            pos = p;
804        }
805        if pos >= buf.len() {
806            break;
807        }
808        let indent = line_indent(buf, pos);
809        if indent < parent_indent {
810            break;
811        }
812        if buf[pos] != b'-' || (pos + 1 < buf.len() && !matches!(buf[pos + 1], b' ' | b'\t' | b'\n')) {
813            break;
814        }
815        // 跳过 `- ` 前缀。
816        let after_dash = pos + 1;
817        let after_dash = skip_white(buf, after_dash)?;
818        // inline scalar?
819        if !at_line_end(buf, after_dash) {
820            // `- foo` 或 `- key: value` 形式。
821            // 先看是不是 inline mapping(同行带 `key: value`)。
822            if let Some((key, colon)) = try_read_key(buf, after_dash) {
823                let after_colon = skip_white(buf, colon + 1)?;
824                if at_line_end(buf, after_colon) {
825                    // 值在下一行,缩进至少要比 `- ` 之后更深一档。
826                    let mut p = after_colon;
827                    if p < buf.len() && buf[p] == b'\n' {
828                        p += 1;
829                    }
830                    let next_indent = if p < buf.len() { line_indent(buf, p) } else { 0 };
831                    let (value, consumed) = parse_node(buf, p, next_indent.max(parent_indent + 2), true)?;
832                    let mut map = IndexMap::new();
833                    map.insert(key, value);
834                    items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
835                    pos = consumed;
836                } else {
837                    // `- key: value` 同行内联值。处理完首条 entry 后,
838                    // 还要看后面是不是还有以更深缩进延续的 `key: value` 行
839                    // (YAML compact block sequence)。
840                    let (value, consumed) = parse_node(buf, after_colon, parent_indent, false)?;
841                    let mut map = IndexMap::new();
842                    map.insert(key.clone(), value);
843                    // 把已读位置推到行尾之后,准备看下一行。
844                    let mut p = consumed;
845                    if p < buf.len() && buf[p] != b'\n' {
846                        // parse_node 没吞掉换行,手动跳过空白。
847                        p = skip_white(buf, p)?;
848                    }
849                    // 跳过空行 / 注释行,看下一条 entry 是不是更深缩进的 key:。
850                    while p < buf.len() && line_is_blank(buf, p) {
851                        let mut q = p;
852                        while q < buf.len() && buf[q] != b'\n' {
853                            q += 1;
854                        }
855                        if q < buf.len() {
856                            q += 1;
857                        }
858                        p = q;
859                    }
860                    if p < buf.len() {
861                        let next_indent = line_indent(buf, p);
862                        let dash_indent = line_indent(buf, pos);
863                        // 跳过行首缩进,看看是不是 `key:` 续行。
864                        let key_start = p + next_indent;
865                        if next_indent > dash_indent && key_start < buf.len()
866                            && let Some((next_key, colon)) = try_read_key(buf, key_start)
867                        {
868                            // 用 parse_block_map 把后续 key:value 行都收进来。
869                            // parse_block_map 现在期望 pos 在 `:` 之后。
870                            let value_pos = skip_white(buf, colon + 1)?;
871                            let (rest, consumed2) = parse_block_map(buf, value_pos, next_indent - 1, next_key)?;
872                            if let Dynamic::Map(extra) = rest {
873                                for (k, v) in extra.read().iter() {
874                                    map.insert(k.clone(), v.clone());
875                                }
876                            }
877                            p = consumed2;
878                        }
879                    }
880                    items.push(Dynamic::Map(Arc::new(RwLock::new(map))));
881                    pos = p;
882                }
883            } else {
884                let (value, consumed) = parse_node(buf, after_dash, parent_indent, false)?;
885                items.push(value);
886                pos = consumed;
887            }
888            continue;
889        }
890        // 值在下一行,缩进必须比 `- ` 之后更深。
891        let mut p = after_dash;
892        if p < buf.len() && buf[p] == b'\n' {
893            p += 1;
894        }
895        if p >= buf.len() {
896            items.push(Dynamic::Null);
897            break;
898        }
899        let next_indent = line_indent(buf, p);
900        if next_indent <= parent_indent {
901            items.push(Dynamic::Null);
902            break;
903        }
904        let (value, consumed) = parse_node(buf, p, next_indent, true)?;
905        items.push(value);
906        pos = consumed;
907    }
908    Ok((Dynamic::List(Arc::new(RwLock::new(items))), pos))
909}
910
911/// 解析 `{ key: value, ... }` 这种 inline mapping。
912fn parse_flow_map(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
913    let mut p = pos + 1;
914    let mut map: IndexMap<SmolStr, Dynamic> = IndexMap::new();
915    p = skip_white(buf, p)?;
916    if p < buf.len() && buf[p] == b'}' {
917        return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
918    }
919    loop {
920        p = skip_white(buf, p)?;
921        let (key, after) = match try_read_key(buf, p) {
922            Some(pair) => pair,
923            None => return Err(anyhow!("yaml flow mapping 缺少 key @{}", p)),
924        };
925        p = skip_white(buf, after + 1)?;
926        let (value, consumed) = parse_node(buf, p, 0, false)?;
927        map.insert(key, value);
928        p = consumed;
929        p = skip_white(buf, p)?;
930        if p < buf.len() && buf[p] == b',' {
931            p += 1;
932            continue;
933        }
934        if p < buf.len() && buf[p] == b'}' {
935            return Ok((Dynamic::Map(Arc::new(RwLock::new(map))), p + 1));
936        }
937        return Err(anyhow!("yaml flow mapping 缺少 ',' 或 '}}' @{}", p));
938    }
939}
940
941/// 解析 `[ a, b, c ]` 这种 inline sequence。
942fn parse_flow_seq(buf: &[u8], pos: usize) -> Result<(Dynamic, usize)> {
943    let mut p = pos + 1;
944    let mut items: Vec<Dynamic> = Vec::new();
945    p = skip_white(buf, p)?;
946    if p < buf.len() && buf[p] == b']' {
947        return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
948    }
949    loop {
950        p = skip_white(buf, p)?;
951        let (value, consumed) = parse_node(buf, p, 0, false)?;
952        items.push(value);
953        p = consumed;
954        p = skip_white(buf, p)?;
955        if p < buf.len() && buf[p] == b',' {
956            p += 1;
957            continue;
958        }
959        if p < buf.len() && buf[p] == b']' {
960            return Ok((Dynamic::List(Arc::new(RwLock::new(items))), p + 1));
961        }
962        return Err(anyhow!("yaml flow sequence 缺少 ',' 或 ']' @{}", p));
963    }
964}
965
966// ---- Dynamic 上的便捷方法 ------------------------------------------
967
968impl Dynamic {
969    /// 顶层便捷入口:把 `Dynamic` 序列化成 YAML 字符串。
970    pub fn to_yaml_string(&self) -> String {
971        let mut buf = String::new();
972        self.to_yaml(&mut buf);
973        buf
974    }
975
976    /// 顶层便捷入口:从 YAML 文本解析 `Dynamic`。
977    pub fn from_yaml_buf(buf: &[u8]) -> Result<Self> {
978        let (value, _) = <Self as FromYaml>::from_yaml(buf)?;
979        Ok(value)
980    }
981}
982
983impl Dynamic {
984    /// 给 `to_yaml` / 测试用的小工具:在已有 map 里塞一个 entry 并返回 map。
985    fn map_with_entry(key: SmolStr, value: Dynamic) -> Self {
986        let mut map = IndexMap::new();
987        map.insert(key, value);
988        Dynamic::Map(Arc::new(RwLock::new(map)))
989    }
990}
991
992// ---- 测试 --------------------------------------------------------------
993
994#[cfg(test)]
995mod tests {
996    use super::*;
997
998    fn round_trip(input: &str) -> String {
999        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse yaml");
1000        let mut buf = String::new();
1001        value.to_yaml(&mut buf);
1002        buf
1003    }
1004
1005    #[test]
1006    fn simple_scalar_round_trip() {
1007        assert_eq!(round_trip("42"), "42");
1008        assert_eq!(round_trip("-7"), "-7");
1009        assert_eq!(round_trip("3.14"), "3.14");
1010        assert_eq!(round_trip("true"), "true");
1011        assert_eq!(round_trip("false"), "false");
1012        assert_eq!(round_trip("null"), "null");
1013        assert_eq!(round_trip("~"), "null");
1014    }
1015
1016    #[test]
1017    fn string_quoting_rules() {
1018        // 像数字的字符串必须加引号。
1019        let value = Dynamic::String("123".into());
1020        let mut buf = String::new();
1021        value.to_yaml(&mut buf);
1022        assert_eq!(buf, "\"123\"");
1023
1024        // 像 bool 的字符串也要加引号。
1025        let value = Dynamic::String("yes".into());
1026        let mut buf = String::new();
1027        value.to_yaml(&mut buf);
1028        assert_eq!(buf, "\"yes\"");
1029
1030        // 普通字符串裸出。
1031        let value = Dynamic::String("hello world".into());
1032        let mut buf = String::new();
1033        value.to_yaml(&mut buf);
1034        assert_eq!(buf, "hello world");
1035
1036        // 含 `:` 的字符串必须引号。
1037        let value = Dynamic::String("a: b".into());
1038        let mut buf = String::new();
1039        value.to_yaml(&mut buf);
1040        assert_eq!(buf, "\"a: b\"");
1041
1042        // 含 `#` 的字符串要引号。
1043        let value = Dynamic::String("color #ff00ff".into());
1044        let mut buf = String::new();
1045        value.to_yaml(&mut buf);
1046        assert_eq!(buf, "\"color #ff00ff\"");
1047    }
1048
1049    #[test]
1050    fn multiline_string_uses_block_scalar() {
1051        let value = Dynamic::String("line1\nline2\nline3".into());
1052        let mut buf = String::new();
1053        value.to_yaml(&mut buf);
1054        assert!(buf.starts_with("|+\n"), "should use block scalar: {buf}");
1055        assert!(buf.contains("line1\n"));
1056        assert!(buf.contains("line2\n"));
1057        assert!(buf.contains("line3\n"));
1058    }
1059
1060    #[test]
1061    fn mapping_block_style() {
1062        let mut map = IndexMap::new();
1063        map.insert(SmolStr::from("name"), Dynamic::String("zust".into()));
1064        map.insert(SmolStr::from("version"), Dynamic::I64(1));
1065        map.insert(SmolStr::from("active"), Dynamic::Bool(true));
1066        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1067        let mut buf = String::new();
1068        value.to_yaml(&mut buf);
1069        assert!(buf.contains("name: zust"), "{buf}");
1070        assert!(buf.contains("version: 1"), "{buf}");
1071        assert!(buf.contains("active: true"), "{buf}");
1072    }
1073
1074    #[test]
1075    fn nested_mapping_and_list() {
1076        let mut inner = IndexMap::new();
1077        inner.insert(SmolStr::from("a"), Dynamic::I64(1));
1078        inner.insert(SmolStr::from("b"), Dynamic::I64(2));
1079        let mut map = IndexMap::new();
1080        map.insert(SmolStr::from("items"), Dynamic::list(vec![Dynamic::Map(Arc::new(RwLock::new(inner)))]));
1081        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1082        let yaml = value.to_yaml_string();
1083        assert!(yaml.contains("items:\n  - a: 1"), "{yaml}");
1084    }
1085
1086    #[test]
1087    fn block_sequence_round_trip() {
1088        let input = "- 1\n- 2\n- 3\n";
1089        let (value, consumed) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1090        assert!(matches!(value, Dynamic::List(_)));
1091        assert_eq!(consumed, input.len());
1092    }
1093
1094    #[test]
1095    fn block_mapping_round_trip() {
1096        let input = "name: zust\nversion: 1\n";
1097        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1098        let map = value.get_dynamic("name").unwrap();
1099        assert_eq!(map.as_str(), "zust");
1100        assert_eq!(value.get_dynamic("version").and_then(|v| v.as_int()), Some(1));
1101    }
1102
1103    #[test]
1104    fn nested_block_structures() {
1105        let input = "users:\n  - name: alice\n    age: 30\n  - name: bob\n    age: 25\n";
1106        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1107        let users = value.get_dynamic("users").unwrap();
1108        assert!(users.is_list());
1109        let first = users.get_idx(0).unwrap();
1110        assert_eq!(first.get_dynamic("name").unwrap().as_str(), "alice");
1111        assert_eq!(first.get_dynamic("age").unwrap().as_int(), Some(30));
1112    }
1113
1114    #[test]
1115    fn inline_mapping_and_sequence() {
1116        let input = "{a: 1, b: [2, 3]}";
1117        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1118        assert_eq!(value.get_dynamic("a").unwrap().as_int(), Some(1));
1119        let b = value.get_dynamic("b").unwrap();
1120        assert!(b.is_list());
1121    }
1122
1123    #[test]
1124    fn comments_and_blank_lines_are_ignored() {
1125        let input = "# header comment\nname: zust  # trailing comment\n\nversion: 1\n";
1126        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1127        assert_eq!(value.get_dynamic("name").unwrap().as_str(), "zust");
1128        assert_eq!(value.get_dynamic("version").unwrap().as_int(), Some(1));
1129    }
1130
1131    #[test]
1132    fn double_quoted_string_with_escapes() {
1133        let input = "msg: \"hello\\nworld\"\n";
1134        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1135        assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "hello\nworld");
1136    }
1137
1138    #[test]
1139    fn single_quoted_string_literal() {
1140        let input = "msg: 'a: b'\n";
1141        let (value, _) = <Dynamic as FromYaml>::from_yaml(input.as_bytes()).expect("parse");
1142        assert_eq!(value.get_dynamic("msg").unwrap().as_str(), "a: b");
1143    }
1144
1145    #[test]
1146    fn quoted_round_trip_preserves_string_like_number() {
1147        // 字符串 "123" 必须 round-trip 仍然是字符串,而不是整数。
1148        let mut map = IndexMap::new();
1149        map.insert(SmolStr::from("id"), Dynamic::String("123".into()));
1150        let value = Dynamic::Map(Arc::new(RwLock::new(map)));
1151        let yaml = value.to_yaml_string();
1152        assert!(yaml.contains("id: \"123\""), "{yaml}");
1153        let (parsed, _) = <Dynamic as FromYaml>::from_yaml(yaml.as_bytes()).expect("parse");
1154        let id = parsed.get_dynamic("id").unwrap();
1155        assert_eq!(id.as_str(), "123");
1156        assert!(!id.is_int(), "id should stay string");
1157    }
1158
1159    #[test]
1160    fn empty_collection_serialization() {
1161        let value = Dynamic::List(Arc::new(RwLock::new(Vec::new())));
1162        let mut buf = String::new();
1163        value.to_yaml(&mut buf);
1164        assert_eq!(buf, "[]");
1165    }
1166}