Skip to main content

yaml_edit/
value.rs

1//! Value wrapper that can represent any YAML value type (scalar, sequence, mapping).
2
3use crate::scalar::ScalarValue;
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt;
6
7/// Represents any YAML value - scalar, sequence, mapping, or special collections.
8///
9/// **Deprecated:** Prefer using syntax tree types (`Mapping`, `Sequence`,
10/// `Scalar`) and the `AsYaml` trait instead - they preserve formatting and
11/// work with the CST directly. `YamlValue` loses all formatting information.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum YamlValue {
14    /// A scalar value (string, number, boolean, null)
15    Scalar(ScalarValue),
16    /// A sequence of values (list/array)
17    Sequence(Vec<YamlValue>),
18    /// A mapping of key-value pairs
19    Mapping(BTreeMap<String, YamlValue>),
20    /// A set of unique values (!!set)
21    Set(BTreeSet<String>),
22    /// An ordered mapping preserving key order (!!omap)
23    OrderedMapping(Vec<(String, YamlValue)>),
24    /// A sequence of key-value pairs allowing duplicates (!!pairs)
25    Pairs(Vec<(String, YamlValue)>),
26}
27
28impl YamlValue {
29    /// Try to cast a SyntaxNode to a YamlValue
30    pub fn cast(node: rowan::SyntaxNode<crate::yaml::Lang>) -> Option<Self> {
31        use crate::lex::SyntaxKind;
32        use crate::yaml::{Mapping, Scalar, Sequence, TaggedNode};
33        use rowan::ast::AstNode;
34
35        match node.kind() {
36            SyntaxKind::SCALAR => {
37                // cast() only checks the kind, which we already matched, so it cannot fail here
38                Scalar::cast(node)
39                    .map(|scalar| YamlValue::Scalar(ScalarValue::from_scalar(&scalar)))
40            }
41            SyntaxKind::SEQUENCE => {
42                if let Some(seq) = Sequence::cast(node) {
43                    let items: Vec<YamlValue> = seq.items().filter_map(YamlValue::cast).collect();
44                    Some(YamlValue::Sequence(items))
45                } else {
46                    None
47                }
48            }
49            SyntaxKind::MAPPING => {
50                if let Some(mapping) = Mapping::cast(node) {
51                    let mut map = std::collections::BTreeMap::new();
52                    for (key_node, value_node) in mapping.pairs() {
53                        // pairs() yields the KEY wrapper, so cast through
54                        // extract_scalar to reach the scalar inside it.
55                        // Casting the wrapper directly fails and falls back to
56                        // raw text, which keeps the quotes on `'0'` and makes
57                        // the entry unreachable by its actual key.
58                        let key_str = match crate::yaml::extract_scalar(&key_node) {
59                            Some(key_scalar) => key_scalar.as_string(),
60                            None => key_node.text().to_string(),
61                        };
62
63                        // Convert value node to YamlValue
64                        if let Some(value_yaml) = YamlValue::cast(value_node) {
65                            map.insert(key_str, value_yaml);
66                        }
67                    }
68                    Some(YamlValue::Mapping(map))
69                } else {
70                    None
71                }
72            }
73            SyntaxKind::TAGGED_NODE => {
74                // Delegate to tag-aware special-collection helpers, then fall back
75                // to extracting the plain string value from the inner scalar.
76                TaggedNode::cast(node).and_then(|tagged| {
77                    // Check for special collection tags first.
78                    if let Some(set) = tagged.as_set() {
79                        let members: BTreeSet<String> = set
80                            .members()
81                            .filter_map(|v| v.as_scalar().map(|s| s.as_string()))
82                            .collect();
83                        return Some(YamlValue::Set(members));
84                    }
85                    if let Some(entries) = tagged.as_ordered_mapping() {
86                        let pairs: Vec<(String, YamlValue)> = entries
87                            .into_iter()
88                            .filter_map(|e| {
89                                // key() returns the KEY wrapper node; get its scalar child
90                                let key_node = e.key()?;
91                                let key_scalar = Scalar::cast(key_node.children().next()?)?;
92                                let key = key_scalar.as_string();
93                                let val = e
94                                    .value()
95                                    .and_then(|v| v.children().next().and_then(YamlValue::cast))?;
96                                Some((key, val))
97                            })
98                            .collect();
99                        return Some(YamlValue::OrderedMapping(pairs));
100                    }
101                    if let Some(entries) = tagged.as_pairs() {
102                        let pairs: Vec<(String, YamlValue)> = entries
103                            .into_iter()
104                            .filter_map(|e| {
105                                let key_node = e.key()?;
106                                let key_scalar = Scalar::cast(key_node.children().next()?)?;
107                                let key = key_scalar.as_string();
108                                let val = e
109                                    .value()
110                                    .and_then(|v| v.children().next().and_then(YamlValue::cast))?;
111                                Some((key, val))
112                            })
113                            .collect();
114                        return Some(YamlValue::Pairs(pairs));
115                    }
116                    // Plain tagged scalar: return the string value.
117                    tagged
118                        .as_string()
119                        .map(|s| YamlValue::Scalar(ScalarValue::string(s)))
120                })
121            }
122            SyntaxKind::VALUE => {
123                // VALUE nodes contain the actual content as children
124                node.children().next().and_then(YamlValue::cast)
125            }
126            _ => None,
127        }
128    }
129
130    /// Create a scalar value
131    pub fn scalar(value: impl Into<ScalarValue>) -> Self {
132        YamlValue::Scalar(value.into())
133    }
134
135    /// Create an empty sequence
136    pub const fn sequence() -> Self {
137        YamlValue::Sequence(Vec::new())
138    }
139
140    /// Create a sequence from a vector
141    pub const fn from_sequence(items: Vec<YamlValue>) -> Self {
142        YamlValue::Sequence(items)
143    }
144
145    /// Create an empty mapping
146    pub const fn mapping() -> Self {
147        YamlValue::Mapping(BTreeMap::new())
148    }
149
150    /// Parse a raw YAML string into a YamlValue
151    /// This handles both simple scalars and complex structures
152    pub fn parse_raw(yaml_str: &str) -> Self {
153        use std::str::FromStr;
154
155        // Try to parse as a complete YAML document
156        if let Ok(parsed) = crate::YamlFile::from_str(yaml_str) {
157            if let Some(doc) = parsed.document() {
158                return Self::from_document(&doc);
159            }
160        }
161
162        // Fallback: treat as a scalar value
163        YamlValue::Scalar(ScalarValue::parse(yaml_str))
164    }
165
166    /// Convert a Document to a YamlValue
167    pub fn from_document(doc: &crate::yaml::Document) -> Self {
168        use rowan::ast::AstNode;
169        doc.syntax()
170            .children()
171            .find_map(Self::cast)
172            .unwrap_or_else(|| YamlValue::Scalar(ScalarValue::string("")))
173    }
174
175    /// Create a mapping from a BTreeMap
176    pub const fn from_mapping(map: BTreeMap<String, YamlValue>) -> Self {
177        YamlValue::Mapping(map)
178    }
179
180    /// Create an empty set
181    pub const fn set() -> Self {
182        YamlValue::Set(BTreeSet::new())
183    }
184
185    /// Create a set from a BTreeSet
186    pub const fn from_set(set: BTreeSet<String>) -> Self {
187        YamlValue::Set(set)
188    }
189
190    /// Create an empty ordered mapping
191    pub const fn ordered_mapping() -> Self {
192        YamlValue::OrderedMapping(Vec::new())
193    }
194
195    /// Create an ordered mapping from a vector of pairs
196    pub const fn from_ordered_mapping(pairs: Vec<(String, YamlValue)>) -> Self {
197        YamlValue::OrderedMapping(pairs)
198    }
199
200    /// Create empty pairs
201    pub const fn pairs() -> Self {
202        YamlValue::Pairs(Vec::new())
203    }
204
205    /// Create pairs from a vector of pairs
206    pub const fn from_pairs(pairs: Vec<(String, YamlValue)>) -> Self {
207        YamlValue::Pairs(pairs)
208    }
209
210    /// Return `true` if this value is a scalar (string, integer, float, bool, null, etc.).
211    ///
212    /// Use [`as_scalar`](Self::as_scalar) to access the inner [`ScalarValue`].
213    #[inline]
214    pub fn is_scalar(&self) -> bool {
215        matches!(self, YamlValue::Scalar(_))
216    }
217
218    /// Return `true` if this value is a sequence (YAML list / array).
219    ///
220    /// Use [`as_sequence`](Self::as_sequence) to access the inner sequence.
221    #[inline]
222    pub fn is_sequence(&self) -> bool {
223        matches!(self, YamlValue::Sequence(_))
224    }
225
226    /// Return `true` if this value is a plain mapping (`key: value` pairs).
227    ///
228    /// Use [`as_mapping`](Self::as_mapping) to access the inner mapping.
229    #[inline]
230    pub fn is_mapping(&self) -> bool {
231        matches!(self, YamlValue::Mapping(_))
232    }
233
234    /// Return `true` if this value is a `!!set` tagged collection.
235    #[inline]
236    pub fn is_set(&self) -> bool {
237        matches!(self, YamlValue::Set(_))
238    }
239
240    /// Return `true` if this value is a `!!omap` (ordered mapping) tagged collection.
241    #[inline]
242    pub fn is_ordered_mapping(&self) -> bool {
243        matches!(self, YamlValue::OrderedMapping(_))
244    }
245
246    /// Return `true` if this value is a `!!pairs` tagged collection.
247    #[inline]
248    pub fn is_pairs(&self) -> bool {
249        matches!(self, YamlValue::Pairs(_))
250    }
251
252    /// Get as scalar if this is a scalar
253    pub fn as_scalar(&self) -> Option<&ScalarValue> {
254        match self {
255            YamlValue::Scalar(s) => Some(s),
256            _ => None,
257        }
258    }
259
260    /// Get as sequence if this is a sequence
261    pub fn as_sequence(&self) -> Option<&[YamlValue]> {
262        match self {
263            YamlValue::Sequence(seq) => Some(seq),
264            _ => None,
265        }
266    }
267
268    /// Get as mutable sequence if this is a sequence
269    pub fn as_sequence_mut(&mut self) -> Option<&mut Vec<YamlValue>> {
270        match self {
271            YamlValue::Sequence(seq) => Some(seq),
272            _ => None,
273        }
274    }
275
276    /// Get as mapping if this is a mapping
277    pub fn as_mapping(&self) -> Option<&BTreeMap<String, YamlValue>> {
278        match self {
279            YamlValue::Mapping(map) => Some(map),
280            _ => None,
281        }
282    }
283
284    /// Get as mutable mapping if this is a mapping
285    pub fn as_mapping_mut(&mut self) -> Option<&mut BTreeMap<String, YamlValue>> {
286        match self {
287            YamlValue::Mapping(map) => Some(map),
288            _ => None,
289        }
290    }
291
292    /// Get as set if this is a set
293    pub fn as_set(&self) -> Option<&BTreeSet<String>> {
294        match self {
295            YamlValue::Set(set) => Some(set),
296            _ => None,
297        }
298    }
299
300    /// Get as mutable set if this is a set
301    pub fn as_set_mut(&mut self) -> Option<&mut BTreeSet<String>> {
302        match self {
303            YamlValue::Set(set) => Some(set),
304            _ => None,
305        }
306    }
307
308    /// Get as ordered mapping if this is an ordered mapping
309    pub fn as_ordered_mapping(&self) -> Option<&[(String, YamlValue)]> {
310        match self {
311            YamlValue::OrderedMapping(pairs) => Some(pairs),
312            _ => None,
313        }
314    }
315
316    /// Get as mutable ordered mapping if this is an ordered mapping
317    pub fn as_ordered_mapping_mut(&mut self) -> Option<&mut Vec<(String, YamlValue)>> {
318        match self {
319            YamlValue::OrderedMapping(pairs) => Some(pairs),
320            _ => None,
321        }
322    }
323
324    /// Get as pairs if this is pairs
325    pub fn as_pairs(&self) -> Option<&[(String, YamlValue)]> {
326        match self {
327            YamlValue::Pairs(pairs) => Some(pairs),
328            _ => None,
329        }
330    }
331
332    /// Get as mutable pairs if this is pairs
333    pub fn as_pairs_mut(&mut self) -> Option<&mut Vec<(String, YamlValue)>> {
334        match self {
335            YamlValue::Pairs(pairs) => Some(pairs),
336            _ => None,
337        }
338    }
339
340    /// Get a reference to the string value if this is a scalar
341    ///
342    /// Returns the string representation of the scalar value.
343    /// Returns None for non-scalar types (mappings, sequences, etc).
344    /// Note: All scalars can be represented as strings, regardless of their type.
345    pub fn as_str(&self) -> Option<&str> {
346        match self {
347            YamlValue::Scalar(s) => Some(s.value()),
348            _ => None,
349        }
350    }
351
352    /// Try to convert this value to an i64
353    ///
354    /// Returns the integer value if this is a scalar with Integer type.
355    /// Supports various formats (decimal, hexadecimal 0x, binary 0b, octal 0o).
356    /// Returns None if this is not a scalar or not an integer type.
357    ///
358    /// Note: This respects YAML's type system - `42` (unquoted) is an integer,
359    /// but `"42"` (quoted) is a string and will return None.
360    pub fn to_i64(&self) -> Option<i64> {
361        self.as_scalar().and_then(ScalarValue::to_i64)
362    }
363
364    /// Try to convert this value to an f64
365    ///
366    /// Returns the float value if this is a scalar with Float type.
367    /// Returns None if this is not a scalar or not a float type.
368    ///
369    /// Note: This respects YAML's type system - `3.14` (unquoted) is a float,
370    /// but `"3.14"` (quoted) is a string and will return None.
371    pub fn to_f64(&self) -> Option<f64> {
372        self.as_scalar().and_then(ScalarValue::to_f64)
373    }
374
375    /// Try to convert this value to a bool
376    ///
377    /// Returns the boolean value if this is a scalar with Boolean type.
378    /// Recognizes: true, false, yes, no, on, off (case-insensitive).
379    /// Returns None if this is not a scalar or not a boolean type.
380    ///
381    /// Note: This respects YAML's type system - `no` (unquoted) is boolean,
382    /// but `"no"` (quoted) is a string and will return None.
383    pub fn to_bool(&self) -> Option<bool> {
384        self.as_scalar().and_then(ScalarValue::to_bool)
385    }
386
387    /// Convert to YAML string representation
388    pub fn to_yaml_string(&self, indent: usize) -> String {
389        match self {
390            YamlValue::Scalar(s) => s.to_yaml_string(),
391            YamlValue::Sequence(seq) => {
392                if seq.is_empty() {
393                    "[]".to_string()
394                } else {
395                    let mut result = String::with_capacity(seq.len() * 20); // Pre-allocate
396                    let indent_str = " ".repeat(indent);
397                    for item in seq {
398                        result.push_str(&indent_str);
399                        result.push_str("  - ");
400                        match item {
401                            YamlValue::Scalar(s) => result.push_str(&s.to_yaml_string()),
402                            _ => {
403                                result.push('\n');
404                                result.push_str(&item.to_yaml_string(indent + 4));
405                            }
406                        }
407                        result.push('\n');
408                    }
409                    result.truncate(result.trim_end().len());
410                    result
411                }
412            }
413            YamlValue::Mapping(map) => {
414                if map.is_empty() {
415                    "{}".to_string()
416                } else {
417                    let mut result = String::with_capacity(map.len() * 30); // Pre-allocate
418                    let indent_str = " ".repeat(indent);
419                    for (key, value) in map {
420                        result.push_str(&indent_str);
421                        result.push_str(key);
422                        result.push_str(": ");
423                        match value {
424                            YamlValue::Scalar(s) => result.push_str(&s.to_yaml_string()),
425                            _ => {
426                                result.push('\n');
427                                result.push_str(&value.to_yaml_string(indent + 2));
428                            }
429                        }
430                        result.push('\n');
431                    }
432                    result.truncate(result.trim_end().len());
433                    result
434                }
435            }
436            YamlValue::Set(set) => {
437                // Sets are represented as mappings with null values
438                if set.is_empty() {
439                    "!!set {}".to_string()
440                } else {
441                    let mut result = String::from("!!set");
442                    result.push('\n');
443                    let indent_str = " ".repeat(indent);
444                    for item in set {
445                        result.push_str(&indent_str);
446                        result.push_str(item);
447                        result.push_str(": null");
448                        result.push('\n');
449                    }
450                    result.truncate(result.trim_end().len());
451                    result
452                }
453            }
454            YamlValue::OrderedMapping(pairs) => Self::render_tagged_pairs("!!omap", pairs, indent),
455            YamlValue::Pairs(pairs) => Self::render_tagged_pairs("!!pairs", pairs, indent),
456        }
457    }
458
459    /// Render `!!omap` / `!!pairs`: a tagged sequence of single-key mappings.
460    fn render_tagged_pairs(tag: &str, pairs: &[(String, YamlValue)], indent: usize) -> String {
461        if pairs.is_empty() {
462            return format!("{tag} []");
463        }
464        let mut result = String::from(tag);
465        result.push('\n');
466        let indent_str = " ".repeat(indent);
467        for (key, value) in pairs {
468            result.push_str(&indent_str);
469            result.push_str("  - ");
470            result.push_str(key);
471            result.push_str(": ");
472            match value {
473                YamlValue::Scalar(s) => result.push_str(&s.to_yaml_string()),
474                _ => {
475                    result.push('\n');
476                    result.push_str(&value.to_yaml_string(indent + 4));
477                }
478            }
479            result.push('\n');
480        }
481        result.truncate(result.trim_end().len());
482        result
483    }
484}
485
486impl fmt::Display for YamlValue {
487    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488        write!(f, "{}", self.to_yaml_string(0))
489    }
490}
491
492// Convenience conversions from common types
493impl From<String> for YamlValue {
494    fn from(value: String) -> Self {
495        YamlValue::Scalar(ScalarValue::from(value))
496    }
497}
498
499impl From<&str> for YamlValue {
500    fn from(value: &str) -> Self {
501        YamlValue::Scalar(ScalarValue::from(value))
502    }
503}
504
505impl From<i32> for YamlValue {
506    fn from(value: i32) -> Self {
507        YamlValue::Scalar(ScalarValue::from(value))
508    }
509}
510
511impl From<i64> for YamlValue {
512    fn from(value: i64) -> Self {
513        YamlValue::Scalar(ScalarValue::from(value))
514    }
515}
516
517impl From<f32> for YamlValue {
518    fn from(value: f32) -> Self {
519        YamlValue::Scalar(ScalarValue::from(value))
520    }
521}
522
523impl From<f64> for YamlValue {
524    fn from(value: f64) -> Self {
525        YamlValue::Scalar(ScalarValue::from(value))
526    }
527}
528
529impl From<bool> for YamlValue {
530    fn from(value: bool) -> Self {
531        YamlValue::Scalar(ScalarValue::from(value))
532    }
533}
534
535impl From<ScalarValue> for YamlValue {
536    fn from(value: ScalarValue) -> Self {
537        YamlValue::Scalar(value)
538    }
539}
540
541impl From<&YamlValue> for YamlValue {
542    fn from(value: &YamlValue) -> Self {
543        value.clone()
544    }
545}
546
547impl<T> From<Vec<T>> for YamlValue
548where
549    T: Into<YamlValue>,
550{
551    fn from(vec: Vec<T>) -> Self {
552        let values: Vec<_> = vec.into_iter().map(Into::into).collect();
553        YamlValue::Sequence(values)
554    }
555}
556
557impl<K, V> From<BTreeMap<K, V>> for YamlValue
558where
559    K: Into<String>,
560    V: Into<YamlValue>,
561{
562    fn from(map: BTreeMap<K, V>) -> Self {
563        YamlValue::Mapping(map.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
564    }
565}
566
567impl<T> From<BTreeSet<T>> for YamlValue
568where
569    T: Into<String>,
570{
571    fn from(set: BTreeSet<T>) -> Self {
572        YamlValue::Set(set.into_iter().map(Into::into).collect())
573    }
574}
575
576#[cfg(test)]
577mod tests {
578    use super::*;
579
580    fn render_as_yaml<T: crate::AsYaml>(v: &T) -> String {
581        let mut builder = rowan::GreenNodeBuilder::new();
582        v.build_content(&mut builder, 0, false);
583        let green = builder.finish();
584        let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
585        node.text().to_string()
586    }
587
588    fn scalar_token_kind(v: &YamlValue) -> crate::lex::SyntaxKind {
589        use rowan::ast::AstNode;
590        let mut builder = rowan::GreenNodeBuilder::new();
591        crate::AsYaml::build_content(v, &mut builder, 0, false);
592        let green = builder.finish();
593        let node = rowan::SyntaxNode::<crate::yaml::Lang>::new_root(green);
594        let scalar = crate::yaml::Scalar::cast(node).expect("scalar node");
595        scalar
596            .syntax()
597            .children_with_tokens()
598            .filter_map(|c| c.into_token())
599            .next()
600            .expect("token")
601            .kind()
602    }
603
604    #[test]
605    fn test_scalar_build_content_token_kind() {
606        use crate::lex::SyntaxKind;
607        assert_eq!(
608            scalar_token_kind(&YamlValue::from("hello")),
609            SyntaxKind::STRING
610        );
611        assert_eq!(scalar_token_kind(&YamlValue::from(42)), SyntaxKind::INT);
612        assert_eq!(scalar_token_kind(&YamlValue::from(1.5)), SyntaxKind::FLOAT);
613        // Strings that happen to spell a bool or null keep their String type
614        // and are quoted, so they tokenize as STRING - rendering them bare
615        // would change their type on the next parse.
616        assert_eq!(
617            scalar_token_kind(&YamlValue::Scalar(ScalarValue::string("true"))),
618            SyntaxKind::STRING
619        );
620        assert_eq!(
621            scalar_token_kind(&YamlValue::Scalar(ScalarValue::string("false"))),
622            SyntaxKind::STRING
623        );
624        assert_eq!(
625            scalar_token_kind(&YamlValue::Scalar(ScalarValue::string("null"))),
626            SyntaxKind::STRING
627        );
628        // Parsed values keep their detected type.
629        assert_eq!(
630            scalar_token_kind(&YamlValue::Scalar(ScalarValue::parse("true"))),
631            SyntaxKind::BOOL
632        );
633        assert_eq!(
634            scalar_token_kind(&YamlValue::Scalar(ScalarValue::parse("null"))),
635            SyntaxKind::NULL
636        );
637    }
638
639    #[test]
640    fn test_scalar_build_content_quotes_like_scalar_value() {
641        // YamlValue::Scalar delegates to ScalarValue so that strings needing
642        // quotes get them. Emitting the raw value would re-parse as a
643        // different type, or (for "a: b") as a different structure.
644        for raw in ["null", "true", "42", "a: b", "", "#c", "- x"] {
645            let sv = ScalarValue::string(raw);
646            assert_eq!(
647                render_as_yaml(&YamlValue::Scalar(sv.clone())),
648                render_as_yaml(&sv),
649                "YamlValue::Scalar({raw:?}) must render like the ScalarValue"
650            );
651        }
652    }
653
654    #[test]
655    fn test_as_yaml_build_content_tagged_and_nested() {
656        // Tagged collections render the tag, a newline, then content indented by
657        // two spaces (indent + 2). A wrong arithmetic op would drop the indent.
658        let omap = YamlValue::from_ordered_mapping(vec![
659            ("a".to_string(), YamlValue::from("1")),
660            ("b".to_string(), YamlValue::from("2")),
661        ]);
662        assert_eq!(render_as_yaml(&omap), "!!omap\n  - a: '1'\n  - b: '2'\n");
663
664        let pairs = YamlValue::from_pairs(vec![("a".to_string(), YamlValue::from("1"))]);
665        assert_eq!(render_as_yaml(&pairs), "!!pairs\n  - a: '1'\n");
666
667        let set = YamlValue::from_set(BTreeSet::from(["a".to_string(), "b".to_string()]));
668        assert_eq!(render_as_yaml(&set), "!!set\n  a: null\n  b: null\n");
669
670        // A mapping whose value is a multi-entry mapping. The inner mapping's
671        // own entry separators are indented by the indent passed down
672        // (BTreeMap::build_content indent + 2), so a wrong op shows up on the
673        // second inner key.
674        let mut inner = BTreeMap::new();
675        inner.insert("x".to_string(), YamlValue::from("1"));
676        inner.insert("y".to_string(), YamlValue::from("2"));
677        let mut outer = BTreeMap::new();
678        outer.insert("o".to_string(), YamlValue::Mapping(inner));
679        assert_eq!(render_as_yaml(&outer), "o:\n  x: '1'\n  y: '2'\n");
680
681        // Pairs with two entries: the indent passed into the nested sequence
682        // shows on the second item's "  - " separator (Pairs branch indent + 2).
683        let pairs2 = YamlValue::from_pairs(vec![
684            ("a".to_string(), YamlValue::from("1")),
685            ("b".to_string(), YamlValue::from("2")),
686        ]);
687        assert_eq!(render_as_yaml(&pairs2), "!!pairs\n  - a: '1'\n  - b: '2'\n");
688
689        // A sequence nested inside a sequence (&[]::build_content indent + 2).
690        let seq = vec![YamlValue::Sequence(vec![YamlValue::from("a")])];
691        assert_eq!(render_as_yaml(&seq), "- - a\n");
692
693        // A sequence whose single item is a multi-entry mapping: the indent
694        // handed to that item (&[]::build_content indent + 2) shows on the
695        // second mapping key.
696        let mut item = BTreeMap::new();
697        item.insert("x".to_string(), YamlValue::from("1"));
698        item.insert("y".to_string(), YamlValue::from("2"));
699        let seq_of_map = vec![YamlValue::Mapping(item)];
700        assert_eq!(render_as_yaml(&seq_of_map), "- x: '1'\n  y: '2'\n");
701    }
702
703    #[test]
704    fn test_as_yaml_build_content() {
705        use crate::AsYaml;
706
707        // Vec<YamlValue> and &[YamlValue] render as a block sequence.
708        let seq = vec![YamlValue::from("a"), YamlValue::from("b")];
709        assert_eq!(render_as_yaml(&seq), "- a\n- b\n");
710        assert_eq!(render_as_yaml(&seq.as_slice()), "- a\n- b\n");
711
712        // BTreeMap renders as a block mapping (keys sorted).
713        let mut m = BTreeMap::new();
714        m.insert("k".to_string(), YamlValue::from("v"));
715        m.insert("j".to_string(), YamlValue::from("w"));
716        assert_eq!(render_as_yaml(&m), "j: w\nk: v\n");
717
718        // Empty collections render in flow style.
719        let empty_seq: Vec<YamlValue> = vec![];
720        assert_eq!(render_as_yaml(&empty_seq), "[]");
721        let empty_map: BTreeMap<String, YamlValue> = BTreeMap::new();
722        assert_eq!(render_as_yaml(&empty_map), "{}");
723
724        // A mapping whose value is a non-scalar gets a newline and deeper indent.
725        let mut m2 = BTreeMap::new();
726        m2.insert(
727            "list".to_string(),
728            YamlValue::Sequence(vec![YamlValue::from("a")]),
729        );
730        assert_eq!(render_as_yaml(&m2), "list:\n  - a\n");
731
732        // YamlValue delegates to the right impl per variant.
733        assert_eq!(
734            render_as_yaml(&YamlValue::Sequence(seq.clone())),
735            "- a\n- b\n"
736        );
737        assert_eq!(
738            render_as_yaml(&YamlValue::Mapping(m.clone())),
739            "j: w\nk: v\n"
740        );
741        assert_eq!(render_as_yaml(&YamlValue::from("scalar")), "scalar");
742
743        // is_inline: empty collections are inline, non-empty block collections
744        // are not. Scalars are always inline.
745        assert!(!AsYaml::is_inline(&seq));
746        assert!(AsYaml::is_inline(&empty_seq));
747        assert!(!AsYaml::is_inline(&m));
748        assert!(AsYaml::is_inline(&empty_map));
749        assert!(!AsYaml::is_inline(&seq.as_slice()));
750        assert!(AsYaml::is_inline(&empty_seq.as_slice()));
751        assert!(AsYaml::is_inline(&YamlValue::from("x")));
752        assert!(!AsYaml::is_inline(&YamlValue::Sequence(seq)));
753        assert!(AsYaml::is_inline(&YamlValue::sequence()));
754        assert!(!AsYaml::is_inline(&YamlValue::Mapping(m)));
755        assert!(AsYaml::is_inline(&YamlValue::mapping()));
756    }
757
758    #[test]
759    fn test_parse_raw_variants() {
760        // parse_raw goes through YamlValue::cast, so each YAML shape must map to
761        // the corresponding variant.
762        assert!(YamlValue::parse_raw("hello").is_scalar());
763
764        let seq = YamlValue::parse_raw("- a\n- b\n");
765        assert_eq!(
766            seq.as_sequence(),
767            Some([YamlValue::from("a"), YamlValue::from("b")].as_slice())
768        );
769
770        let map = YamlValue::parse_raw("k: v\n");
771        assert_eq!(map.as_mapping().map(|m| m.len()), Some(1));
772
773        let set = YamlValue::parse_raw("!!set\n  a: ~\n  b: ~\n");
774        assert_eq!(
775            set.as_set(),
776            Some(&BTreeSet::from(["a".to_string(), "b".to_string()]))
777        );
778
779        let omap = YamlValue::parse_raw("!!omap\n  - a: 1\n  - b: 2\n");
780        assert_eq!(
781            omap.as_ordered_mapping(),
782            Some(
783                [
784                    ("a".to_string(), YamlValue::from(1)),
785                    ("b".to_string(), YamlValue::from(2)),
786                ]
787                .as_slice()
788            )
789        );
790
791        let pairs = YamlValue::parse_raw("!!pairs\n  - a: 1\n  - a: 2\n");
792        assert_eq!(
793            pairs.as_pairs(),
794            Some(
795                [
796                    ("a".to_string(), YamlValue::from(1)),
797                    ("a".to_string(), YamlValue::from(2)),
798                ]
799                .as_slice()
800            )
801        );
802    }
803
804    #[test]
805    fn test_display_matches_to_yaml_string() {
806        let map = {
807            let mut m = BTreeMap::new();
808            m.insert("name".to_string(), YamlValue::from("app"));
809            YamlValue::Mapping(m)
810        };
811        assert_eq!(format!("{}", map), map.to_yaml_string(0));
812        assert_eq!(format!("{}", map), "name: app");
813
814        let scalar = YamlValue::from("hello");
815        assert_eq!(format!("{}", scalar), "hello");
816    }
817
818    #[test]
819    fn test_to_yaml_string_nested_indentation() {
820        // Sequence holding a non-scalar item: nested mapping is indented by
821        // indent + 4 relative to the dash.
822        let mut inner = BTreeMap::new();
823        inner.insert("h".to_string(), YamlValue::from("x"));
824        let seq = YamlValue::Sequence(vec![YamlValue::Mapping(inner)]);
825        assert_eq!(seq.to_yaml_string(0), "  - \n    h: x");
826
827        // Mapping holding a non-scalar value: nested sequence is rendered at
828        // indent + 2.
829        let mut m = BTreeMap::new();
830        m.insert(
831            "k".to_string(),
832            YamlValue::Sequence(vec![YamlValue::from("a")]),
833        );
834        assert_eq!(YamlValue::Mapping(m).to_yaml_string(0), "k: \n    - a");
835
836        // Ordered mapping with a non-scalar value: nested mapping at indent + 4.
837        let mut inner2 = BTreeMap::new();
838        inner2.insert("x".to_string(), YamlValue::from("y"));
839        let omap =
840            YamlValue::from_ordered_mapping(vec![("a".to_string(), YamlValue::Mapping(inner2))]);
841        assert_eq!(omap.to_yaml_string(0), "!!omap\n  - a: \n    x: y");
842
843        // Pairs with a non-scalar value: nested sequence at indent + 4.
844        let pairs = YamlValue::from_pairs(vec![(
845            "a".to_string(),
846            YamlValue::Sequence(vec![YamlValue::from("z")]),
847        )]);
848        assert_eq!(pairs.to_yaml_string(0), "!!pairs\n  - a: \n      - z");
849    }
850
851    #[test]
852    fn test_scalar_value() {
853        let val = YamlValue::from("hello");
854        assert!(val.is_scalar());
855        assert_eq!(val.to_yaml_string(0), "hello");
856
857        let val = YamlValue::from(42);
858        assert!(val.is_scalar());
859        assert_eq!(val.to_yaml_string(0), "42");
860
861        let val = YamlValue::from(true);
862        assert!(val.is_scalar());
863        assert_eq!(val.to_yaml_string(0), "true");
864    }
865
866    #[test]
867    fn test_sequence_value() {
868        let val = YamlValue::from(vec!["item1", "item2", "item3"]);
869        assert!(val.is_sequence());
870        let yaml = val.to_yaml_string(0);
871        assert_eq!(yaml, "  - item1\n  - item2\n  - item3");
872
873        // Empty sequence
874        let val = YamlValue::sequence();
875        assert_eq!(val.to_yaml_string(0), "[]");
876    }
877
878    #[test]
879    fn test_mapping_value() {
880        let mut map = BTreeMap::new();
881        map.insert("name", YamlValue::from("project"));
882        map.insert("version", YamlValue::from("1.0.0"));
883
884        let val = YamlValue::from(map);
885        assert!(val.is_mapping());
886        let yaml = val.to_yaml_string(0);
887        assert_eq!(yaml, "name: project\nversion: 1.0.0");
888
889        // Empty mapping
890        let val = YamlValue::mapping();
891        assert_eq!(val.to_yaml_string(0), "{}");
892    }
893
894    #[test]
895    fn test_nested_structure() {
896        let mut db_map = BTreeMap::new();
897        db_map.insert("host", YamlValue::from("localhost"));
898        db_map.insert("port", YamlValue::from(5432));
899
900        let mut root_map = BTreeMap::new();
901        root_map.insert("name", YamlValue::from("app"));
902        root_map.insert("database", YamlValue::from(db_map));
903        root_map.insert("features", YamlValue::from(vec!["auth", "logging"]));
904
905        let val = YamlValue::from(root_map);
906        let yaml = val.to_yaml_string(0);
907        assert_eq!(
908            yaml,
909            "database: \n  host: localhost\n  port: 5432\nfeatures: \n    - auth\n    - logging\nname: app"
910        );
911    }
912
913    #[test]
914    fn test_set_value() {
915        let mut set = BTreeSet::new();
916        set.insert("item1".to_string());
917        set.insert("item2".to_string());
918        set.insert("item3".to_string());
919
920        let val = YamlValue::from_set(set);
921        assert!(val.is_set());
922        let yaml = val.to_yaml_string(0);
923        assert_eq!(yaml, "!!set\nitem1: null\nitem2: null\nitem3: null");
924
925        // Empty set
926        let val = YamlValue::set();
927        assert_eq!(val.to_yaml_string(0), "!!set {}");
928    }
929
930    #[test]
931    fn test_ordered_mapping_value() {
932        let pairs = vec![
933            ("first".to_string(), YamlValue::from("value1")),
934            ("second".to_string(), YamlValue::from("value2")),
935            ("third".to_string(), YamlValue::from("value3")),
936        ];
937
938        let val = YamlValue::from_ordered_mapping(pairs);
939        assert!(val.is_ordered_mapping());
940        let yaml = val.to_yaml_string(0);
941        assert_eq!(
942            yaml,
943            "!!omap\n  - first: value1\n  - second: value2\n  - third: value3"
944        );
945
946        // Empty ordered mapping
947        let val = YamlValue::ordered_mapping();
948        assert_eq!(val.to_yaml_string(0), "!!omap []");
949    }
950
951    #[test]
952    fn test_pairs_value() {
953        let pairs = vec![
954            ("key1".to_string(), YamlValue::from("value1")),
955            ("key1".to_string(), YamlValue::from("value2")), // Duplicate key allowed
956            ("key2".to_string(), YamlValue::from("value3")),
957        ];
958
959        let val = YamlValue::from_pairs(pairs);
960        assert!(val.is_pairs());
961        let yaml = val.to_yaml_string(0);
962        assert_eq!(
963            yaml,
964            "!!pairs\n  - key1: value1\n  - key1: value2\n  - key2: value3"
965        );
966
967        // Empty pairs
968        let val = YamlValue::pairs();
969        assert_eq!(val.to_yaml_string(0), "!!pairs []");
970    }
971
972    #[test]
973    fn test_predicates_and_accessors() {
974        let scalar = YamlValue::from("s");
975        let seq = YamlValue::Sequence(vec![YamlValue::from("a")]);
976        let map = {
977            let mut m = BTreeMap::new();
978            m.insert("k".to_string(), YamlValue::from("v"));
979            YamlValue::Mapping(m)
980        };
981        let set = YamlValue::from_set(BTreeSet::from(["x".to_string()]));
982        let omap = YamlValue::from_ordered_mapping(vec![("a".to_string(), YamlValue::from("b"))]);
983        let pairs = YamlValue::from_pairs(vec![("a".to_string(), YamlValue::from("b"))]);
984
985        // Each predicate is true only for its own variant.
986        assert!(scalar.is_scalar());
987        assert!(!seq.is_scalar());
988
989        assert!(seq.is_sequence());
990        assert!(!scalar.is_sequence());
991
992        assert!(map.is_mapping());
993        assert!(!seq.is_mapping());
994
995        assert!(set.is_set());
996        assert!(!map.is_set());
997
998        assert!(omap.is_ordered_mapping());
999        assert!(!map.is_ordered_mapping());
1000
1001        assert!(pairs.is_pairs());
1002        assert!(!omap.is_pairs());
1003
1004        // Accessors return Some only for the matching variant, None otherwise.
1005        assert!(scalar.as_scalar().is_some());
1006        assert!(seq.as_scalar().is_none());
1007
1008        assert_eq!(seq.as_sequence(), Some([YamlValue::from("a")].as_slice()));
1009        assert_eq!(scalar.as_sequence(), None);
1010
1011        assert_eq!(map.as_mapping().map(|m| m.len()), Some(1));
1012        assert_eq!(scalar.as_mapping(), None);
1013
1014        assert_eq!(set.as_set().map(|s| s.len()), Some(1));
1015        assert_eq!(map.as_set(), None);
1016
1017        assert_eq!(omap.as_ordered_mapping().map(|p| p.len()), Some(1));
1018        assert_eq!(map.as_ordered_mapping(), None);
1019
1020        assert_eq!(pairs.as_pairs().map(|p| p.len()), Some(1));
1021        assert_eq!(omap.as_pairs(), None);
1022    }
1023
1024    #[test]
1025    fn test_accessors_mut() {
1026        let mut seq = YamlValue::Sequence(vec![YamlValue::from("a")]);
1027        seq.as_sequence_mut().unwrap().push(YamlValue::from("b"));
1028        assert_eq!(seq.as_sequence().map(|s| s.len()), Some(2));
1029        assert!(YamlValue::from("x").as_sequence_mut().is_none());
1030
1031        let mut map = YamlValue::mapping();
1032        map.as_mapping_mut()
1033            .unwrap()
1034            .insert("k".to_string(), YamlValue::from("v"));
1035        assert_eq!(map.as_mapping().map(|m| m.len()), Some(1));
1036        assert!(YamlValue::from("x").as_mapping_mut().is_none());
1037
1038        let mut set = YamlValue::set();
1039        set.as_set_mut().unwrap().insert("a".to_string());
1040        assert_eq!(set.as_set().map(|s| s.len()), Some(1));
1041        assert!(YamlValue::from("x").as_set_mut().is_none());
1042
1043        let mut omap = YamlValue::ordered_mapping();
1044        omap.as_ordered_mapping_mut()
1045            .unwrap()
1046            .push(("a".to_string(), YamlValue::from("b")));
1047        assert_eq!(omap.as_ordered_mapping().map(|p| p.len()), Some(1));
1048        assert!(YamlValue::from("x").as_ordered_mapping_mut().is_none());
1049
1050        let mut pairs = YamlValue::pairs();
1051        pairs
1052            .as_pairs_mut()
1053            .unwrap()
1054            .push(("a".to_string(), YamlValue::from("b")));
1055        assert_eq!(pairs.as_pairs().map(|p| p.len()), Some(1));
1056        assert!(YamlValue::from("x").as_pairs_mut().is_none());
1057    }
1058
1059    #[test]
1060    fn test_as_str() {
1061        // Test with different scalar types - all should return Some
1062        let string_val = YamlValue::from("hello");
1063        assert_eq!(string_val.as_str(), Some("hello"));
1064
1065        let int_val = YamlValue::from(42);
1066        assert_eq!(int_val.as_str(), Some("42"));
1067
1068        let float_val = YamlValue::from(1.5);
1069        assert_eq!(float_val.as_str(), Some("1.5"));
1070
1071        let bool_val = YamlValue::from(true);
1072        assert_eq!(bool_val.as_str(), Some("true"));
1073
1074        // Test with non-scalar types - should return None
1075        let mapping = YamlValue::mapping();
1076        assert_eq!(mapping.as_str(), None);
1077
1078        let sequence = YamlValue::sequence();
1079        assert_eq!(sequence.as_str(), None);
1080
1081        let set = YamlValue::set();
1082        assert_eq!(set.as_str(), None);
1083    }
1084
1085    #[test]
1086    fn test_to_i64() {
1087        use crate::scalar::ScalarValue;
1088
1089        // Unquoted integers should parse
1090        let int_val = YamlValue::Scalar(ScalarValue::parse("42"));
1091        assert_eq!(int_val.to_i64(), Some(42));
1092
1093        let neg_int = YamlValue::Scalar(ScalarValue::parse("-123"));
1094        assert_eq!(neg_int.to_i64(), Some(-123));
1095
1096        // Hexadecimal
1097        let hex_val = YamlValue::Scalar(ScalarValue::parse("0x2A"));
1098        assert_eq!(hex_val.to_i64(), Some(42));
1099
1100        // Binary
1101        let bin_val = YamlValue::Scalar(ScalarValue::parse("0b101010"));
1102        assert_eq!(bin_val.to_i64(), Some(42));
1103
1104        // Octal
1105        let oct_val = YamlValue::Scalar(ScalarValue::parse("0o52"));
1106        assert_eq!(oct_val.to_i64(), Some(42));
1107
1108        // Quoted string "42" is a string, not an integer
1109        let string_val = YamlValue::from("42");
1110        assert_eq!(string_val.to_i64(), None);
1111
1112        // Boolean values should not convert to integers
1113        let bool_val = YamlValue::Scalar(ScalarValue::parse("true"));
1114        assert_eq!(bool_val.to_i64(), None);
1115
1116        let no_val = YamlValue::Scalar(ScalarValue::parse("no"));
1117        assert_eq!(no_val.to_i64(), None);
1118
1119        // Float values should not convert to integers
1120        let float_val = YamlValue::Scalar(ScalarValue::parse("3.14"));
1121        assert_eq!(float_val.to_i64(), None);
1122
1123        // Non-scalar types should return None
1124        let mapping = YamlValue::mapping();
1125        assert_eq!(mapping.to_i64(), None);
1126
1127        let sequence = YamlValue::sequence();
1128        assert_eq!(sequence.to_i64(), None);
1129
1130        // Random strings should not convert
1131        let random_string = YamlValue::from("blah");
1132        assert_eq!(random_string.to_i64(), None);
1133
1134        // Large integers
1135        let large_int = YamlValue::Scalar(ScalarValue::parse("23432"));
1136        assert_eq!(large_int.to_i64(), Some(23432));
1137
1138        // Numbers that look like booleans but are typed as integers should work
1139        let zero = YamlValue::Scalar(ScalarValue::parse("0"));
1140        assert_eq!(zero.to_i64(), Some(0));
1141
1142        let one = YamlValue::Scalar(ScalarValue::parse("1"));
1143        assert_eq!(one.to_i64(), Some(1));
1144
1145        // But quoted "1" and "0" are strings
1146        let quoted_one = YamlValue::from("1");
1147        assert_eq!(quoted_one.to_i64(), None);
1148
1149        let quoted_zero = YamlValue::from("0");
1150        assert_eq!(quoted_zero.to_i64(), None);
1151    }
1152
1153    #[test]
1154    fn test_to_f64() {
1155        use crate::scalar::ScalarValue;
1156
1157        // Unquoted floats should parse
1158        let float_val = YamlValue::Scalar(ScalarValue::parse("1.5"));
1159        assert_eq!(float_val.to_f64(), Some(1.5));
1160
1161        let neg_float = YamlValue::Scalar(ScalarValue::parse("-2.5"));
1162        assert_eq!(neg_float.to_f64(), Some(-2.5));
1163
1164        // Scientific notation
1165        let sci_val = YamlValue::Scalar(ScalarValue::parse("1.23e10"));
1166        assert_eq!(sci_val.to_f64(), Some(1.23e10));
1167
1168        // Quoted string "3.14" is a string, not a float
1169        let string_val = YamlValue::from("3.14");
1170        assert_eq!(string_val.to_f64(), None);
1171
1172        // Integer values should not convert to floats (type mismatch)
1173        let int_val = YamlValue::Scalar(ScalarValue::parse("42"));
1174        assert_eq!(int_val.to_f64(), None);
1175
1176        // Boolean values should not convert
1177        let bool_val = YamlValue::Scalar(ScalarValue::parse("true"));
1178        assert_eq!(bool_val.to_f64(), None);
1179
1180        // Non-scalar types should return None
1181        let mapping = YamlValue::mapping();
1182        assert_eq!(mapping.to_f64(), None);
1183
1184        let sequence = YamlValue::sequence();
1185        assert_eq!(sequence.to_f64(), None);
1186
1187        // Random strings should not convert
1188        let random_string = YamlValue::from("blah");
1189        assert_eq!(random_string.to_f64(), None);
1190    }
1191
1192    #[test]
1193    fn test_to_bool() {
1194        use crate::scalar::ScalarValue;
1195
1196        // Unquoted boolean values should parse
1197        let true_val = YamlValue::Scalar(ScalarValue::parse("true"));
1198        assert_eq!(true_val.to_bool(), Some(true));
1199
1200        let false_val = YamlValue::Scalar(ScalarValue::parse("false"));
1201        assert_eq!(false_val.to_bool(), Some(false));
1202
1203        let yes_val = YamlValue::Scalar(ScalarValue::parse("yes"));
1204        assert_eq!(yes_val.to_bool(), Some(true));
1205
1206        let no_val = YamlValue::Scalar(ScalarValue::parse("no"));
1207        assert_eq!(no_val.to_bool(), Some(false));
1208
1209        let on_val = YamlValue::Scalar(ScalarValue::parse("on"));
1210        assert_eq!(on_val.to_bool(), Some(true));
1211
1212        let off_val = YamlValue::Scalar(ScalarValue::parse("off"));
1213        assert_eq!(off_val.to_bool(), Some(false));
1214
1215        // Case insensitivity
1216        let true_upper = YamlValue::Scalar(ScalarValue::parse("TRUE"));
1217        assert_eq!(true_upper.to_bool(), Some(true));
1218
1219        let yes_mixed = YamlValue::Scalar(ScalarValue::parse("Yes"));
1220        assert_eq!(yes_mixed.to_bool(), Some(true));
1221
1222        // Quoted strings are NOT booleans
1223        let quoted_true = YamlValue::from("true");
1224        assert_eq!(quoted_true.to_bool(), None);
1225
1226        let quoted_false = YamlValue::from("false");
1227        assert_eq!(quoted_false.to_bool(), None);
1228
1229        let quoted_yes = YamlValue::from("yes");
1230        assert_eq!(quoted_yes.to_bool(), None);
1231
1232        let quoted_no = YamlValue::from("no");
1233        assert_eq!(quoted_no.to_bool(), None);
1234
1235        // Integers are NOT booleans (even 0 and 1)
1236        let zero = YamlValue::Scalar(ScalarValue::parse("0"));
1237        assert_eq!(zero.to_bool(), None);
1238
1239        let one = YamlValue::Scalar(ScalarValue::parse("1"));
1240        assert_eq!(one.to_bool(), None);
1241
1242        let large_int = YamlValue::Scalar(ScalarValue::parse("23432"));
1243        assert_eq!(large_int.to_bool(), None);
1244
1245        // Quoted "1" and "0" are strings, not booleans
1246        let quoted_one = YamlValue::from("1");
1247        assert_eq!(quoted_one.to_bool(), None);
1248
1249        let quoted_zero = YamlValue::from("0");
1250        assert_eq!(quoted_zero.to_bool(), None);
1251
1252        // Random strings should not convert
1253        let random_string = YamlValue::from("blah");
1254        assert_eq!(random_string.to_bool(), None);
1255
1256        // Non-scalar types should return None
1257        let mapping = YamlValue::mapping();
1258        assert_eq!(mapping.to_bool(), None);
1259
1260        let sequence = YamlValue::sequence();
1261        assert_eq!(sequence.to_bool(), None);
1262
1263        // Float values are not booleans
1264        let float_val = YamlValue::Scalar(ScalarValue::parse("3.14"));
1265        assert_eq!(float_val.to_bool(), None);
1266    }
1267}
1268
1269// AsYaml trait implementation for YamlValue
1270use crate::as_yaml::{AsYaml, YamlKind};
1271use crate::yaml::SyntaxNode;
1272
1273/// Build a `!!omap` / `!!pairs` TAGGED_NODE wrapping a sequence of
1274/// single-entry mappings. Returns whether the content ended with a newline.
1275fn build_tagged_pair_seq(
1276    builder: &mut rowan::GreenNodeBuilder,
1277    tag: &str,
1278    pairs: &[(String, YamlValue)],
1279    indent: usize,
1280) -> bool {
1281    use crate::lex::SyntaxKind;
1282
1283    builder.start_node(SyntaxKind::TAGGED_NODE.into());
1284    builder.token(SyntaxKind::TAG.into(), tag);
1285    builder.token(SyntaxKind::NEWLINE.into(), "\n");
1286    builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent + 2));
1287
1288    let seq: Vec<YamlValue> = pairs
1289        .iter()
1290        .map(|(k, v)| {
1291            let mut map = BTreeMap::new();
1292            map.insert(k.clone(), v.clone());
1293            YamlValue::Mapping(map)
1294        })
1295        .collect();
1296    let ends_with_newline = seq.as_slice().build_content(builder, indent + 2, false);
1297
1298    builder.finish_node(); // TAGGED_NODE
1299    ends_with_newline
1300}
1301
1302impl AsYaml for YamlValue {
1303    fn as_node(&self) -> Option<&SyntaxNode> {
1304        None
1305    }
1306
1307    fn kind(&self) -> YamlKind {
1308        match self {
1309            YamlValue::Scalar(_) => YamlKind::Scalar,
1310            YamlValue::Sequence(_) => YamlKind::Sequence,
1311            YamlValue::Mapping(_) => YamlKind::Mapping,
1312            YamlValue::Set(_) => YamlKind::Tagged(std::borrow::Cow::Borrowed("!!set")),
1313            YamlValue::OrderedMapping(_) => YamlKind::Tagged(std::borrow::Cow::Borrowed("!!omap")),
1314            YamlValue::Pairs(_) => YamlKind::Tagged(std::borrow::Cow::Borrowed("!!pairs")),
1315        }
1316    }
1317
1318    fn build_content(
1319        &self,
1320        builder: &mut rowan::GreenNodeBuilder,
1321        indent: usize,
1322        flow_context: bool,
1323    ) -> bool {
1324        use crate::lex::SyntaxKind;
1325        match self {
1326            YamlValue::Scalar(s) => s.build_content(builder, indent, flow_context),
1327            YamlValue::Mapping(map) => map.build_content(builder, indent, flow_context),
1328            YamlValue::Sequence(seq) => seq.as_slice().build_content(builder, indent, flow_context),
1329            YamlValue::Set(set) => {
1330                if set.is_empty() {
1331                    // Empty set in flow style: !!set {}
1332                    builder.start_node(SyntaxKind::TAGGED_NODE.into());
1333                    builder.token(SyntaxKind::TAG.into(), "!!set");
1334                    builder.token(SyntaxKind::WHITESPACE.into(), " ");
1335                    builder.start_node(SyntaxKind::MAPPING.into());
1336                    builder.token(SyntaxKind::LEFT_BRACE.into(), "{");
1337                    builder.token(SyntaxKind::RIGHT_BRACE.into(), "}");
1338                    builder.finish_node(); // MAPPING
1339                    builder.finish_node(); // TAGGED_NODE
1340                    return false;
1341                }
1342
1343                // Non-empty set: wrap in TAGGED_NODE node
1344                builder.start_node(SyntaxKind::TAGGED_NODE.into());
1345                builder.token(SyntaxKind::TAG.into(), "!!set");
1346                builder.token(SyntaxKind::NEWLINE.into(), "\n");
1347                builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent + 2));
1348
1349                // Build as a mapping where each key maps to null (set semantics)
1350                let map: BTreeMap<String, YamlValue> = set
1351                    .iter()
1352                    .map(|k| (k.clone(), YamlValue::Scalar(ScalarValue::null())))
1353                    .collect();
1354                let ends_with_newline = map.build_content(builder, indent + 2, false);
1355
1356                builder.finish_node(); // TAGGED_NODE
1357                                       // Return whether the inner content ended with newline
1358                ends_with_newline
1359            }
1360            YamlValue::OrderedMapping(pairs) => {
1361                build_tagged_pair_seq(builder, "!!omap", pairs, indent)
1362            }
1363            YamlValue::Pairs(pairs) => build_tagged_pair_seq(builder, "!!pairs", pairs, indent),
1364        }
1365    }
1366
1367    fn is_inline(&self) -> bool {
1368        match self {
1369            YamlValue::Scalar(_) => true,
1370            YamlValue::Mapping(map) => map.is_empty(),
1371            YamlValue::Sequence(seq) => seq.is_empty(),
1372            // For programmatically created tagged collections, we use the common style:
1373            // tag inline with key (e.g., "tags: !!set"), even when non-empty.
1374            // Note: Parsed YAML may have tags on separate lines, which is also valid,
1375            // but for YamlValue we choose this as the default formatting.
1376            YamlValue::Set(_) => true,
1377            YamlValue::OrderedMapping(_) => true,
1378            YamlValue::Pairs(_) => true,
1379        }
1380    }
1381}
1382
1383#[derive(Debug, Clone, Copy)]
1384enum CollectionStyle {
1385    Flow,
1386    Block,
1387}
1388
1389impl AsYaml for BTreeMap<String, YamlValue> {
1390    fn as_node(&self) -> Option<&SyntaxNode> {
1391        None
1392    }
1393
1394    fn kind(&self) -> YamlKind {
1395        YamlKind::Mapping
1396    }
1397
1398    fn build_content(
1399        &self,
1400        builder: &mut rowan::GreenNodeBuilder,
1401        indent: usize,
1402        _flow_context: bool,
1403    ) -> bool {
1404        use crate::lex::SyntaxKind;
1405
1406        let style = if self.is_empty() {
1407            CollectionStyle::Flow
1408        } else {
1409            CollectionStyle::Block
1410        };
1411
1412        builder.start_node(SyntaxKind::MAPPING.into());
1413
1414        match style {
1415            CollectionStyle::Flow => {
1416                builder.token(SyntaxKind::LEFT_BRACE.into(), "{");
1417                builder.token(SyntaxKind::RIGHT_BRACE.into(), "}");
1418            }
1419            CollectionStyle::Block => {
1420                let indent_str = " ".repeat(indent);
1421                let entries: Vec<_> = self.iter().collect();
1422
1423                for (i, (key, value)) in entries.iter().enumerate() {
1424                    if i > 0 {
1425                        // Previous entry's newline serves as separator, just add indent
1426                        builder.token(SyntaxKind::INDENT.into(), &indent_str);
1427                    }
1428
1429                    builder.start_node(SyntaxKind::MAPPING_ENTRY.into());
1430
1431                    builder.start_node(SyntaxKind::KEY.into());
1432                    key.as_str().build_content(builder, 0, false);
1433                    builder.finish_node(); // KEY
1434
1435                    builder.token(SyntaxKind::COLON.into(), ":");
1436
1437                    builder.start_node(SyntaxKind::VALUE.into());
1438
1439                    let is_complex =
1440                        value.kind() == YamlKind::Mapping || value.kind() == YamlKind::Sequence;
1441
1442                    let value_ends_with_newline = if is_complex {
1443                        builder.token(SyntaxKind::NEWLINE.into(), "\n");
1444                        builder.token(SyntaxKind::INDENT.into(), &" ".repeat(indent + 2));
1445                        value.build_content(builder, indent + 2, _flow_context)
1446                    } else {
1447                        builder.token(SyntaxKind::WHITESPACE.into(), " ");
1448                        value.build_content(builder, 0, _flow_context)
1449                    };
1450
1451                    builder.finish_node(); // VALUE
1452
1453                    // Every MAPPING_ENTRY ends with newline
1454                    // Only add if value didn't already end with one
1455                    if !value_ends_with_newline {
1456                        builder.token(SyntaxKind::NEWLINE.into(), "\n");
1457                    }
1458
1459                    builder.finish_node(); // MAPPING_ENTRY
1460                }
1461            }
1462        }
1463
1464        builder.finish_node(); // MAPPING
1465                               // Only block-style non-empty mappings end with newline (from last entry)
1466        matches!(style, CollectionStyle::Block) && !self.is_empty()
1467    }
1468
1469    fn is_inline(&self) -> bool {
1470        self.is_empty()
1471    }
1472}
1473
1474impl AsYaml for Vec<YamlValue> {
1475    fn as_node(&self) -> Option<&SyntaxNode> {
1476        None
1477    }
1478
1479    fn kind(&self) -> YamlKind {
1480        YamlKind::Sequence
1481    }
1482
1483    fn build_content(
1484        &self,
1485        builder: &mut rowan::GreenNodeBuilder,
1486        indent: usize,
1487        flow_context: bool,
1488    ) -> bool {
1489        <&[YamlValue] as AsYaml>::build_content(&self.as_slice(), builder, indent, flow_context)
1490    }
1491
1492    fn is_inline(&self) -> bool {
1493        <&[YamlValue] as AsYaml>::is_inline(&self.as_slice())
1494    }
1495}
1496
1497impl AsYaml for &[YamlValue] {
1498    fn as_node(&self) -> Option<&SyntaxNode> {
1499        None
1500    }
1501
1502    fn kind(&self) -> YamlKind {
1503        YamlKind::Sequence
1504    }
1505
1506    fn build_content(
1507        &self,
1508        builder: &mut rowan::GreenNodeBuilder,
1509        indent: usize,
1510        _flow_context: bool,
1511    ) -> bool {
1512        use crate::lex::SyntaxKind;
1513
1514        let style = if self.is_empty() {
1515            CollectionStyle::Flow
1516        } else {
1517            CollectionStyle::Block
1518        };
1519
1520        builder.start_node(SyntaxKind::SEQUENCE.into());
1521
1522        match style {
1523            CollectionStyle::Flow => {
1524                builder.token(SyntaxKind::LEFT_BRACKET.into(), "[");
1525                builder.token(SyntaxKind::RIGHT_BRACKET.into(), "]");
1526            }
1527            CollectionStyle::Block => {
1528                let indent_str = " ".repeat(indent);
1529
1530                for (i, item) in self.iter().enumerate() {
1531                    if i > 0 {
1532                        // Previous entry's newline serves as separator, just add indent
1533                        builder.token(SyntaxKind::INDENT.into(), &indent_str);
1534                    }
1535
1536                    builder.start_node(SyntaxKind::SEQUENCE_ENTRY.into());
1537                    builder.token(SyntaxKind::DASH.into(), "-");
1538                    builder.token(SyntaxKind::WHITESPACE.into(), " ");
1539                    let item_ends_with_newline =
1540                        item.build_content(builder, indent + 2, _flow_context);
1541
1542                    // Every block-style SEQUENCE_ENTRY ends with NEWLINE
1543                    // Only add if item didn't already end with one
1544                    if !item_ends_with_newline {
1545                        builder.token(SyntaxKind::NEWLINE.into(), "\n");
1546                    }
1547
1548                    builder.finish_node(); // SEQUENCE_ENTRY
1549                }
1550            }
1551        }
1552
1553        builder.finish_node(); // SEQUENCE
1554                               // Only block-style non-empty sequences end with newline (from last entry)
1555        matches!(style, CollectionStyle::Block) && !self.is_empty()
1556    }
1557
1558    fn is_inline(&self) -> bool {
1559        self.is_empty()
1560    }
1561}