1use crate::builder::MappingBuilder;
29use crate::yaml::Mapping;
30
31pub trait YamlPath {
41 fn get_path(&self, path: &str) -> Option<crate::as_yaml::YamlNode>;
62
63 fn set_path(&self, path: &str, value: impl crate::AsYaml);
81
82 fn remove_path(&self, path: &str) -> bool;
103}
104
105#[derive(Debug, Clone, PartialEq)]
107pub enum PathSegment {
108 Key(String),
110 Index(usize),
112}
113
114pub fn parse_path(path: &str) -> Vec<PathSegment> {
147 if path.is_empty() {
148 return vec![];
149 }
150
151 let mut segments = Vec::new();
152 let mut current = String::new();
153 let mut chars = path.chars().peekable();
154 let mut escaped = false;
155
156 while let Some(ch) = chars.next() {
157 if escaped {
158 current.push(ch);
160 escaped = false;
161 continue;
162 }
163
164 match ch {
165 '\\' => {
166 escaped = true;
168 }
169 '.' => {
170 if !current.is_empty() {
172 if let Ok(index) = current.parse::<usize>() {
174 segments.push(PathSegment::Index(index));
175 } else {
176 segments.push(PathSegment::Key(current.clone()));
177 }
178 current.clear();
179 }
180 }
181 '[' => {
182 if !current.is_empty() {
184 segments.push(PathSegment::Key(current.clone()));
185 current.clear();
186 }
187
188 let mut index_str = String::new();
190 while let Some(&next_ch) = chars.peek() {
191 if next_ch == ']' {
192 chars.next(); break;
194 }
195 index_str.push(chars.next().unwrap());
196 }
197
198 if let Ok(index) = index_str.parse::<usize>() {
200 segments.push(PathSegment::Index(index));
201 }
202 }
203 _ => {
204 current.push(ch);
205 }
206 }
207 }
208
209 if !current.is_empty() {
211 if let Ok(index) = current.parse::<usize>() {
212 segments.push(PathSegment::Index(index));
213 } else {
214 segments.push(PathSegment::Key(current));
215 }
216 }
217
218 segments
219}
220
221fn navigate_path(
225 mut current: crate::as_yaml::YamlNode,
226 segments: &[PathSegment],
227) -> Option<crate::as_yaml::YamlNode> {
228 for segment in segments {
229 match segment {
230 PathSegment::Key(key) => {
231 let mapping = current.as_mapping()?;
233 current = mapping.get(key)?;
234 }
235 PathSegment::Index(index) => {
236 let sequence = current.as_sequence()?;
238 current = sequence.get(*index)?;
239 }
240 }
241 }
242
243 Some(current)
244}
245
246impl YamlPath for crate::yaml::Document {
248 fn get_path(&self, path: &str) -> Option<crate::as_yaml::YamlNode> {
249 let segments = parse_path(path);
250 if segments.is_empty() {
251 return None;
252 }
253
254 let root = if let Some(m) = self.as_mapping() {
256 crate::as_yaml::YamlNode::Mapping(m)
257 } else if let Some(s) = self.as_sequence() {
258 crate::as_yaml::YamlNode::Sequence(s)
259 } else {
260 let sc = self.as_scalar()?;
261 crate::as_yaml::YamlNode::Scalar(sc)
262 };
263
264 navigate_path(root, &segments)
266 }
267
268 fn set_path(&self, path: &str, value: impl crate::AsYaml) {
269 let segments = parse_path(path);
270 if segments.is_empty() {
271 return;
272 }
273
274 let mapping = match self.as_mapping() {
276 Some(m) => m,
277 None => return,
278 };
279
280 set_path_impl(&mapping, &segments, value);
281 }
282
283 fn remove_path(&self, path: &str) -> bool {
284 let segments = parse_path(path);
285 if segments.is_empty() {
286 return false;
287 }
288
289 let root = if let Some(m) = self.as_mapping() {
291 crate::as_yaml::YamlNode::Mapping(m)
292 } else if let Some(s) = self.as_sequence() {
293 crate::as_yaml::YamlNode::Sequence(s)
294 } else {
295 return false;
296 };
297
298 remove_path_impl(root, &segments)
299 }
300}
301
302fn set_path_impl<V: crate::AsYaml>(mapping: &Mapping, segments: &[PathSegment], value: V) {
306 set_path_on_mapping(mapping, segments, value);
307}
308
309fn remove_path_impl(root: crate::as_yaml::YamlNode, segments: &[PathSegment]) -> bool {
313 if segments.is_empty() {
314 return false;
315 }
316
317 if segments.len() == 1 {
318 match &segments[0] {
320 PathSegment::Key(key) => {
321 if let Some(mapping) = root.as_mapping() {
322 return mapping.remove(key.as_str()).is_some();
323 }
324 }
325 PathSegment::Index(_) => {
326 return false;
329 }
330 }
331 return false;
332 }
333
334 match &segments[0] {
336 PathSegment::Key(key) => {
337 if let Some(mapping) = root.as_mapping() {
338 if let Some(nested) = mapping.get(key.as_str()) {
339 return remove_path_impl(nested, &segments[1..]);
340 }
341 }
342 }
343 PathSegment::Index(index) => {
344 if let Some(sequence) = root.as_sequence() {
345 if let Some(nested) = sequence.get(*index) {
346 return remove_path_impl(nested, &segments[1..]);
347 }
348 }
349 }
350 }
351
352 false
353}
354
355impl YamlPath for Mapping {
357 fn get_path(&self, path: &str) -> Option<crate::as_yaml::YamlNode> {
358 let segments = parse_path(path);
359 if segments.is_empty() {
360 return None;
361 }
362
363 let first_key = match &segments[0] {
365 PathSegment::Key(key) => key.as_str(),
366 PathSegment::Index(_) => return None, };
368
369 if segments.len() == 1 {
370 return self.get(first_key);
371 }
372
373 let current = self.get(first_key)?;
375 navigate_path(current, &segments[1..])
376 }
377
378 fn set_path(&self, path: &str, value: impl crate::AsYaml) {
379 let segments = parse_path(path);
380 if segments.is_empty() {
381 return;
382 }
383
384 set_path_on_mapping(self, &segments, value);
385 }
386
387 fn remove_path(&self, path: &str) -> bool {
388 let segments = parse_path(path);
389 if segments.is_empty() {
390 return false;
391 }
392
393 remove_path_from_mapping(self, &segments)
394 }
395}
396
397fn set_path_on_mapping<V: crate::AsYaml>(mapping: &Mapping, segments: &[PathSegment], value: V) {
401 if segments.is_empty() {
402 return;
403 }
404
405 let first_key = match &segments[0] {
407 PathSegment::Key(key) => key.as_str(),
408 PathSegment::Index(_) => return, };
410
411 if segments.len() == 1 {
412 mapping.set(first_key, value);
414 return;
415 }
416
417 if let Some(nested) = mapping.get_mapping(first_key) {
419 set_path_on_mapping(&nested, &segments[1..], value);
421 } else {
422 let empty_mapping = MappingBuilder::new()
424 .build_document()
425 .as_mapping()
426 .expect("MappingBuilder always produces a mapping");
427 mapping.set(first_key, &empty_mapping);
428
429 if let Some(nested) = mapping.get_mapping(first_key) {
431 set_path_on_mapping(&nested, &segments[1..], value);
432 }
433 }
434}
435
436fn remove_path_from_mapping(mapping: &Mapping, segments: &[PathSegment]) -> bool {
440 if segments.is_empty() {
441 return false;
442 }
443
444 let first_key = match &segments[0] {
446 PathSegment::Key(key) => key.as_str(),
447 PathSegment::Index(_) => return false, };
449
450 if segments.len() == 1 {
451 return mapping.remove(first_key).is_some();
453 }
454
455 if let Some(nested) = mapping.get_mapping(first_key) {
457 remove_path_from_mapping(&nested, &segments[1..])
458 } else {
459 false }
461}
462
463#[cfg(test)]
464mod tests {
465 use super::*;
466
467 #[test]
468 fn test_parse_path_basic() {
469 assert_eq!(parse_path(""), Vec::<PathSegment>::new());
470 assert_eq!(parse_path("key"), vec![PathSegment::Key("key".to_string())]);
471 assert_eq!(
472 parse_path("a.b"),
473 vec![
474 PathSegment::Key("a".to_string()),
475 PathSegment::Key("b".to_string())
476 ]
477 );
478 assert_eq!(
479 parse_path("a.b.c.d"),
480 vec![
481 PathSegment::Key("a".to_string()),
482 PathSegment::Key("b".to_string()),
483 PathSegment::Key("c".to_string()),
484 PathSegment::Key("d".to_string())
485 ]
486 );
487 }
488
489 #[test]
490 fn test_parse_path_with_array_indices() {
491 assert_eq!(
492 parse_path("items[0]"),
493 vec![PathSegment::Key("items".to_string()), PathSegment::Index(0)]
494 );
495 assert_eq!(
496 parse_path("items[0].name"),
497 vec![
498 PathSegment::Key("items".to_string()),
499 PathSegment::Index(0),
500 PathSegment::Key("name".to_string())
501 ]
502 );
503 assert_eq!(
504 parse_path("data.items[5].value"),
505 vec![
506 PathSegment::Key("data".to_string()),
507 PathSegment::Key("items".to_string()),
508 PathSegment::Index(5),
509 PathSegment::Key("value".to_string())
510 ]
511 );
512 }
513
514 #[test]
515 fn test_parse_path_with_numeric_indices() {
516 assert_eq!(
517 parse_path("items.0"),
518 vec![PathSegment::Key("items".to_string()), PathSegment::Index(0)]
519 );
520 assert_eq!(
521 parse_path("items.0.name"),
522 vec![
523 PathSegment::Key("items".to_string()),
524 PathSegment::Index(0),
525 PathSegment::Key("name".to_string())
526 ]
527 );
528 }
529
530 #[test]
531 fn test_parse_path_with_escaping() {
532 assert_eq!(
533 parse_path("key\\.with\\.dots"),
534 vec![PathSegment::Key("key.with.dots".to_string())]
535 );
536 assert_eq!(
537 parse_path("a.key\\.with\\.dots.b"),
538 vec![
539 PathSegment::Key("a".to_string()),
540 PathSegment::Key("key.with.dots".to_string()),
541 PathSegment::Key("b".to_string())
542 ]
543 );
544 }
545
546 #[test]
547 fn test_get_path_with_array_index() {
548 use crate::yaml::Document;
549 use std::str::FromStr;
550
551 let yaml = r#"
552items:
553 - name: first
554 value: 1
555 - name: second
556 value: 2
557"#;
558 let doc = Document::from_str(yaml).unwrap();
559
560 let name = doc.get_path("items[0].name");
562 assert_eq!(
563 name.as_ref()
564 .and_then(|v| v.as_scalar())
565 .map(|s| s.as_string()),
566 Some("first".to_string())
567 );
568
569 let value = doc.get_path("items[1].value");
570 assert_eq!(
571 value
572 .as_ref()
573 .and_then(|v| v.as_scalar())
574 .map(|s| s.as_string()),
575 Some("2".to_string())
576 );
577 }
578
579 #[test]
580 fn test_get_path_with_numeric_index() {
581 use crate::yaml::Document;
582 use std::str::FromStr;
583
584 let yaml = r#"
585items:
586 - name: first
587 value: 1
588 - name: second
589 value: 2
590"#;
591 let doc = Document::from_str(yaml).unwrap();
592
593 let name = doc.get_path("items.0.name");
595 assert_eq!(
596 name.as_ref()
597 .and_then(|v| v.as_scalar())
598 .map(|s| s.as_string()),
599 Some("first".to_string())
600 );
601
602 let value = doc.get_path("items.1.value");
603 assert_eq!(
604 value
605 .as_ref()
606 .and_then(|v| v.as_scalar())
607 .map(|s| s.as_string()),
608 Some("2".to_string())
609 );
610 }
611
612 #[test]
613 fn test_get_path_with_escaping() {
614 use crate::yaml::Document;
615
616 let doc = Document::new();
617 doc.set("key.with.dots", "test value");
618
619 assert!(doc.get_path("key.with.dots").is_none());
621
622 let value = doc.get_path("key\\.with\\.dots");
624 assert_eq!(
625 value
626 .as_ref()
627 .and_then(|v| v.as_scalar())
628 .map(|s| s.as_string()),
629 Some("test value".to_string())
630 );
631 }
632
633 #[test]
634 fn test_get_path_array_only() {
635 use crate::yaml::Document;
636 use std::str::FromStr;
637
638 let yaml = r#"
639- first
640- second
641- third
642"#;
643 let doc = Document::from_str(yaml).unwrap();
644
645 let item = doc.get_path("0");
647 assert_eq!(
648 item.as_ref()
649 .and_then(|v| v.as_scalar())
650 .map(|s| s.as_string()),
651 Some("first".to_string())
652 );
653
654 let item = doc.get_path("2");
655 assert_eq!(
656 item.as_ref()
657 .and_then(|v| v.as_scalar())
658 .map(|s| s.as_string()),
659 Some("third".to_string())
660 );
661 }
662
663 #[test]
664 fn test_remove_path_with_array_index() {
665 use crate::yaml::Document;
666 use std::str::FromStr;
667
668 let yaml = r#"
669items:
670 - name: first
671 nested:
672 key: value
673"#;
674 let doc = Document::from_str(yaml).unwrap();
675
676 assert!(doc.remove_path("items[0].nested.key"));
678 assert!(doc.get_path("items[0].nested.key").is_none());
679
680 assert!(doc.get_path("items[0].nested").is_some());
682 }
683
684 #[test]
685 fn test_mapping_get_path_with_indices() {
686 use crate::yaml::Document;
687 use std::str::FromStr;
688
689 let yaml = r#"
690config:
691 servers:
692 - host: server1.com
693 port: 8080
694 - host: server2.com
695 port: 9090
696"#;
697 let doc = Document::from_str(yaml).unwrap();
698 let mapping = doc.as_mapping().unwrap();
699
700 let host = mapping.get_path("config.servers[0].host");
702 assert_eq!(
703 host.as_ref()
704 .and_then(|v| v.as_scalar())
705 .map(|s| s.as_string()),
706 Some("server1.com".to_string())
707 );
708
709 let port = mapping.get_path("config.servers.1.port");
710 assert_eq!(
711 port.as_ref()
712 .and_then(|v| v.as_scalar())
713 .map(|s| s.as_string()),
714 Some("9090".to_string())
715 );
716 }
717
718 #[test]
719 fn test_get_path_simple() {
720 use crate::yaml::Document;
721 use std::str::FromStr;
722
723 let yaml = Document::from_str("name: Alice\nage: 30\n").unwrap();
724
725 let name = yaml.get_path("name");
726 assert_eq!(
727 name.as_ref()
728 .and_then(|v| v.as_scalar())
729 .map(|s| s.to_string()),
730 Some("Alice".to_string())
731 );
732
733 let age = yaml.get_path("age");
734 assert_eq!(
735 age.as_ref()
736 .and_then(|v| v.as_scalar())
737 .map(|s| s.to_string()),
738 Some("30".to_string())
739 );
740 }
741
742 #[test]
743 fn test_get_path_nested() {
744 use crate::yaml::Document;
745 use std::str::FromStr;
746
747 let yaml = Document::from_str("server:\n host: localhost\n port: 8080\n").unwrap();
748
749 let host = yaml.get_path("server.host");
750 assert_eq!(
751 host.as_ref()
752 .and_then(|v| v.as_scalar())
753 .map(|s| s.to_string()),
754 Some("localhost".to_string())
755 );
756
757 let port = yaml.get_path("server.port");
758 assert_eq!(
759 port.as_ref()
760 .and_then(|v| v.as_scalar())
761 .map(|s| s.to_string()),
762 Some("8080".to_string())
763 );
764 }
765
766 #[test]
767 fn test_get_path_deeply_nested() {
768 use crate::yaml::Document;
769 use std::str::FromStr;
770
771 let yaml = Document::from_str(
772 "app:\n database:\n primary:\n host: db.example.com\n port: 5432\n",
773 )
774 .unwrap();
775
776 let host = yaml.get_path("app.database.primary.host");
777 assert_eq!(
778 host.as_ref()
779 .and_then(|v| v.as_scalar())
780 .map(|s| s.to_string()),
781 Some("db.example.com".to_string())
782 );
783
784 let port = yaml.get_path("app.database.primary.port");
785 assert_eq!(
786 port.as_ref()
787 .and_then(|v| v.as_scalar())
788 .map(|s| s.to_string()),
789 Some("5432".to_string())
790 );
791 }
792
793 #[test]
794 fn test_get_path_missing() {
795 use crate::yaml::Document;
796 use std::str::FromStr;
797
798 let yaml = Document::from_str("name: Alice\n").unwrap();
799
800 assert_eq!(yaml.get_path("missing"), None);
801 assert_eq!(yaml.get_path("name.nested"), None);
802 assert_eq!(yaml.get_path(""), None);
803 }
804
805 #[test]
806 fn test_set_path_existing_key() {
807 use crate::yaml::Document;
808 use std::str::FromStr;
809
810 let yaml = Document::from_str("name: Alice\nage: 30\n").unwrap();
811
812 yaml.set_path("name", "Bob");
813
814 assert_eq!(yaml.to_string(), "name: Bob\nage: 30\n");
815 }
816
817 #[test]
818 fn test_set_path_new_key() {
819 use crate::yaml::Document;
820 use std::str::FromStr;
821
822 let yaml = Document::from_str("name: Alice\n").unwrap();
823
824 yaml.set_path("age", 30);
825
826 assert_eq!(yaml.to_string(), "name: Alice\nage: 30\n");
827 }
828
829 #[test]
830 fn test_set_path_nested_existing() {
831 use crate::yaml::Document;
832 use std::str::FromStr;
833
834 let yaml = Document::from_str("server:\n host: localhost\n port: 8080\n").unwrap();
835
836 yaml.set_path("server.port", 9000);
837
838 assert_eq!(
839 yaml.to_string(),
840 "server:\n host: localhost\n port: 9000\n"
841 );
842 }
843
844 #[test]
845 fn test_set_path_nested_new() {
846 use crate::yaml::Document;
847 use std::str::FromStr;
848
849 let yaml = Document::from_str("server:\n host: localhost\n").unwrap();
850
851 yaml.set_path("server.port", 8080);
852
853 assert_eq!(
854 yaml.to_string(),
855 "server:\n host: localhost\n port: 8080\n"
856 );
857 }
858
859 #[test]
860 fn test_set_path_create_intermediate() {
861 use crate::yaml::Document;
862 use std::str::FromStr;
863
864 let yaml = Document::from_str("name: test\n").unwrap();
865
866 yaml.set_path("server.database.host", "localhost");
867
868 assert_eq!(
869 yaml.to_string(),
870 "name: test\nserver:\n database:\n host: localhost\n"
871 );
872
873 let host = yaml.get_path("server.database.host");
875 assert_eq!(
876 host.as_ref()
877 .and_then(|v| v.as_scalar())
878 .map(|s| s.to_string()),
879 Some("localhost".to_string())
880 );
881 }
882
883 #[test]
884 fn test_set_path_deeply_nested_create() {
885 use crate::yaml::Document;
886 use std::str::FromStr;
887
888 let yaml = Document::from_str("app: {}\n").unwrap();
889
890 yaml.set_path("app.database.primary.host", "db.example.com");
891 yaml.set_path("app.database.primary.port", 5432);
892
893 let host = yaml.get_path("app.database.primary.host");
894 assert_eq!(
895 host.as_ref()
896 .and_then(|v| v.as_scalar())
897 .map(|s| s.to_string()),
898 Some("db.example.com".to_string())
899 );
900
901 let port = yaml.get_path("app.database.primary.port");
902 assert_eq!(
903 port.as_ref()
904 .and_then(|v| v.as_scalar())
905 .map(|s| s.to_string()),
906 Some("5432".to_string())
907 );
908 }
909
910 #[test]
911 fn test_remove_path_simple() {
912 use crate::yaml::Document;
913 use std::str::FromStr;
914
915 let yaml = Document::from_str("name: Alice\nage: 30\n").unwrap();
916
917 let result = yaml.remove_path("age");
918 assert!(result);
919
920 assert_eq!(yaml.to_string(), "name: Alice");
921 }
922
923 #[test]
924 fn test_remove_path_nested() {
925 use crate::yaml::Document;
926 use std::str::FromStr;
927
928 let yaml = Document::from_str("server:\n host: localhost\n port: 8080\n").unwrap();
929
930 let result = yaml.remove_path("server.port");
931 assert!(result);
932
933 assert_eq!(yaml.to_string(), "server:\n host: localhost ");
934 }
935
936 #[test]
937 fn test_remove_path_missing() {
938 use crate::yaml::Document;
939 use std::str::FromStr;
940
941 let yaml = Document::from_str("name: Alice\n").unwrap();
942
943 let result = yaml.remove_path("missing");
944 assert!(!result);
945
946 let result = yaml.remove_path("name.nested");
947 assert!(!result);
948
949 assert_eq!(yaml.to_string(), "name: Alice\n");
951 }
952
953 #[test]
954 fn test_remove_path_deeply_nested() {
955 use crate::yaml::Document;
956 use std::str::FromStr;
957
958 let yaml = Document::from_str(
959 "app:\n database:\n primary:\n host: db.example.com\n port: 5432\n",
960 )
961 .unwrap();
962
963 let result = yaml.remove_path("app.database.primary.port");
964 assert!(result);
965
966 assert_eq!(
967 yaml.to_string(),
968 "app:\n database:\n primary:\n host: db.example.com "
969 );
970 }
971
972 #[test]
973 fn test_path_on_mapping_directly() {
974 use crate::yaml::Document;
975 use std::str::FromStr;
976
977 let yaml = Document::from_str("server:\n host: localhost\n").unwrap();
978 let mapping = yaml.as_mapping().unwrap();
979
980 let host = mapping.get_path("server.host");
982 assert_eq!(
983 host.as_ref()
984 .and_then(|v| v.as_scalar())
985 .map(|s| s.to_string()),
986 Some("localhost".to_string())
987 );
988
989 mapping.set_path("server.port", 8080);
991 assert_eq!(
992 yaml.to_string(),
993 "server:\n host: localhost\n port: 8080\n"
994 );
995
996 let result = mapping.remove_path("server.port");
998 assert!(result);
999
1000 let result_missing = mapping.remove_path("nonexistent.path");
1002 assert!(!result_missing);
1003 }
1004
1005 #[test]
1006 fn test_mapping_remove_path_single_segment() {
1007 use crate::yaml::Document;
1008 use std::str::FromStr;
1009
1010 let yaml = Document::from_str("a: 1\nb: 2\n").unwrap();
1011 let mapping = yaml.as_mapping().unwrap();
1012
1013 assert!(mapping.remove_path("a"));
1015 assert!(mapping.get_path("a").is_none());
1016 assert!(mapping.get_path("b").is_some());
1017
1018 assert!(!mapping.remove_path("missing"));
1020 }
1021
1022 #[test]
1023 fn test_set_path_preserves_formatting() {
1024 use crate::yaml::Document;
1025 use std::str::FromStr;
1026
1027 let yaml = Document::from_str("server:\n host: localhost # production server\n").unwrap();
1028
1029 yaml.set_path("server.host", "newhost");
1030
1031 assert_eq!(
1032 yaml.to_string(),
1033 "server:\n host: newhost # production server\n"
1034 );
1035 }
1036
1037 #[test]
1038 fn test_multiple_path_operations() {
1039 use crate::yaml::Document;
1040 use std::str::FromStr;
1041
1042 let yaml = Document::from_str("name: test\n").unwrap();
1043
1044 yaml.set_path("server.host", "localhost");
1046 yaml.set_path("server.port", 8080);
1047 yaml.set_path("database.host", "db.local");
1048 yaml.set_path("database.port", 5432);
1049
1050 assert_eq!(
1052 yaml.get_path("server.host")
1053 .as_ref()
1054 .and_then(|v| v.as_scalar())
1055 .map(|s| s.to_string()),
1056 Some("localhost".to_string())
1057 );
1058 assert_eq!(
1059 yaml.get_path("server.port")
1060 .as_ref()
1061 .and_then(|v| v.as_scalar())
1062 .map(|s| s.to_string()),
1063 Some("8080".to_string())
1064 );
1065 assert_eq!(
1066 yaml.get_path("database.host")
1067 .as_ref()
1068 .and_then(|v| v.as_scalar())
1069 .map(|s| s.to_string()),
1070 Some("db.local".to_string())
1071 );
1072 assert_eq!(
1073 yaml.get_path("database.port")
1074 .as_ref()
1075 .and_then(|v| v.as_scalar())
1076 .map(|s| s.to_string()),
1077 Some("5432".to_string())
1078 );
1079
1080 yaml.remove_path("server.port");
1082 yaml.remove_path("database.host");
1083
1084 assert_eq!(yaml.get_path("server.port"), None);
1086 assert_eq!(yaml.get_path("database.host"), None);
1087
1088 assert_eq!(
1090 yaml.get_path("server.host")
1091 .as_ref()
1092 .and_then(|v| v.as_scalar())
1093 .map(|s| s.to_string()),
1094 Some("localhost".to_string())
1095 );
1096 assert_eq!(
1097 yaml.get_path("database.port")
1098 .as_ref()
1099 .and_then(|v| v.as_scalar())
1100 .map(|s| s.to_string()),
1101 Some("5432".to_string())
1102 );
1103 }
1104}