Skip to main content

rust_yaml/
composer.rs

1//! YAML composer for converting events to nodes
2
3use crate::resolver::{PlainScalarType, resolve_plain_scalar, value_tag_error};
4#[cfg(test)]
5use crate::scanner::Scanner;
6use crate::tag::TagResolver;
7use crate::version::YamlVersion;
8use crate::{
9    BasicParser, Error, Limits, Parser, Position, ResourceTracker, Result, Value, parser::EventType,
10};
11use indexmap::IndexMap;
12use std::collections::HashMap;
13
14/// Calculate complexity score for a value (for resource limiting)
15fn calculate_value_complexity(value: &Value) -> Result<usize> {
16    let mut complexity = 1usize;
17
18    match value {
19        Value::Sequence(seq) => {
20            complexity = complexity.saturating_add(seq.len());
21            for item in seq {
22                complexity = complexity.saturating_add(calculate_value_complexity(item)?);
23            }
24        }
25        Value::Mapping(map) => {
26            complexity = complexity.saturating_add(map.len().saturating_mul(2));
27            for (key, val) in map {
28                complexity = complexity.saturating_add(calculate_value_complexity(key)?);
29                complexity = complexity.saturating_add(calculate_value_complexity(val)?);
30            }
31        }
32        _ => {} // Scalars have complexity 1
33    }
34
35    Ok(complexity)
36}
37
38/// Calculate the maximum nesting depth of a value structure
39fn calculate_structure_depth(value: &Value) -> usize {
40    match value {
41        Value::Sequence(seq) => {
42            if seq.is_empty() {
43                1
44            } else {
45                1 + seq.iter().map(calculate_structure_depth).max().unwrap_or(0)
46            }
47        }
48        Value::Mapping(map) => {
49            if map.is_empty() {
50                1
51            } else {
52                1 + map
53                    .values()
54                    .map(calculate_structure_depth)
55                    .max()
56                    .unwrap_or(0)
57            }
58        }
59        _ => 1, // Scalars have depth 1
60    }
61}
62
63/// Trait for YAML composers that convert event streams to node structures
64pub trait Composer {
65    /// Check if there are more documents available
66    fn check_document(&self) -> bool;
67
68    /// Compose the next document
69    ///
70    /// # Errors
71    /// Returns an error if parsing or composition fails
72    fn compose_document(&mut self) -> Result<Option<Value>>;
73
74    /// Get the current position in the stream
75    fn position(&self) -> Position;
76
77    /// Reset the composer state
78    fn reset(&mut self);
79}
80
81/// A basic composer implementation for converting events to nodes
82#[derive(Debug)]
83pub struct BasicComposer {
84    parser: BasicParser,
85    position: Position,
86    anchors: HashMap<String, Value>,
87    limits: Limits,
88    resource_tracker: ResourceTracker,
89    alias_expansion_stack: Vec<String>,
90    current_depth: usize,
91    tag_resolver: TagResolver,
92    /// Active YAML spec version for the current document, set from the
93    /// `%YAML` directive (when present) on each `DocumentStart` event.
94    /// Defaults to [`YamlVersion::V1_2`].
95    yaml_version: YamlVersion,
96}
97
98impl BasicComposer {
99    /// Create a new composer from input string
100    #[must_use]
101    pub fn new(input: String) -> Self {
102        Self::with_limits(input, Limits::default())
103    }
104
105    /// Create a new composer with custom limits
106    #[must_use]
107    pub fn with_limits(input: String, limits: Limits) -> Self {
108        Self {
109            parser: BasicParser::with_limits(input, limits.clone()),
110            position: Position::new(),
111            anchors: HashMap::new(),
112            limits,
113            resource_tracker: ResourceTracker::new(),
114            alias_expansion_stack: Vec::new(),
115            current_depth: 0,
116            tag_resolver: TagResolver::new(),
117            yaml_version: YamlVersion::default(),
118        }
119    }
120
121    /// Create a new composer with eager parsing (for compatibility)
122    #[must_use]
123    pub fn new_eager(input: String) -> Self {
124        Self::new_eager_with_limits(input, Limits::default())
125    }
126
127    /// Create a new composer with eager parsing and custom limits
128    #[must_use]
129    pub fn new_eager_with_limits(input: String, limits: Limits) -> Self {
130        Self {
131            parser: BasicParser::new_eager_with_limits(input, limits.clone()),
132            position: Position::new(),
133            anchors: HashMap::new(),
134            limits,
135            resource_tracker: ResourceTracker::new(),
136            alias_expansion_stack: Vec::new(),
137            current_depth: 0,
138            tag_resolver: TagResolver::new(),
139            yaml_version: YamlVersion::default(),
140        }
141    }
142
143    /// Compose a node from events (recursive)
144    fn compose_node(&mut self) -> Result<Option<Value>> {
145        if !self.parser.check_event() {
146            return Ok(None);
147        }
148
149        let Some(event) = self.parser.get_event()? else {
150            return Ok(None);
151        };
152
153        self.position = event.position;
154
155        match event.event_type {
156            EventType::StreamStart | EventType::StreamEnd => {
157                // Skip stream boundaries, these don't produce nodes
158                self.compose_node()
159            }
160
161            EventType::DocumentStart { version, .. } => {
162                // Capture the YAML version directive (if any) so plain-scalar
163                // resolution in compose_scalar honors `%YAML 1.1`. The
164                // compose_document peek loop also extracts it for the
165                // implicit-StreamStart case, but reach this arm when events
166                // are consumed via compose_node directly.
167                self.yaml_version = version
168                    .map(|(maj, min)| YamlVersion::from_directive(maj, min))
169                    .unwrap_or_default();
170                self.compose_node()
171            }
172
173            EventType::DocumentEnd { .. } => {
174                // Document end, return None to indicate end of document
175                Ok(None)
176            }
177
178            EventType::Scalar {
179                value,
180                anchor,
181                tag,
182                style,
183                ..
184            } => {
185                let scalar_value = if let Some(tag_str) = tag {
186                    // Apply tag if present
187                    self.compose_tagged_scalar(value, tag_str)?
188                } else {
189                    // Use implicit typing
190                    self.compose_scalar(value, style, event.position)?
191                };
192
193                // Store anchor if present
194                if let Some(anchor_name) = anchor {
195                    self.resource_tracker.add_anchor(&self.limits)?;
196                    self.anchors.insert(anchor_name, scalar_value.clone());
197                }
198
199                Ok(Some(scalar_value))
200            }
201
202            EventType::SequenceStart { anchor, .. } => {
203                let sequence = self.compose_sequence()?;
204
205                // Store anchor if present
206                if let Some(anchor_name) = anchor {
207                    if let Some(ref seq) = sequence {
208                        self.resource_tracker.add_anchor(&self.limits)?;
209                        self.anchors.insert(anchor_name, seq.clone());
210                    }
211                }
212
213                Ok(sequence)
214            }
215
216            EventType::MappingStart { anchor, .. } => {
217                let mapping = self.compose_mapping()?;
218
219                // Store anchor if present
220                if let Some(anchor_name) = anchor {
221                    if let Some(ref map) = mapping {
222                        self.resource_tracker.add_anchor(&self.limits)?;
223                        self.anchors.insert(anchor_name, map.clone());
224                    }
225                }
226
227                Ok(mapping)
228            }
229
230            EventType::SequenceEnd | EventType::MappingEnd => {
231                // These collection end events should normally be handled by their respective compose methods.
232                // However, if we encounter them here, it means we're in an unexpected state.
233                // This can happen when the parser generates a flattened structure instead of proper nesting.
234                // Return None to indicate we've reached the end of the current node.
235                Ok(None)
236            }
237
238            EventType::Alias { anchor } => {
239                // Check for cyclic references
240                if self.alias_expansion_stack.contains(&anchor) {
241                    return Err(Error::construction(
242                        event.position,
243                        format!("Cyclic alias reference detected: '{anchor}'"),
244                    ));
245                }
246
247                // Check alias expansion depth limit BEFORE pushing
248                if self.alias_expansion_stack.len() >= self.limits.max_alias_depth {
249                    return Err(Error::construction(
250                        event.position,
251                        format!(
252                            "Maximum alias expansion depth {} exceeded",
253                            self.limits.max_alias_depth
254                        ),
255                    ));
256                }
257
258                // Track alias expansion depth
259                self.resource_tracker.enter_alias(&self.limits)?;
260                self.alias_expansion_stack.push(anchor.clone());
261
262                // Resolve alias to the anchored value
263                let result = match self.anchors.get(&anchor) {
264                    Some(value) => {
265                        // Check if the resolved value's structure depth would exceed alias depth limit
266                        let structure_depth = calculate_structure_depth(value);
267                        if structure_depth > self.limits.max_alias_depth {
268                            return Err(Error::construction(
269                                event.position,
270                                format!(
271                                    "Alias '{}' creates structure with depth {} exceeding max_alias_depth {}",
272                                    anchor, structure_depth, self.limits.max_alias_depth
273                                ),
274                            ));
275                        }
276
277                        // Add complexity score for alias expansion
278                        self.resource_tracker
279                            .add_complexity(&self.limits, calculate_value_complexity(value)?)?;
280                        Ok(Some(value.clone()))
281                    }
282                    None => Err(Error::construction(
283                        event.position,
284                        format!("Unknown anchor '{anchor}'"),
285                    )),
286                };
287
288                // Clean up tracking
289                self.alias_expansion_stack.pop();
290                self.resource_tracker.exit_alias();
291
292                result
293            }
294        }
295    }
296
297    /// Compose a scalar value.
298    ///
299    /// Single- and double-quoted scalars always become `Value::String`.
300    /// Plain, literal, and folded scalars go through the shared
301    /// [`resolve_plain_scalar`] helper so the YAML version (1.1 vs 1.2)
302    /// governs which boolean forms are recognized.
303    ///
304    /// `position` is the scalar's source position, used only to anchor
305    /// the error returned for the YAML 1.1 `!!value` (`=`) tag.
306    fn compose_scalar(
307        &self,
308        value: String,
309        style: crate::parser::ScalarStyle,
310        position: crate::Position,
311    ) -> Result<Value> {
312        match style {
313            crate::parser::ScalarStyle::SingleQuoted | crate::parser::ScalarStyle::DoubleQuoted => {
314                return Ok(Value::String(value));
315            }
316            _ => {}
317        }
318
319        Ok(match resolve_plain_scalar(&value, self.yaml_version) {
320            PlainScalarType::Null => Value::Null,
321            PlainScalarType::Bool(b) => Value::Bool(b),
322            PlainScalarType::Int(i) => Value::Int(i),
323            PlainScalarType::Float(f) => Value::Float(f),
324            PlainScalarType::Str => Value::String(value),
325            PlainScalarType::Value => return Err(value_tag_error(position)),
326        })
327    }
328
329    /// Compose a tagged scalar value
330    fn compose_tagged_scalar(&mut self, value: String, tag_str: String) -> Result<Value> {
331        // Resolve the tag (TagResolver should handle already-resolved URIs)
332        let tag = self.tag_resolver.resolve(&tag_str)?;
333
334        // Apply the tag to the value
335        self.tag_resolver.apply_tag(&tag, &value)
336    }
337
338    /// Compose a sequence
339    fn compose_sequence(&mut self) -> Result<Option<Value>> {
340        // Track depth
341        self.current_depth += 1;
342        self.resource_tracker
343            .check_depth(&self.limits, self.current_depth)?;
344
345        let mut sequence = Vec::new();
346
347        while self.parser.check_event() {
348            // Peek at the next event to see if we're at the end
349            if let Ok(Some(event)) = self.parser.peek_event() {
350                if matches!(event.event_type, EventType::SequenceEnd) {
351                    // Consume the end event
352                    self.parser.get_event()?;
353                    break;
354                } else if matches!(
355                    event.event_type,
356                    EventType::DocumentEnd { .. }
357                        | EventType::DocumentStart { .. }
358                        | EventType::StreamEnd
359                ) {
360                    // Don't consume these - let compose_document handle them
361                    break;
362                }
363            }
364
365            // Compose the next element
366            if let Some(node) = self.compose_node()? {
367                self.resource_tracker.add_collection_item(&self.limits)?;
368                self.resource_tracker.add_complexity(&self.limits, 1)?;
369                sequence.push(node);
370            } else {
371                // If compose_node returns None, we might have hit a document boundary
372                break;
373            }
374        }
375
376        self.current_depth -= 1;
377        Ok(Some(Value::Sequence(sequence)))
378    }
379
380    /// Compose a mapping
381    fn compose_mapping(&mut self) -> Result<Option<Value>> {
382        // Track depth
383        self.current_depth += 1;
384        self.resource_tracker
385            .check_depth(&self.limits, self.current_depth)?;
386
387        let mut mapping = IndexMap::new();
388
389        while self.parser.check_event() {
390            // Peek at the next event to see if we're at the end
391            if let Ok(Some(event)) = self.parser.peek_event() {
392                if matches!(event.event_type, EventType::MappingEnd) {
393                    // Consume the end event
394                    self.parser.get_event()?;
395                    break;
396                } else if matches!(
397                    event.event_type,
398                    EventType::DocumentEnd { .. }
399                        | EventType::DocumentStart { .. }
400                        | EventType::StreamEnd
401                ) {
402                    // Don't consume these - let compose_document handle them
403                    break;
404                }
405            }
406
407            // Compose key
408            let Some(key) = self.compose_node()? else {
409                break;
410            };
411
412            // Compose value
413            let value = self.compose_node()?.unwrap_or(Value::Null);
414
415            // Check for merge key (YAML 1.2 specification)
416            if let Value::String(key_str) = &key {
417                if key_str == "<<" {
418                    // Handle merge key - the value should already be resolved by compose_node()
419                    self.process_merge_key(&mut mapping, &value)?;
420                    continue;
421                }
422            }
423
424            self.resource_tracker.add_collection_item(&self.limits)?;
425            self.resource_tracker.add_complexity(&self.limits, 2)?; // Key-value pair
426            mapping.insert(key, value);
427        }
428
429        self.current_depth -= 1;
430        Ok(Some(Value::Mapping(mapping)))
431    }
432
433    /// Process a merge key by merging values into the current mapping
434    /// The `merge_value` should already be resolved by `compose_node()`
435    fn process_merge_key(
436        &self,
437        mapping: &mut IndexMap<Value, Value>,
438        merge_value: &Value,
439    ) -> Result<()> {
440        match merge_value {
441            // Single mapping to merge
442            Value::Mapping(source_map) => {
443                for (key, value) in source_map {
444                    // Only insert if key doesn't already exist (explicit keys override merged keys)
445                    mapping.entry(key.clone()).or_insert_with(|| value.clone());
446                }
447            }
448
449            // Sequence of mappings to merge (in order)
450            Value::Sequence(sources) => {
451                for source in sources {
452                    if let Value::Mapping(source_map) = source {
453                        for (key, value) in source_map {
454                            // Only insert if key doesn't already exist
455                            mapping.entry(key.clone()).or_insert_with(|| value.clone());
456                        }
457                    } else {
458                        return Err(Error::construction(
459                            self.position,
460                            "Merge key sequence can only contain mappings",
461                        ));
462                    }
463                }
464            }
465
466            _ => {
467                return Err(Error::construction(
468                    self.position,
469                    "Merge key value must be a mapping or sequence of mappings",
470                ));
471            }
472        }
473
474        Ok(())
475    }
476}
477
478impl Composer for BasicComposer {
479    fn check_document(&self) -> bool {
480        // Check if there are events that could form a document
481        if let Ok(Some(event)) = self.parser.peek_event() {
482            !matches!(event.event_type, EventType::StreamEnd)
483        } else {
484            false
485        }
486    }
487
488    fn compose_document(&mut self) -> Result<Option<Value>> {
489        // Check for parser scanning errors first
490        if let Some(error) = self.parser.take_scanning_error() {
491            return Err(error);
492        }
493
494        // Process document start events and extract tag directives + YAML version.
495        while let Ok(Some(event)) = self.parser.peek_event() {
496            if let EventType::DocumentStart { tags, version, .. } = &event.event_type {
497                // Reset YAML version per document (directives don't carry across).
498                self.yaml_version = version
499                    .map(|(maj, min)| YamlVersion::from_directive(maj, min))
500                    .unwrap_or_default();
501
502                // Clear previous document's tag directives
503                self.tag_resolver.clear_directives();
504
505                // Add new tag directives from this document
506                for (handle, prefix) in tags {
507                    self.tag_resolver
508                        .add_directive(handle.clone(), prefix.clone());
509                }
510
511                self.parser.get_event()?; // consume the DocumentStart
512            } else if matches!(event.event_type, EventType::DocumentStart { .. }) {
513                self.parser.get_event()?; // consume the DocumentStart
514            } else {
515                break;
516            }
517        }
518
519        // Compose the actual document content
520        let document = self.compose_node()?;
521
522        // Skip any document end event
523        while let Ok(Some(event)) = self.parser.peek_event() {
524            if matches!(event.event_type, EventType::DocumentEnd { .. }) {
525                self.parser.get_event()?; // consume the DocumentEnd
526            } else {
527                break;
528            }
529        }
530
531        Ok(document)
532    }
533
534    fn position(&self) -> Position {
535        self.position
536    }
537
538    fn reset(&mut self) {
539        self.position = Position::new();
540        self.anchors.clear();
541        self.resource_tracker.reset();
542        self.alias_expansion_stack.clear();
543        self.current_depth = 0;
544        self.tag_resolver = TagResolver::new();
545    }
546}
547
548impl Default for BasicComposer {
549    fn default() -> Self {
550        Self::new(String::new())
551    }
552}
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557    use indexmap::IndexMap;
558
559    #[test]
560    fn test_check_document() {
561        let mut composer = BasicComposer::new_eager("42".to_string());
562        assert!(composer.check_document());
563
564        let _document = composer.compose_document().unwrap();
565        // After composing, may or may not have more documents depending on implementation
566    }
567
568    #[test]
569    fn test_scalar_composition() {
570        let mut composer = BasicComposer::new_eager("42".to_string());
571        let document = composer.compose_document().unwrap().unwrap();
572        assert_eq!(document, Value::Int(42));
573    }
574
575    #[test]
576    fn test_boolean_composition() {
577        let mut composer = BasicComposer::new_eager("true".to_string());
578        let document = composer.compose_document().unwrap().unwrap();
579        assert_eq!(document, Value::Bool(true));
580    }
581
582    #[test]
583    fn test_null_composition() {
584        let mut composer = BasicComposer::new_eager("~".to_string());
585        let document = composer.compose_document().unwrap().unwrap();
586        assert_eq!(document, Value::Null);
587    }
588
589    #[test]
590    fn test_string_composition() {
591        let mut composer = BasicComposer::new_eager("hello world".to_string());
592        let document = composer.compose_document().unwrap().unwrap();
593        assert_eq!(document, Value::String("hello world".to_string()));
594    }
595
596    #[test]
597    fn test_flow_sequence_composition() {
598        let mut composer = BasicComposer::new_eager("[1, 2, 3]".to_string());
599        let document = composer.compose_document().unwrap().unwrap();
600
601        let expected = Value::Sequence(vec![Value::Int(1), Value::Int(2), Value::Int(3)]);
602        assert_eq!(document, expected);
603    }
604
605    #[test]
606    fn test_flow_mapping_composition() {
607        let mut composer = BasicComposer::new_eager("{'key': 'value', 'number': 42}".to_string());
608        let document = composer.compose_document().unwrap().unwrap();
609
610        let mut expected_map = IndexMap::new();
611        expected_map.insert(
612            Value::String("key".to_string()),
613            Value::String("value".to_string()),
614        );
615        expected_map.insert(Value::String("number".to_string()), Value::Int(42));
616        let expected = Value::Mapping(expected_map);
617
618        assert_eq!(document, expected);
619    }
620
621    #[test]
622    fn test_nested_composition() {
623        let yaml_content = "{'users': [{'name': 'Alice', 'age': 30}]}";
624        let mut composer = BasicComposer::new_eager(yaml_content.to_string());
625        let document = composer.compose_document().unwrap().unwrap();
626
627        // Build expected structure
628        let mut user = IndexMap::new();
629        user.insert(
630            Value::String("name".to_string()),
631            Value::String("Alice".to_string()),
632        );
633        user.insert(Value::String("age".to_string()), Value::Int(30));
634
635        let users = Value::Sequence(vec![Value::Mapping(user)]);
636
637        let mut expected = IndexMap::new();
638        expected.insert(Value::String("users".to_string()), users);
639
640        assert_eq!(document, Value::Mapping(expected));
641    }
642
643    #[test]
644    fn test_multiple_types() {
645        let yaml_content = "[42, 'hello', true, null]";
646        let mut composer = BasicComposer::new_eager(yaml_content.to_string());
647        let document = composer.compose_document().unwrap().unwrap();
648
649        let expected = Value::Sequence(vec![
650            Value::Int(42),
651            Value::String("hello".to_string()),
652            Value::Bool(true),
653            Value::Null,
654        ]);
655
656        assert_eq!(document, expected);
657    }
658
659    #[test]
660    fn test_merge_keys_simple() {
661        let yaml_content = r"
662base: &base
663  key: value
664  timeout: 30
665
666test:
667  <<: *base
668  environment: prod
669";
670        let mut composer = BasicComposer::new_eager(yaml_content.to_string());
671        let document = composer.compose_document().unwrap().unwrap();
672
673        if let Value::Mapping(ref map) = document {
674            // Check that base mapping exists
675            assert!(map.contains_key(&Value::String("base".to_string())));
676
677            // Check that test mapping exists and has merged keys
678            if let Some(Value::Mapping(test_map)) = map.get(&Value::String("test".to_string())) {
679                assert!(test_map.contains_key(&Value::String("key".to_string())));
680                assert!(test_map.contains_key(&Value::String("timeout".to_string())));
681                assert!(test_map.contains_key(&Value::String("environment".to_string())));
682
683                // Verify values
684                assert_eq!(
685                    test_map.get(&Value::String("key".to_string())),
686                    Some(&Value::String("value".to_string()))
687                );
688                assert_eq!(
689                    test_map.get(&Value::String("timeout".to_string())),
690                    Some(&Value::Int(30))
691                );
692                assert_eq!(
693                    test_map.get(&Value::String("environment".to_string())),
694                    Some(&Value::String("prod".to_string()))
695                );
696            } else {
697                panic!("test mapping not found or not a mapping");
698            }
699        } else {
700            panic!("Document should be a mapping, got: {:?}", document);
701        }
702    }
703
704    #[test]
705    fn test_debug_alias_tokens() {
706        let yaml_content = r"
707base: &base
708  key: value
709
710ref: *base
711";
712
713        let mut scanner = crate::BasicScanner::new_eager(yaml_content.to_string());
714
715        println!("Scanning tokens for alias test:");
716        let mut token_count = 0;
717
718        while scanner.check_token() {
719            if let Ok(Some(token)) = scanner.get_token() {
720                token_count += 1;
721                println!(
722                    "{}: {:?} at {:?}-{:?}",
723                    token_count, token.token_type, token.start_position, token.end_position
724                );
725            } else {
726                println!("No more tokens");
727                break;
728            }
729        }
730
731        println!("Total tokens: {}", token_count);
732    }
733
734    #[test]
735    fn test_debug_alias_events() {
736        let yaml_content = r"
737base: &base
738  key: value
739
740ref: *base
741";
742
743        let mut parser = BasicParser::new_eager(yaml_content.to_string());
744
745        println!("Parsing events for alias test:");
746        let mut event_count = 0;
747
748        while parser.check_event() {
749            if let Ok(Some(event)) = parser.get_event() {
750                event_count += 1;
751                println!(
752                    "{}: {:?} at {:?}",
753                    event_count, event.event_type, event.position
754                );
755            } else {
756                println!("No more events");
757                break;
758            }
759        }
760
761        println!("Total events: {}", event_count);
762    }
763
764    #[test]
765    fn test_simple_scalar_alias_resolution() {
766        // Test with a simple scalar alias first
767        let yaml_content = r"
768base: &base 'hello world'
769ref: *base
770";
771        let mut composer = BasicComposer::new_eager(yaml_content.to_string());
772        let document = composer.compose_document().unwrap().unwrap();
773
774        println!("Simple alias document: {:?}", document);
775
776        if let Value::Mapping(ref map) = document {
777            println!("Mapping keys: {:?}", map.keys().collect::<Vec<_>>());
778
779            let base_value = map
780                .get(&Value::String("base".to_string()))
781                .expect("base should exist");
782            let ref_value = map
783                .get(&Value::String("ref".to_string()))
784                .expect("ref should exist");
785
786            println!("base_value: {:?}", base_value);
787            println!("ref_value: {:?}", ref_value);
788
789            assert_eq!(base_value, ref_value);
790        } else {
791            panic!("Document should be a mapping, got: {:?}", document);
792        }
793    }
794
795    #[test]
796    fn test_basic_alias_resolution() {
797        let yaml_content = r"
798base: &base
799  key: value
800
801ref: *base
802";
803        let mut composer = BasicComposer::new_eager(yaml_content.to_string());
804        let document = composer.compose_document().unwrap().unwrap();
805
806        println!("Composed document: {:?}", document);
807
808        if let Value::Mapping(ref map) = document {
809            println!("Mapping keys: {:?}", map.keys().collect::<Vec<_>>());
810
811            // Check that both base and ref exist and are equal
812            let base_value = map
813                .get(&Value::String("base".to_string()))
814                .expect("base should exist");
815            let ref_value = map
816                .get(&Value::String("ref".to_string()))
817                .expect("ref should exist");
818
819            println!("base_value: {:?}", base_value);
820            println!("ref_value: {:?}", ref_value);
821
822            // Verify both values are the same nested mapping
823            assert_eq!(
824                base_value, ref_value,
825                "Alias should resolve to the same value as the anchor"
826            );
827
828            // Verify the structure is correct
829            if let Value::Mapping(nested) = base_value {
830                assert_eq!(
831                    nested.get(&Value::String("key".to_string())),
832                    Some(&Value::String("value".to_string()))
833                );
834            } else {
835                panic!("base value should be a nested mapping");
836            }
837
838            println!("✅ Alias resolution working correctly!");
839        } else {
840            panic!("Document should be a mapping, got: {:?}", document);
841        }
842    }
843}