rust_yaml/
composer.rs

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