rust_yaml/
lib.rs

1//! # rust-yaml
2//!
3//! A fast, safe YAML library for Rust - port of ruamel-yaml
4//!
5//! This library provides comprehensive YAML 1.2 support with focus on:
6//! - Security: Memory safety, no unsafe operations
7//! - Performance: Zero-copy parsing, efficient memory usage
8//! - Reliability: Comprehensive error handling, deterministic behavior
9//! - Maintainability: Clean architecture, extensive testing
10
11#![deny(unsafe_code)]
12#![warn(missing_docs)]
13#![warn(clippy::all)]
14#![warn(clippy::pedantic)]
15#![allow(clippy::module_name_repetitions)]
16#![allow(clippy::result_large_err)]
17#![allow(clippy::uninlined_format_args)]
18#![allow(clippy::approx_constant)]
19#![allow(clippy::too_many_lines)]
20#![allow(clippy::unnecessary_wraps)]
21#![allow(clippy::missing_errors_doc)]
22#![allow(clippy::must_use_candidate)]
23#![allow(clippy::return_self_not_must_use)]
24#![allow(clippy::unused_self)]
25#![allow(clippy::only_used_in_recursion)]
26#![allow(clippy::manual_let_else)]
27#![allow(clippy::needless_pass_by_value)]
28#![allow(clippy::map_unwrap_or)]
29#![allow(clippy::redundant_closure_for_method_calls)]
30#![allow(clippy::inefficient_to_string)]
31#![allow(clippy::doc_markdown)]
32#![allow(clippy::match_same_arms)]
33#![allow(clippy::unnecessary_map_or)]
34#![allow(clippy::len_zero)]
35#![allow(clippy::field_reassign_with_default)]
36#![allow(clippy::single_match_else)]
37#![allow(clippy::clone_on_copy)]
38#![allow(clippy::unwrap_or_default)]
39#![allow(clippy::cast_possible_truncation)]
40#![allow(clippy::format_push_string)]
41#![allow(clippy::missing_panics_doc)]
42#![allow(clippy::struct_excessive_bools)]
43#![allow(clippy::cast_sign_loss)]
44#![allow(clippy::explicit_iter_loop)]
45#![allow(clippy::ignored_unit_patterns)]
46#![allow(clippy::no_effect_underscore_binding)]
47#![allow(clippy::collapsible_if)]
48#![allow(clippy::comparison_chain)]
49#![allow(clippy::collapsible_else_if)]
50#![allow(clippy::redundant_pattern_matching)]
51#![allow(clippy::cast_precision_loss)]
52#![allow(dead_code)]
53#![allow(clippy::needless_pass_by_ref_mut)]
54#![allow(clippy::missing_const_for_fn)]
55#![allow(clippy::manual_contains)]
56#![allow(clippy::option_if_let_else)]
57#![allow(clippy::elidable_lifetime_names)]
58#![allow(clippy::derive_partial_eq_without_eq)]
59#![allow(clippy::needless_borrow)]
60#![allow(clippy::cast_possible_wrap)]
61#![allow(clippy::wildcard_imports)]
62#![allow(clippy::or_fun_call)]
63#![allow(clippy::if_not_else)]
64#![allow(clippy::manual_strip)]
65#![allow(clippy::range_plus_one)]
66#![allow(clippy::get_first)]
67#![allow(clippy::use_self)]
68#![allow(clippy::needless_raw_string_hashes)]
69#![allow(unused_mut)]
70#![allow(clippy::single_match)]
71#![allow(clippy::manual_flatten)]
72#![allow(unused_variables)]
73#![allow(clippy::while_let_on_iterator)]
74#![allow(clippy::collapsible_if)]
75
76pub mod composer;
77pub mod composer_borrowed;
78pub mod composer_comments;
79pub mod composer_optimized;
80pub mod constructor;
81pub mod emitter;
82pub mod error;
83pub mod limits;
84pub mod parser;
85pub mod position;
86pub mod profiling;
87pub mod representer;
88pub mod resolver;
89pub mod scanner;
90pub mod schema;
91pub mod serializer;
92#[cfg(feature = "async")]
93pub mod streaming_async;
94pub mod streaming_enhanced;
95pub mod tag;
96pub mod value;
97pub mod value_borrowed;
98pub mod yaml;
99pub mod zero_copy_value;
100pub mod zerocopy;
101
102// Re-exports for convenience
103pub use error::{Error, Result};
104pub use limits::{Limits, ResourceStats, ResourceTracker};
105pub use position::Position;
106pub use scanner::QuoteStyle;
107pub use schema::{
108    Schema, SchemaRule, SchemaValidator, ValidationError, ValidationResult, ValueType,
109};
110pub use value::{CommentedValue, Comments, IndentStyle, Style, Value};
111pub use value_borrowed::BorrowedValue;
112pub use yaml::{LoaderType, Yaml, YamlConfig};
113pub use zero_copy_value::OptimizedValue;
114
115// Re-export commonly used types from components
116pub use composer::{BasicComposer, Composer};
117pub use composer_borrowed::{BorrowedComposer, ZeroCopyComposer};
118pub use composer_comments::CommentPreservingComposer;
119pub use composer_optimized::{OptimizedComposer, ReducedAllocComposer};
120pub use constructor::{
121    CommentPreservingConstructor, Constructor, RoundTripConstructor, SafeConstructor,
122};
123pub use emitter::{BasicEmitter, Emitter};
124pub use parser::{
125    BasicParser, Event, EventType, Parser, StreamingConfig, StreamingParser, StreamingStats,
126};
127pub use representer::{Representer, SafeRepresenter};
128pub use resolver::{BasicResolver, Resolver};
129pub use scanner::{BasicScanner, Scanner, Token, TokenType};
130pub use serializer::{BasicSerializer, Serializer};
131pub use streaming_enhanced::{
132    stream_from_file, stream_from_string, StreamConfig, StreamingYamlParser,
133};
134pub use zerocopy::{ScannerStats, TokenPool, ZeroScanner, ZeroString, ZeroToken, ZeroTokenType};
135// pub use profiling::{YamlProfiler, StringInterner, ObjectPool}; // Temporarily disabled
136
137#[cfg(feature = "serde")]
138pub mod serde_integration;
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::parser::ScalarStyle;
144
145    #[test]
146    fn test_basic_functionality() {
147        let yaml = Yaml::new();
148        let value = yaml.load_str("42").unwrap();
149        assert_eq!(value, Value::Int(42));
150    }
151
152    #[test]
153    fn test_error_creation() {
154        let pos = Position::new();
155        let error = Error::parse(pos, "test error");
156        assert!(error.to_string().contains("test error"));
157    }
158
159    #[test]
160    fn test_value_types() {
161        assert_eq!(Value::Null, Value::Null);
162        assert_eq!(Value::Bool(true), Value::Bool(true));
163        assert_eq!(Value::Int(42), Value::Int(42));
164        assert_eq!(Value::Float(3.14), Value::Float(3.14));
165        assert_eq!(
166            Value::String("test".to_string()),
167            Value::String("test".to_string())
168        );
169    }
170
171    #[test]
172    fn test_anchor_alias_parsing() {
173        let yaml_with_anchors = r"
174base: &base
175  name: test
176  value: 42
177
178prod: *base
179";
180
181        let mut parser = BasicParser::new_eager(yaml_with_anchors.to_string());
182
183        let mut events = Vec::new();
184        while parser.check_event() {
185            if let Ok(Some(event)) = parser.get_event() {
186                events.push(event);
187            } else {
188                break;
189            }
190        }
191
192        // Find the mapping with anchor (the anchor is on the mapping, not a scalar)
193        let base_mapping = events.iter().find(|e| {
194            if let EventType::MappingStart { anchor, .. } = &e.event_type {
195                anchor.as_ref().map_or(false, |a| a == "base")
196            } else {
197                false
198            }
199        });
200
201        assert!(
202            base_mapping.is_some(),
203            "Should find mapping with 'base' anchor"
204        );
205
206        // Find the alias event
207        let alias_event = events
208            .iter()
209            .find(|e| matches!(e.event_type, EventType::Alias { .. }));
210
211        assert!(alias_event.is_some(), "Should find alias event");
212
213        if let EventType::Alias { anchor } = &alias_event.unwrap().event_type {
214            assert_eq!(anchor, "base", "Alias should reference 'base'");
215        }
216    }
217
218    #[test]
219    fn test_literal_block_scalar() {
220        let yaml_literal = r"literal: |
221  This text contains
222  multiple lines
223  with preserved newlines
224";
225
226        let mut parser = BasicParser::new_eager(yaml_literal.to_string());
227
228        let mut events = Vec::new();
229        while parser.check_event() {
230            if let Ok(Some(event)) = parser.get_event() {
231                events.push(event);
232            } else {
233                break;
234            }
235        }
236
237        // Find the literal scalar
238        let literal_scalar = events.iter().find(|e| {
239            if let EventType::Scalar { value, style, .. } = &e.event_type {
240                *style == ScalarStyle::Literal && value.contains("This text contains")
241            } else {
242                false
243            }
244        });
245
246        assert!(literal_scalar.is_some(), "Should find literal scalar");
247
248        if let EventType::Scalar { value, .. } = &literal_scalar.unwrap().event_type {
249            assert!(
250                value.contains('\n'),
251                "Literal scalar should preserve newlines"
252            );
253            assert!(
254                value.contains("This text contains"),
255                "Should contain the literal text"
256            );
257        }
258    }
259
260    #[test]
261    fn test_folded_block_scalar() {
262        let yaml_folded = r"folded: >
263  This text will be
264  folded into a
265  single line
266";
267
268        let mut parser = BasicParser::new_eager(yaml_folded.to_string());
269
270        let mut events = Vec::new();
271        while parser.check_event() {
272            if let Ok(Some(event)) = parser.get_event() {
273                events.push(event);
274            } else {
275                break;
276            }
277        }
278
279        // Find the folded scalar
280        let folded_scalar = events.iter().find(|e| {
281            if let EventType::Scalar { value, style, .. } = &e.event_type {
282                *style == ScalarStyle::Folded && value.contains("This text will be")
283            } else {
284                false
285            }
286        });
287
288        assert!(folded_scalar.is_some(), "Should find folded scalar");
289
290        if let EventType::Scalar { value, .. } = &folded_scalar.unwrap().event_type {
291            // Folded scalars should have spaces instead of newlines
292            assert!(
293                !value.contains('\n'),
294                "Folded scalar should not preserve newlines"
295            );
296            assert!(
297                value.contains("This text will be folded into a single line"),
298                "Should fold the text"
299            );
300        }
301    }
302
303    #[test]
304    fn test_explicit_type_tags() {
305        let yaml_with_tags = r"
306string_value: !!str 42
307int_value: !!int '123'
308float_value: !!float '3.14'
309bool_value: !!bool 'yes'
310null_value: !!null 'something'
311";
312
313        let mut parser = BasicParser::new_eager(yaml_with_tags.to_string());
314
315        let mut events = Vec::new();
316        while parser.check_event() {
317            if let Ok(Some(event)) = parser.get_event() {
318                events.push(event);
319            } else {
320                break;
321            }
322        }
323
324        // Find scalars with tags
325        let tagged_scalars: Vec<_> = events
326            .iter()
327            .filter_map(|e| {
328                if let EventType::Scalar { value, tag, .. } = &e.event_type {
329                    Some((value.as_str(), tag.as_ref()?))
330                } else {
331                    None
332                }
333            })
334            .collect();
335
336        assert!(!tagged_scalars.is_empty(), "Should find tagged scalars");
337
338        // Verify specific tags are normalized
339        let str_scalar = tagged_scalars.iter().find(|(value, _)| *value == "42");
340        if let Some((_, tag)) = str_scalar {
341            assert_eq!(
342                *tag, "tag:yaml.org,2002:str",
343                "String tag should be normalized"
344            );
345        }
346
347        let int_scalar = tagged_scalars.iter().find(|(value, _)| *value == "123");
348        if let Some((_, tag)) = int_scalar {
349            assert_eq!(
350                *tag, "tag:yaml.org,2002:int",
351                "Int tag should be normalized"
352            );
353        }
354    }
355
356    #[test]
357    fn test_collection_type_tags() {
358        let yaml_with_collection_tags = r"
359explicit_sequence: !!seq [a, b, c]
360explicit_mapping: !!map {key: value}
361";
362
363        let mut parser = BasicParser::new_eager(yaml_with_collection_tags.to_string());
364
365        let mut events = Vec::new();
366        while parser.check_event() {
367            if let Ok(Some(event)) = parser.get_event() {
368                events.push(event);
369            } else {
370                break;
371            }
372        }
373
374        // Find collections with tags
375        let tagged_seq = events.iter().find(|e| {
376            if let EventType::SequenceStart { tag, .. } = &e.event_type {
377                tag.as_ref().map_or(false, |t| t == "tag:yaml.org,2002:seq")
378            } else {
379                false
380            }
381        });
382
383        let tagged_map = events.iter().find(|e| {
384            if let EventType::MappingStart { tag, .. } = &e.event_type {
385                tag.as_ref().map_or(false, |t| t == "tag:yaml.org,2002:map")
386            } else {
387                false
388            }
389        });
390
391        assert!(tagged_seq.is_some(), "Should find tagged sequence");
392        assert!(tagged_map.is_some(), "Should find tagged mapping");
393    }
394
395    #[test]
396    fn test_tag_scanner() {
397        let yaml_with_various_tags = "value: !!str hello\nother: !custom tag\nshort: !int 42";
398
399        let mut scanner = BasicScanner::new_eager(yaml_with_various_tags.to_string());
400
401        let mut tag_tokens = Vec::new();
402        while scanner.check_token() {
403            if let Ok(Some(token)) = scanner.get_token() {
404                if let TokenType::Tag(tag) = &token.token_type {
405                    tag_tokens.push(tag.clone());
406                }
407            } else {
408                break;
409            }
410        }
411
412        assert!(!tag_tokens.is_empty(), "Should find tag tokens");
413        assert!(
414            tag_tokens.iter().any(|t| t == "!!str"),
415            "Should find !!str tag"
416        );
417        assert!(
418            tag_tokens.iter().any(|t| t == "!custom"),
419            "Should preserve custom tags"
420        );
421        assert!(
422            tag_tokens.iter().any(|t| t == "!int"),
423            "Should find !int tag"
424        );
425    }
426
427    #[test]
428    fn test_streaming_parser() {
429        let yaml = r"
430items:
431  - name: first
432    value: 1
433  - name: second
434    value: 2
435";
436
437        // Test streaming (lazy) parser
438        let mut streaming_parser = BasicParser::new(yaml.to_string());
439        let mut stream_events = Vec::new();
440
441        // Events are generated on demand
442        while streaming_parser.check_event() {
443            if let Ok(Some(event)) = streaming_parser.get_event() {
444                stream_events.push(event);
445            } else {
446                break;
447            }
448        }
449
450        // Test eager parser for comparison
451        let mut eager_parser = BasicParser::new_eager(yaml.to_string());
452        let mut eager_events = Vec::new();
453
454        while eager_parser.check_event() {
455            if let Ok(Some(event)) = eager_parser.get_event() {
456                eager_events.push(event);
457            } else {
458                break;
459            }
460        }
461
462        // For now, just verify that streaming parser produces some meaningful events
463        // Full streaming optimization is a complex feature requiring more architecture work
464        let has_mapping_start = stream_events
465            .iter()
466            .any(|e| matches!(e.event_type, EventType::MappingStart { .. }));
467        let has_scalars = stream_events
468            .iter()
469            .any(|e| matches!(e.event_type, EventType::Scalar { .. }));
470
471        assert!(
472            stream_events.len() > 0,
473            "Streaming parser should generate events"
474        );
475        assert!(has_mapping_start, "Should have mapping start events");
476        assert!(has_scalars, "Should have scalar events");
477
478        // Verify eager parser works fully
479        let eager_has_sequence = eager_events
480            .iter()
481            .any(|e| matches!(e.event_type, EventType::SequenceStart { .. }));
482        assert!(
483            eager_has_sequence,
484            "Eager parser should have sequence start events"
485        );
486    }
487
488    #[test]
489    fn test_complex_yaml_document() {
490        let complex_yaml = r"
491# Configuration for a web service
492service:
493  name: my-web-service
494  version: &version '2.1.0'
495
496  # Server configuration
497  server:
498    host: localhost
499    port: 8080
500    ssl: true
501
502  # Database connections
503  databases:
504    primary: &primary_db
505      driver: postgresql
506      host: db.example.com
507      port: 5432
508      name: myapp_prod
509
510    cache:
511      driver: redis
512      host: cache.example.com
513      port: 6379
514
515  # Feature flags with explicit types
516  features:
517    new_ui: !!bool true
518    max_connections: !!int 100
519    timeout: !!float 30.5
520
521  # Deployment environments
522  environments:
523    - name: development
524      database: *primary_db
525      debug: true
526
527    - name: staging
528      database: *primary_db
529      debug: false
530
531    - name: production
532      database: *primary_db
533      debug: false
534
535  # Multi-line configurations
536  nginx_config: |
537    server {
538        listen 80;
539        server_name example.com;
540        location / {
541            proxy_pass http://localhost:8080;
542        }
543    }
544
545  description: >
546    This is a long description that will be
547    folded into a single line when parsed,
548    making it easier to read in the YAML file.
549";
550
551        let mut parser = BasicParser::new_eager(complex_yaml.to_string());
552        let mut events = Vec::new();
553
554        while parser.check_event() {
555            if let Ok(Some(event)) = parser.get_event() {
556                events.push(event);
557            } else {
558                break;
559            }
560        }
561
562        // Verify we parsed a complex document successfully
563        assert!(
564            events.len() > 20,
565            "Complex YAML should generate many events"
566        );
567
568        // Check for different types of events
569        let has_mapping_starts = events
570            .iter()
571            .filter(|e| matches!(e.event_type, EventType::MappingStart { .. }))
572            .count();
573        let has_sequence_starts = events
574            .iter()
575            .filter(|e| matches!(e.event_type, EventType::SequenceStart { .. }))
576            .count();
577        let has_scalars = events
578            .iter()
579            .filter(|e| matches!(e.event_type, EventType::Scalar { .. }))
580            .count();
581        let has_aliases = events
582            .iter()
583            .filter(|e| matches!(e.event_type, EventType::Alias { .. }))
584            .count();
585
586        assert!(
587            has_mapping_starts > 0,
588            "Should have mapping starts (found: {})",
589            has_mapping_starts
590        );
591        assert!(has_sequence_starts > 0, "Should have sequence starts");
592        assert!(has_scalars > 10, "Should have many scalars");
593        assert!(has_aliases > 0, "Should have aliases");
594
595        // Check for anchored values
596        let anchored_scalars = events
597            .iter()
598            .filter(|e| {
599                if let EventType::Scalar { anchor, .. } = &e.event_type {
600                    anchor.is_some()
601                } else {
602                    false
603                }
604            })
605            .count();
606        assert!(anchored_scalars > 0, "Should have anchored scalars");
607
608        // Check for tagged values
609        let tagged_scalars = events
610            .iter()
611            .filter(|e| {
612                if let EventType::Scalar { tag, .. } = &e.event_type {
613                    tag.is_some()
614                } else {
615                    false
616                }
617            })
618            .count();
619        assert!(tagged_scalars > 0, "Should have tagged scalars");
620
621        // Check for block scalar styles
622        let literal_scalars = events
623            .iter()
624            .filter(|e| {
625                if let EventType::Scalar { style, .. } = &e.event_type {
626                    matches!(style, parser::ScalarStyle::Literal)
627                } else {
628                    false
629                }
630            })
631            .count();
632
633        let folded_scalars = events
634            .iter()
635            .filter(|e| {
636                if let EventType::Scalar { style, .. } = &e.event_type {
637                    matches!(style, parser::ScalarStyle::Folded)
638                } else {
639                    false
640                }
641            })
642            .count();
643
644        assert!(literal_scalars > 0, "Should have literal block scalars");
645        assert!(folded_scalars > 0, "Should have folded block scalars");
646    }
647
648    #[test]
649    fn test_yaml_edge_cases() {
650        // Test various edge cases and special syntax
651        let edge_cases = vec![
652            // Empty document
653            ("", "empty document"),
654            // Document with only comments
655            ("# Just a comment\n# Another comment", "comment only"),
656            // Null values
657            ("key: ~\nother: null\nthird:", "null values"),
658            // Boolean variations
659            ("yes: true\nno: false\nmaybe: !!bool yes", "boolean values"),
660            // Number formats
661            (
662                "decimal: 123\noctal: 0o123\nhex: 0x123\nfloat: 1.23e4",
663                "number formats",
664            ),
665            // Empty collections
666            ("empty_list: []\nempty_dict: {}", "empty collections"),
667            // Nested structures
668            ("a: {b: {c: {d: value}}}", "deep nesting"),
669        ];
670
671        for (yaml_content, description) in edge_cases {
672            let mut parser = BasicParser::new_eager(yaml_content.to_string());
673            let mut events = Vec::new();
674
675            while parser.check_event() {
676                if let Ok(Some(event)) = parser.get_event() {
677                    events.push(event);
678                } else {
679                    break;
680                }
681            }
682
683            // Every YAML should at least have StreamStart and StreamEnd
684            assert!(
685                events.len() >= 2,
686                "Failed parsing {}: should have at least stream events",
687                description
688            );
689
690            let first_event = &events[0];
691            let last_event = &events[events.len() - 1];
692
693            assert!(
694                matches!(first_event.event_type, EventType::StreamStart),
695                "Failed {}: should start with StreamStart",
696                description
697            );
698            assert!(
699                matches!(last_event.event_type, EventType::StreamEnd),
700                "Failed {}: should end with StreamEnd",
701                description
702            );
703        }
704    }
705
706    #[test]
707    fn test_round_trip_scalars() {
708        let yaml = Yaml::new();
709
710        // Simplified test values for faster execution
711        let test_values = vec![
712            Value::Null,
713            Value::Bool(true),
714            Value::Bool(false),
715            Value::Int(42),
716            Value::String("hello".to_string()),
717        ];
718
719        for original in test_values {
720            // Only test if both serialize and parse succeed
721            if let Ok(yaml_str) = yaml.dump_str(&original) {
722                if let Ok(round_trip) = yaml.load_str(&yaml_str) {
723                    assert_eq!(
724                        original, round_trip,
725                        "Round-trip failed for {:?}. YAML: {}",
726                        original, yaml_str
727                    );
728                }
729                // If parsing fails, that's ok - some features may not be implemented
730            }
731            // If serialization fails, that's ok - some features may not be implemented
732        }
733    }
734
735    #[test]
736    fn test_round_trip_collections() {
737        let yaml = Yaml::new();
738
739        // Test sequences
740        let seq = Value::Sequence(vec![
741            Value::Int(1),
742            Value::String("hello".to_string()),
743            Value::Bool(true),
744        ]);
745
746        let yaml_str = yaml.dump_str(&seq).expect("Failed to serialize sequence");
747        let round_trip = yaml.load_str(&yaml_str).expect("Failed to parse sequence");
748        assert_eq!(
749            seq, round_trip,
750            "Sequence round-trip failed. YAML: {}",
751            yaml_str
752        );
753
754        // Test mappings
755        let mut map = indexmap::IndexMap::new();
756        map.insert(
757            Value::String("name".to_string()),
758            Value::String("Alice".to_string()),
759        );
760        map.insert(Value::String("age".to_string()), Value::Int(30));
761        map.insert(Value::String("active".to_string()), Value::Bool(true));
762        let mapping = Value::Mapping(map);
763
764        let yaml_str = yaml
765            .dump_str(&mapping)
766            .expect("Failed to serialize mapping");
767        let round_trip = yaml.load_str(&yaml_str).expect("Failed to parse mapping");
768        assert_eq!(
769            mapping, round_trip,
770            "Mapping round-trip failed. YAML: {}",
771            yaml_str
772        );
773    }
774
775    #[test]
776    fn test_round_trip_nested_structure() {
777        let yaml = Yaml::new();
778
779        // Create nested structure: mapping containing sequences and mappings
780        let mut inner_map = indexmap::IndexMap::new();
781        inner_map.insert(Value::String("x".to_string()), Value::Int(10));
782        inner_map.insert(Value::String("y".to_string()), Value::Int(20));
783
784        let seq = Value::Sequence(vec![
785            Value::String("first".to_string()),
786            Value::String("second".to_string()),
787            Value::Mapping(inner_map),
788        ]);
789
790        let mut outer_map = indexmap::IndexMap::new();
791        outer_map.insert(Value::String("items".to_string()), seq);
792        outer_map.insert(Value::String("count".to_string()), Value::Int(3));
793
794        let original = Value::Mapping(outer_map);
795
796        let yaml_str = yaml
797            .dump_str(&original)
798            .expect("Failed to serialize nested structure");
799        let round_trip = yaml
800            .load_str(&yaml_str)
801            .expect("Failed to parse nested structure");
802
803        assert_eq!(
804            original, round_trip,
805            "Nested structure round-trip failed. YAML: {}",
806            yaml_str
807        );
808    }
809
810    #[test]
811    fn test_round_trip_with_special_strings() {
812        let yaml = Yaml::new();
813
814        let special_strings = vec![
815            "null",       // Should be quoted
816            "true",       // Should be quoted
817            "false",      // Should be quoted
818            "123",        // Should be quoted
819            "3.14",       // Should be quoted
820            "yes",        // Should be quoted
821            "no",         // Should be quoted
822            "on",         // Should be quoted
823            "off",        // Should be quoted
824            "",           // Empty string, should be quoted
825            "  spaced  ", // String with spaces, should be quoted
826        ];
827
828        for s in special_strings {
829            let original = Value::String(s.to_string());
830            let yaml_str = yaml
831                .dump_str(&original)
832                .expect("Failed to serialize special string");
833            let round_trip = yaml
834                .load_str(&yaml_str)
835                .expect("Failed to parse special string");
836
837            assert_eq!(
838                original, round_trip,
839                "Special string round-trip failed for '{}'. YAML: {}",
840                s, yaml_str
841            );
842        }
843    }
844
845    #[test]
846    fn test_round_trip_complex_yaml() {
847        let yaml = Yaml::new();
848
849        // Test with the complex YAML from our integration test
850        let complex_yaml = r"
851service:
852  name: my-web-service
853  version: '2.1.0'
854  server:
855    host: localhost
856    port: 8080
857    ssl: true
858  features:
859    new_ui: true
860    max_connections: 100
861    timeout: 30.5
862";
863
864        // Parse the original
865        let parsed = yaml
866            .load_str(complex_yaml)
867            .expect("Failed to parse complex YAML");
868
869        // Serialize it
870        let serialized = yaml
871            .dump_str(&parsed)
872            .expect("Failed to serialize complex structure");
873
874        // Parse the serialized version
875        let round_trip = yaml
876            .load_str(&serialized)
877            .expect("Failed to parse round-trip");
878
879        // Should be the same
880        assert_eq!(parsed, round_trip, "Complex YAML round-trip failed");
881    }
882
883    #[test]
884    fn test_anchor_alias_serialization() {
885        let yaml = Yaml::new();
886
887        // Create a structure with shared values that should generate anchors/aliases
888        let shared_mapping = {
889            let mut map = indexmap::IndexMap::new();
890            map.insert(
891                Value::String("name".to_string()),
892                Value::String("shared".to_string()),
893            );
894            map.insert(Value::String("value".to_string()), Value::Int(42));
895            Value::Mapping(map)
896        };
897
898        // Create a root structure that references the shared mapping multiple times
899        let mut root_map = indexmap::IndexMap::new();
900        root_map.insert(Value::String("first".to_string()), shared_mapping.clone());
901        root_map.insert(Value::String("second".to_string()), shared_mapping.clone());
902        root_map.insert(Value::String("third".to_string()), shared_mapping);
903
904        let root = Value::Mapping(root_map);
905
906        // Serialize - should generate anchors/aliases for shared values
907        let serialized = yaml
908            .dump_str(&root)
909            .expect("Failed to serialize shared structure");
910
911        println!("Serialized with anchors/aliases:");
912        println!("{}", serialized);
913
914        // Check that anchors and aliases are generated
915        assert!(
916            serialized.contains("&anchor"),
917            "Should contain anchor definition"
918        );
919        assert!(
920            serialized.contains("*anchor"),
921            "Should contain alias reference"
922        );
923
924        // Verify the structure is correct
925        assert!(
926            serialized.contains("first:") && serialized.contains("&anchor0"),
927            "Should have anchored first mapping"
928        );
929        assert!(
930            serialized.contains("second:") && serialized.contains("*anchor0"),
931            "Should have aliased second mapping"
932        );
933        assert!(
934            serialized.contains("third:") && serialized.contains("*anchor0"),
935            "Should have aliased third mapping"
936        );
937        assert!(
938            serialized.contains("name: shared"),
939            "Should contain shared content"
940        );
941        assert!(
942            serialized.contains("value: 42"),
943            "Should contain shared value"
944        );
945    }
946
947    #[test]
948    fn test_anchor_alias_with_sequences() {
949        let yaml = Yaml::new();
950
951        // Create a shared sequence
952        let shared_sequence = Value::Sequence(vec![
953            Value::String("item1".to_string()),
954            Value::String("item2".to_string()),
955            Value::Int(123),
956        ]);
957
958        // Create a structure that reuses the sequence
959        let mut root_map = indexmap::IndexMap::new();
960        root_map.insert(Value::String("list1".to_string()), shared_sequence.clone());
961        root_map.insert(Value::String("list2".to_string()), shared_sequence);
962
963        let root = Value::Mapping(root_map);
964
965        // Serialize
966        let serialized = yaml
967            .dump_str(&root)
968            .expect("Failed to serialize shared sequences");
969
970        println!("Serialized sequences with anchors/aliases:");
971        println!("{}", serialized);
972
973        // Should contain anchor/alias for sequences
974        assert!(
975            serialized.contains("&anchor"),
976            "Should contain anchor for shared sequence"
977        );
978        assert!(
979            serialized.contains("*anchor"),
980            "Should contain alias for shared sequence"
981        );
982
983        // Verify the structure
984        assert!(
985            serialized.contains("list1:") && serialized.contains("&anchor0"),
986            "Should have anchored sequence"
987        );
988        assert!(
989            serialized.contains("list2:") && serialized.contains("*anchor0"),
990            "Should have aliased sequence"
991        );
992        assert!(
993            serialized.contains("- item1"),
994            "Should contain sequence items"
995        );
996        assert!(
997            serialized.contains("- 123"),
998            "Should contain sequence values"
999        );
1000    }
1001}