1use indexmap::IndexMap;
6use itertools::Itertools;
7
8use crate::types::capability::ContextCapability;
9use crate::types::child::ContextChild;
10use crate::types::collection::ContextCollection;
11use crate::types::common::*;
12use crate::types::environment::ContextEnvironment;
13use crate::types::expose::ContextExpose;
14use crate::types::offer::ContextOffer;
15use crate::types::program::ContextProgram;
16use crate::types::r#use::ContextUse;
17use crate::{
18 CanonicalizeContext, Capability, CapabilityFromRef, Child, Collection, ConfigKey,
19 ConfigValueType, Environment, Error, Expose, Location, Offer, Program, Use, merge_spanned_vec,
20};
21
22pub use cm_types::{
23 Availability, BorrowedName, BoundedName, DeliveryType, DependencyType, HandleType, Name,
24 OnTerminate, ParseError, Path, RelativePath, StartupMode, StorageId, Url,
25};
26use reference_doc::ReferenceDoc;
27use serde::{Deserialize, Serialize};
28use serde_json::{Map, Value};
29
30use std::collections::{BTreeMap, HashMap, HashSet};
31use std::sync::Arc;
32use std::{cmp, path};
33
34#[derive(ReferenceDoc, Deserialize, Debug, Default, PartialEq, Serialize)]
82#[serde(deny_unknown_fields)]
83pub struct Document {
84 #[serde(skip_serializing_if = "Option::is_none")]
218 pub include: Option<Vec<String>>,
219
220 #[reference_doc(json_type = "object")]
255 #[serde(skip_serializing_if = "Option::is_none")]
256 pub program: Option<Program>,
257
258 #[reference_doc(recurse)]
263 #[serde(skip_serializing_if = "Option::is_none")]
264 pub children: Option<Vec<Child>>,
265
266 #[reference_doc(recurse)]
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub collections: Option<Vec<Collection>>,
271
272 #[reference_doc(recurse)]
277 #[serde(skip_serializing_if = "Option::is_none")]
278 pub environments: Option<Vec<Environment>>,
279
280 #[reference_doc(recurse)]
303 #[serde(skip_serializing_if = "Option::is_none")]
304 pub capabilities: Option<Vec<Capability>>,
305
306 #[reference_doc(recurse)]
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub r#use: Option<Vec<Use>>,
332
333 #[reference_doc(recurse)]
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub expose: Option<Vec<Expose>>,
354
355 #[reference_doc(recurse)]
376 #[serde(skip_serializing_if = "Option::is_none")]
377 pub offer: Option<Vec<Offer>>,
378
379 #[serde(skip_serializing_if = "Option::is_none")]
383 pub facets: Option<IndexMap<String, Value>>,
384
385 #[reference_doc(json_type = "object")]
452 #[serde(skip_serializing_if = "Option::is_none")]
453 pub config: Option<BTreeMap<ConfigKey, ConfigValueType>>,
458}
459
460fn merge_from_context_capability_field<T: ContextCapabilityClause>(
461 us: &mut Option<Vec<T>>,
462 other: &mut Option<Vec<T>>,
463) -> Result<(), Error> {
464 for entry in us.iter().flatten().chain(other.iter().flatten()) {
467 if entry.names().is_empty() {
468 return Err(Error::Validate {
469 err: format!("{}: Missing type name: {:#?}", entry.decl_type(), entry),
471 filename: None,
472 });
473 }
474 }
475
476 if let Some(all_ours) = us.as_mut() {
477 if let Some(all_theirs) = other.take() {
478 for mut theirs in all_theirs {
479 for ours in &mut *all_ours {
480 compute_diff_context(ours, &mut theirs);
481 }
482 all_ours.push(theirs);
483 }
484 }
485 all_ours.retain(|ours| !ours.names().is_empty())
487 } else if let Some(theirs) = other.take() {
488 us.replace(theirs);
489 }
490 Ok(())
491}
492
493fn compute_diff_context<T: ContextCapabilityClause>(ours: &mut T, theirs: &mut T) {
500 let our_spanned = ours.names();
501 let their_spanned = theirs.names();
502
503 if our_spanned.is_empty() || their_spanned.is_empty() {
504 return;
505 }
506
507 if ours.capability_type(None).unwrap() != theirs.capability_type(None).unwrap() {
508 return;
509 }
510
511 let mut ours_check = ours.clone();
512 let mut theirs_check = theirs.clone();
513
514 ours_check.set_names(Vec::new());
515 theirs_check.set_names(Vec::new());
516 ours_check.set_availability(None);
517 theirs_check.set_availability(None);
518
519 if ours_check != theirs_check {
520 return;
521 }
522
523 let our_avail = ours.availability().map(|a| a.value).unwrap_or_default();
524 let their_avail = theirs.availability().map(|a| a.value).unwrap_or_default();
525
526 let Some(avail_cmp) = our_avail.partial_cmp(&their_avail) else {
527 return;
528 };
529
530 let our_raw_set: HashSet<&Name> = our_spanned.iter().map(|s| &s.value).collect();
531
532 let mut remove_from_ours_raw = HashSet::new();
533 let mut remove_from_theirs_raw = HashSet::new();
534
535 for item in &their_spanned {
536 let name = &item.value;
537 if !our_raw_set.contains(name) {
538 continue;
539 }
540
541 match avail_cmp {
542 cmp::Ordering::Less => {
543 remove_from_ours_raw.insert(name.clone());
544 }
545 cmp::Ordering::Greater => {
546 remove_from_theirs_raw.insert(name.clone());
547 }
548 cmp::Ordering::Equal => {
549 remove_from_theirs_raw.insert(name.clone());
550 }
551 }
552 }
553
554 if !remove_from_ours_raw.is_empty() {
555 let new_ours =
556 our_spanned.into_iter().filter(|s| !remove_from_ours_raw.contains(&s.value)).collect();
557 ours.set_names(new_ours);
558 }
559
560 if !remove_from_theirs_raw.is_empty() {
561 let new_theirs = their_spanned
562 .into_iter()
563 .filter(|s| !remove_from_theirs_raw.contains(&s.value))
564 .collect();
565 theirs.set_names(new_theirs);
566 }
567}
568
569trait ValueMap {
571 fn get_mut(&mut self, key: &str) -> Option<&mut Value>;
572 fn insert(&mut self, key: String, val: Value);
573}
574
575impl ValueMap for Map<String, Value> {
576 fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
577 self.get_mut(key)
578 }
579
580 fn insert(&mut self, key: String, val: Value) {
581 self.insert(key, val);
582 }
583}
584
585impl ValueMap for IndexMap<String, Value> {
586 fn get_mut(&mut self, key: &str) -> Option<&mut Value> {
587 self.get_mut(key)
588 }
589
590 fn insert(&mut self, key: String, val: Value) {
591 self.insert(key, val);
592 }
593}
594
595#[derive(Debug, Default, Serialize, PartialEq)]
596pub struct DocumentContext {
597 #[serde(skip_serializing_if = "Option::is_none")]
598 pub include: Option<Vec<ContextSpanned<String>>>,
599 #[serde(skip_serializing_if = "Option::is_none")]
600 pub program: Option<ContextSpanned<ContextProgram>>,
601 #[serde(skip_serializing_if = "Option::is_none")]
602 pub children: Option<Vec<ContextSpanned<ContextChild>>>,
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub collections: Option<Vec<ContextSpanned<ContextCollection>>>,
605 #[serde(skip_serializing_if = "Option::is_none")]
606 pub environments: Option<Vec<ContextSpanned<ContextEnvironment>>>,
607 #[serde(skip_serializing_if = "Option::is_none")]
608 pub capabilities: Option<Vec<ContextSpanned<ContextCapability>>>,
609 #[serde(skip_serializing_if = "Option::is_none")]
610 pub r#use: Option<Vec<ContextSpanned<ContextUse>>>,
611 #[serde(skip_serializing_if = "Option::is_none")]
612 pub expose: Option<Vec<ContextSpanned<ContextExpose>>>,
613 #[serde(skip_serializing_if = "Option::is_none")]
614 pub offer: Option<Vec<ContextSpanned<ContextOffer>>>,
615 #[serde(skip_serializing_if = "Option::is_none")]
616 pub facets: Option<IndexMap<String, ContextSpanned<Value>>>,
617 #[serde(skip_serializing_if = "Option::is_none")]
618 pub config: Option<BTreeMap<ConfigKey, ContextSpanned<ConfigValueType>>>,
619}
620
621impl DocumentContext {
622 pub fn merge_from(
623 &mut self,
624 mut other: DocumentContext,
625 include_path: &path::Path,
626 ) -> Result<(), Error> {
627 merge_spanned_vec!(self, other, include);
628 self.merge_program(&mut other, include_path)?;
629 merge_spanned_vec!(self, other, children);
630 merge_spanned_vec!(self, other, collections);
631 self.merge_environment(&mut other)?;
632 merge_from_context_capability_field(&mut self.capabilities, &mut other.capabilities)?;
633 merge_from_context_capability_field(&mut self.r#use, &mut other.r#use)?;
634 merge_from_context_capability_field(&mut self.expose, &mut other.expose)?;
635 merge_from_context_capability_field(&mut self.offer, &mut other.offer)?;
636 self.merge_facets(&mut other, include_path)?;
637 self.merge_config(&mut other)?;
638 Ok(())
639 }
640
641 pub fn canonicalize(&mut self) {
642 if let Some(children) = &mut self.children {
643 children.sort_by(|a, b| a.value.name.cmp(&b.value.name));
644 }
645 if let Some(collections) = &mut self.collections {
646 collections.sort_by(|a, b| a.value.name.cmp(&b.value.name));
647 }
648 if let Some(environments) = &mut self.environments {
649 environments.sort_by(|a, b| a.value.name.cmp(&b.value.name));
650 }
651 if let Some(capabilities) = &mut self.capabilities {
652 capabilities.canonicalize_context();
653 }
654 if let Some(offers) = &mut self.offer {
655 offers.canonicalize_context();
656 }
657 if let Some(expose) = &mut self.expose {
658 expose.canonicalize_context();
659 }
660 if let Some(r#use) = &mut self.r#use {
661 r#use.canonicalize_context();
662 }
663 }
664
665 pub fn all_storage_names(&self) -> Vec<&BorrowedName> {
666 if let Some(capabilities) = self.capabilities.as_ref() {
667 capabilities
668 .iter()
669 .filter_map(|c| c.value.storage.as_ref().map(|n| n.value.as_ref()))
670 .collect()
671 } else {
672 vec![]
673 }
674 }
675
676 pub fn all_storage_with_sources<'a>(&'a self) -> HashMap<Name, &'a CapabilityFromRef> {
677 if let Some(capabilities) = self.capabilities.as_ref() {
678 capabilities
679 .iter()
680 .filter_map(|cap_wrapper| {
681 let c = &cap_wrapper.value;
682
683 let storage_span_opt = c.storage.as_ref();
684 let source_span_opt = c.from.as_ref();
685
686 match (storage_span_opt, source_span_opt) {
687 (Some(s_span), Some(f_span)) => {
688 let name_ref: Name = s_span.value.clone();
689 let source_ref: &CapabilityFromRef = &f_span.value;
690
691 Some((name_ref, source_ref))
692 }
693 _ => None,
694 }
695 })
696 .collect()
697 } else {
698 HashMap::new()
699 }
700 }
701
702 pub fn all_capability_names(&self) -> HashSet<Name> {
703 self.capabilities
704 .as_ref()
705 .map(|c| {
706 c.iter()
707 .flat_map(|capability_wrapper| capability_wrapper.value.names())
708 .map(|spanned_name| spanned_name.value)
709 .collect()
710 })
711 .unwrap_or_default()
712 }
713
714 pub fn all_collection_names(&self) -> Vec<&BorrowedName> {
715 if let Some(collections) = self.collections.as_ref() {
716 collections.iter().map(|c| c.value.name.value.as_ref()).collect()
717 } else {
718 vec![]
719 }
720 }
721
722 pub fn all_config_names(&self) -> Vec<&BorrowedName> {
723 self.capabilities
724 .as_ref()
725 .map(|caps| {
726 caps.iter()
727 .filter_map(|cap_wrapper| {
728 let cap = &cap_wrapper.value;
729
730 cap.config.as_ref().map(|spanned_key| spanned_key.value.as_ref())
731 })
732 .collect()
733 })
734 .unwrap_or_else(|| vec![])
735 }
736
737 pub fn all_children_names(&self) -> Vec<&BorrowedName> {
738 self.children
739 .as_ref()
740 .map(|children| children.iter().map(|c| c.value.name.value.as_ref()).collect())
741 .unwrap_or_default()
742 }
743
744 pub fn all_dictionaries<'a>(&'a self) -> HashMap<Name, &'a ContextCapability> {
745 if let Some(capabilities) = self.capabilities.as_ref() {
746 capabilities
747 .iter()
748 .filter_map(|cap_wrapper| {
749 let cap = &cap_wrapper.value;
750 let dict_span_opt = cap.dictionary.as_ref();
751
752 dict_span_opt.and_then(|dict_span| {
753 let name_value = &dict_span.value;
754 let name: Name = name_value.clone();
755 Some((name, cap))
756 })
757 })
758 .collect()
759 } else {
760 HashMap::new()
761 }
762 }
763
764 pub fn all_dictionary_names(&self) -> Vec<&BorrowedName> {
765 if let Some(capabilities) = self.capabilities.as_ref() {
766 capabilities
767 .iter()
768 .filter_map(|c| c.value.dictionary.as_ref().map(|d| d.value.as_ref()))
769 .collect()
770 } else {
771 vec![]
772 }
773 }
774
775 pub fn all_environment_names(&self) -> Vec<&BorrowedName> {
776 self.environments
777 .as_ref()
778 .map(|c| c.iter().map(|s| s.value.name.value.as_ref()).collect())
779 .unwrap_or_else(|| vec![])
780 }
781
782 pub fn all_runner_names(&self) -> Vec<&BorrowedName> {
783 self.capabilities
784 .as_ref()
785 .map(|caps| {
786 caps.iter()
787 .filter_map(|cap_wrapper| {
788 let cap = &cap_wrapper.value;
789
790 cap.runner.as_ref().map(|spanned_key| spanned_key.value.as_ref())
791 })
792 .collect()
793 })
794 .unwrap_or_else(|| vec![])
795 }
796
797 pub fn all_resolver_names(&self) -> Vec<&BorrowedName> {
798 self.capabilities
799 .as_ref()
800 .map(|caps| {
801 caps.iter()
802 .filter_map(|cap_wrapper| {
803 let cap = &cap_wrapper.value;
804
805 cap.resolver.as_ref().map(|spanned_key| spanned_key.value.as_ref())
806 })
807 .collect()
808 })
809 .unwrap_or_else(|| vec![])
811 }
812
813 fn merge_program(
814 &mut self,
815 other: &mut DocumentContext,
816 include_path: &path::Path,
817 ) -> Result<(), Error> {
818 if other.program.is_none() {
819 return Ok(());
820 }
821 if self.program.is_none() {
822 self.program = other.program.clone();
823 return Ok(());
824 }
825
826 let my_program = &mut self.program.as_mut().unwrap().value;
827 let other_wrapper = other.program.as_mut().unwrap();
828
829 let other_origin = other_wrapper.origin.clone();
830 let other_program_val = &mut other_wrapper.value;
831
832 if let Some(other_runner) = other_program_val.runner.take() {
833 if let Some(my_runner) = my_program.runner.as_ref() {
834 if my_runner.value != other_runner.value {
835 return Err(Error::merge(
836 format!(
837 "Manifest include had a conflicting `program.runner`: parent='{}', include='{}'",
838 my_runner.value, other_runner.value
839 ),
840 Some(other_runner.origin),
841 ));
842 }
843 } else {
844 my_program.runner = Some(other_runner);
845 }
846 }
847
848 Self::merge_maps_unified(
849 &mut my_program.info,
850 &other_program_val.info,
851 "program",
852 include_path,
853 Some(&other_origin),
854 Some(&vec!["environ", "features"]),
855 )
856 }
857
858 fn merge_environment(&mut self, other: &mut DocumentContext) -> Result<(), Error> {
859 if other.environments.is_none() {
860 return Ok(());
861 }
862 if self.environments.is_none() {
863 self.environments = Some(vec![]);
864 }
865
866 let merged_results = {
867 let my_environments = self.environments.as_mut().unwrap();
868 let other_environments = other.environments.as_mut().unwrap();
869
870 my_environments.sort_by(|x, y| x.value.name.value.cmp(&y.value.name.value));
871 other_environments.sort_by(|x, y| x.value.name.value.cmp(&y.value.name.value));
872
873 let all_environments =
874 my_environments.drain(..).merge_by(other_environments.drain(..), |x, y| {
875 x.value.name.value <= y.value.name.value
876 });
877
878 let groups = all_environments.chunk_by(|e| e.value.name.value.clone());
879
880 let mut results = vec![];
881 for (_name_value, group) in &groups {
882 let mut group_iter = group.into_iter();
883 let first_wrapper = group_iter.next().expect("chunk cannot be empty");
884 let first_origin = first_wrapper.origin.clone();
885 let mut merged_inner = first_wrapper.value;
886
887 for subsequent in group_iter {
888 merged_inner.merge_from(subsequent.value)?;
889 }
890
891 results.push(ContextSpanned { value: merged_inner, origin: first_origin });
892 }
893 results
894 };
895
896 self.environments = Some(merged_results);
897 Ok(())
898 }
899
900 fn merge_facets(
901 &mut self,
902 other: &mut DocumentContext,
903 include_path: &path::Path,
904 ) -> Result<(), Error> {
905 if let None = other.facets {
906 return Ok(());
907 }
908 if let None = self.facets {
909 self.facets = Some(Default::default());
910 }
911 let other_facets = other.facets.as_ref().unwrap();
912
913 for (key, include_spanned) in other_facets {
914 let entry_origin = Some(&include_spanned.origin);
915 let my_facets = self.facets.as_mut().unwrap();
916
917 if !my_facets.contains_key(key) {
918 my_facets.insert(key.clone(), include_spanned.clone());
919 } else {
920 let self_spanned = my_facets.get_mut(key).unwrap();
921 match (&mut self_spanned.value, &include_spanned.value) {
922 (
923 serde_json::Value::Object(self_obj),
924 serde_json::Value::Object(include_obj),
925 ) => {
926 Self::merge_maps_unified(
927 self_obj,
928 include_obj,
929 &format!("facets.{}", key),
930 include_path,
931 entry_origin,
932 None,
933 )?;
934 }
935 (v1, v2) => {
936 if v1 != v2 {
937 return Err(Error::merge(
938 format!(
939 "Manifest include '{}' had a conflicting value for field \"facets.{}\"",
940 include_path.display(),
941 key
942 ),
943 entry_origin.cloned(),
944 ));
945 }
946 }
947 }
948 }
949 }
950 Ok(())
951 }
952
953 fn merge_config(&mut self, other: &mut DocumentContext) -> Result<(), Error> {
954 if other.config.is_none() {
955 return Ok(());
956 }
957 if self.config.is_none() {
958 self.config = Some(BTreeMap::new());
959 }
960
961 let my_config = self.config.as_mut().unwrap();
962 let other_config = other.config.as_ref().unwrap();
963
964 for (key, other_spanned) in other_config {
965 if let Some(my_spanned) = my_config.get(key) {
966 if my_spanned.value != other_spanned.value {
967 return Err(Error::merge(
968 format!("Conflicting configuration key found: '{}'", key),
969 Some(other_spanned.origin.clone()),
970 ));
971 }
972 } else {
973 my_config.insert(key.clone(), other_spanned.clone());
974 }
975 }
976 Ok(())
977 }
978
979 fn merge_maps_unified<'s, Source, Dest>(
980 self_map: &mut Dest,
981 include_map: Source,
982 outer_key: &str,
983 include_path: &path::Path,
984 origin: Option<&Arc<path::Path>>,
985 allow_array_concatenation_keys: Option<&Vec<&str>>,
986 ) -> Result<(), Error>
987 where
988 Source: IntoIterator<Item = (&'s String, &'s serde_json::Value)>,
989 Dest: ValueMap,
990 {
991 for (key, include_val) in include_map {
992 match self_map.get_mut(key) {
993 None => {
994 self_map.insert(key.clone(), include_val.clone());
995 }
996 Some(self_val) => match (self_val, include_val) {
997 (serde_json::Value::Object(s_inner), serde_json::Value::Object(i_inner)) => {
998 let combined_key = format!("{}.{}", outer_key, key);
999 Self::merge_maps_unified(
1000 s_inner,
1001 i_inner,
1002 &combined_key,
1003 include_path,
1004 origin,
1005 allow_array_concatenation_keys,
1006 )?;
1007 }
1008 (serde_json::Value::Array(s_arr), serde_json::Value::Array(i_arr)) => {
1009 let is_allowed = allow_array_concatenation_keys
1010 .map_or(true, |keys| keys.contains(&key.as_str()));
1011
1012 if is_allowed {
1013 s_arr.extend(i_arr.clone());
1014 } else if s_arr != i_arr {
1015 return Err(Error::merge(
1016 format!(
1017 "Conflicting array values for field \"{}.{}\"",
1018 outer_key, key
1019 ),
1020 origin.cloned(),
1021 ));
1022 }
1023 }
1024 (v1, v2) if v1 == v2 => {}
1025 _ => {
1026 return Err(Error::merge(
1027 format!(
1028 "Manifest include '{}' had a conflicting value for field \"{}.{}\"",
1029 include_path.display(),
1030 outer_key,
1031 key
1032 ),
1033 origin.cloned(),
1034 ));
1035 }
1036 },
1037 }
1038 }
1039 Ok(())
1040 }
1041
1042 pub fn includes(&self) -> Vec<String> {
1043 self.include
1044 .as_ref()
1045 .map(|includes| includes.iter().map(|s| s.value.clone()).collect())
1046 .unwrap_or_default()
1047 }
1048}
1049
1050pub fn parse_and_hydrate(
1051 file_arc: Arc<std::path::Path>,
1052 buffer: &String,
1053) -> Result<DocumentContext, Error> {
1054 let parsed_doc: Document = serde_json5::from_str(buffer).map_err(|e| {
1055 let serde_json5::Error::Message { location, msg } = e;
1056 let location = location.map(|l| Location { line: l.line, column: l.column });
1057 Error::parse(msg, location, Some(&file_arc.clone()))
1058 })?;
1059
1060 let include = parsed_doc.include.map(|raw_includes| {
1061 raw_includes
1062 .into_iter()
1063 .map(|path| hydrate_simple(path, &file_arc))
1064 .collect::<Vec<ContextSpanned<String>>>()
1065 });
1066
1067 let facets = parsed_doc.facets.map(|raw_facets| {
1068 raw_facets
1069 .into_iter()
1070 .map(|(key, val)| (key, hydrate_simple(val, &file_arc)))
1071 .collect::<IndexMap<String, ContextSpanned<serde_json::Value>>>()
1072 });
1073
1074 let config = parsed_doc.config.map(|raw_config| {
1075 raw_config
1076 .into_iter()
1077 .map(|(key, val)| (key, hydrate_simple(val, &file_arc)))
1078 .collect::<BTreeMap<ConfigKey, ContextSpanned<ConfigValueType>>>()
1079 });
1080
1081 Ok(DocumentContext {
1082 include,
1083 program: hydrate_opt(parsed_doc.program, &file_arc)?,
1084 children: hydrate_list(parsed_doc.children, &file_arc)?,
1085 collections: hydrate_list(parsed_doc.collections, &file_arc)?,
1086 environments: hydrate_list(parsed_doc.environments, &file_arc)?,
1087 capabilities: hydrate_list(parsed_doc.capabilities, &file_arc)?,
1088 r#use: hydrate_list(parsed_doc.r#use, &file_arc)?,
1089 expose: hydrate_list(parsed_doc.expose, &file_arc)?,
1090 offer: hydrate_list(parsed_doc.offer, &file_arc)?,
1091 facets,
1092 config,
1093 })
1094}
1095
1096#[cfg(test)]
1097mod tests {
1098 use super::*;
1099 use crate::OneOrMany;
1100 use difference::Changeset;
1101 use serde_json::{json, to_string_pretty, to_value};
1102 use std::path;
1103 use std::path::Path;
1104 use test_case::test_case;
1105
1106 fn document_context(contents: &str) -> DocumentContext {
1107 let file_arc = Arc::from(std::path::Path::new("test.cml"));
1108 parse_and_hydrate(file_arc, &contents.to_string()).unwrap()
1109 }
1110
1111 macro_rules! assert_json_eq {
1112 ($a:expr, $e:expr) => {{
1113 if $a != $e {
1114 let expected = to_string_pretty(&$e).unwrap();
1115 let actual = to_string_pretty(&$a).unwrap();
1116 assert_eq!(
1117 $a,
1118 $e,
1119 "JSON actual != expected. Diffs:\n\n{}",
1120 Changeset::new(&actual, &expected, "\n")
1121 );
1122 }
1123 }};
1124 }
1125
1126 #[test]
1127 fn test_includes() {
1128 let buffer = r##"{}"##;
1129 let empty_document = document_context(buffer);
1130 assert_eq!(empty_document.includes(), Vec::<String>::new());
1131
1132 let buffer = r##"{"include": []}"##;
1133 let empty_include = document_context(buffer);
1134 assert_eq!(empty_include.includes(), Vec::<String>::new());
1135
1136 let buffer = r##"{ "include": [ "foo.cml", "bar.cml" ]}"##;
1137 let include_doc = document_context(buffer);
1138
1139 assert_eq!(include_doc.includes(), vec!["foo.cml", "bar.cml"]);
1140 }
1141
1142 #[test]
1143 fn test_merge_same_section() {
1144 let mut some = document_context(r##"{ "use": [{ "protocol": "foo" }] }"##);
1145 let other = document_context(r##"{ "use": [{ "protocol": "bar" }] }"##);
1146 some.merge_from(other, &Path::new("some/path")).unwrap();
1147 let uses = some.r#use.as_ref().unwrap();
1148 assert_eq!(uses.len(), 2);
1149 let get_protocol = |u: &ContextSpanned<ContextUse>| -> String {
1150 let proto_wrapper = u.value.protocol.as_ref().expect("Missing protocol");
1151
1152 match &proto_wrapper.value {
1153 OneOrMany::One(name) => name.to_string(),
1154 OneOrMany::Many(_) => panic!("Expected single protocol, found list"),
1155 }
1156 };
1157
1158 assert_eq!(get_protocol(&uses[0]), "foo");
1159 assert_eq!(get_protocol(&uses[1]), "bar");
1160 }
1161
1162 #[test]
1163 fn test_merge_upgraded_availability() {
1164 let mut some =
1165 document_context(r##"{ "use": [{ "protocol": "foo", "availability": "optional" }] }"##);
1166 let other1 = document_context(r##"{ "use": [{ "protocol": "foo" }] }"##);
1167 let other2 = document_context(
1168 r##"{ "use": [{ "protocol": "foo", "availability": "transitional" }] }"##,
1169 );
1170 let other3 = document_context(
1171 r##"{ "use": [{ "protocol": "foo", "availability": "same_as_target" }] }"##,
1172 );
1173 some.merge_from(other1, &Path::new("some/path")).unwrap();
1174 some.merge_from(other2, &Path::new("some/path")).unwrap();
1175 some.merge_from(other3, &Path::new("some/path")).unwrap();
1176
1177 let uses = some.r#use.as_ref().unwrap();
1178 assert_eq!(uses.len(), 2);
1179 assert_eq!(
1180 uses[0].protocol().as_ref().unwrap().value,
1181 OneOrMany::One("foo".parse::<Name>().unwrap().as_ref())
1182 );
1183 assert!(uses[0].availability().is_none());
1184 assert_eq!(
1185 uses[1].protocol().as_ref().unwrap().value,
1186 OneOrMany::One("foo".parse::<Name>().unwrap().as_ref())
1187 );
1188 assert_eq!(uses[1].availability().as_ref().unwrap().value, Availability::SameAsTarget,);
1189 }
1190
1191 #[test]
1192 fn test_merge_different_sections() {
1193 let mut some = document_context(r##"{ "use": [{ "protocol": "foo" }] }"##);
1194 let other = document_context(r##"{ "expose": [{ "protocol": "bar", "from": "self" }] }"##);
1195 some.merge_from(other, &Path::new("some/path")).unwrap();
1196 let uses = some.r#use.as_ref().unwrap();
1197 let exposes = some.expose.as_ref().unwrap();
1198 assert_eq!(uses.len(), 1);
1199 assert_eq!(exposes.len(), 1);
1200 assert_eq!(
1201 uses[0].protocol().as_ref().unwrap().value,
1202 OneOrMany::One("foo".parse::<Name>().unwrap().as_ref())
1203 );
1204 assert_eq!(
1205 exposes[0].protocol().as_ref().unwrap().value,
1206 OneOrMany::One("bar".parse::<Name>().unwrap().as_ref())
1207 );
1208 }
1209
1210 #[test]
1211 fn test_merge_environments() {
1212 let mut some = document_context(
1213 r##"
1214 { "environments": [
1215 {
1216 "name": "one",
1217 "extends": "realm"
1218 },
1219 {
1220 "name": "two",
1221 "extends": "none",
1222 "runners": [
1223 {
1224 "runner": "r1",
1225 "from": "#c1"
1226 },
1227 {
1228 "runner": "r2",
1229 "from": "#c2"
1230 }
1231 ],
1232 "resolvers": [
1233 {
1234 "resolver": "res1",
1235 "from": "#c1",
1236 "scheme": "foo"
1237 }
1238 ],
1239 "debug": [
1240 {
1241 "protocol": "baz",
1242 "from": "#c2"
1243 }
1244 ]
1245 }
1246 ]}"##,
1247 );
1248 let other = document_context(
1249 r##"
1250 { "environments": [
1251 {
1252 "name": "two",
1253 "__stop_timeout_ms": 100,
1254 "runners": [
1255 {
1256 "runner": "r3",
1257 "from": "#c3"
1258 }
1259 ],
1260 "resolvers": [
1261 {
1262 "resolver": "res2",
1263 "from": "#c1",
1264 "scheme": "bar"
1265 }
1266 ],
1267 "debug": [
1268 {
1269 "protocol": "faz",
1270 "from": "#c2"
1271 }
1272 ]
1273 },
1274 {
1275 "name": "three",
1276 "__stop_timeout_ms": 1000
1277 }
1278 ]}"##,
1279 );
1280 some.merge_from(other, &Path::new("some/path")).unwrap();
1281 assert_eq!(
1282 to_value(some).unwrap(),
1283 json!({"environments": [
1284 {
1285 "name": "one",
1286 "extends": "realm",
1287 },
1288 {
1289 "name": "three",
1290 "__stop_timeout_ms": 1000,
1291 },
1292 {
1293 "name": "two",
1294 "extends": "none",
1295 "__stop_timeout_ms": 100,
1296 "runners": [
1297 {
1298 "runner": "r1",
1299 "from": "#c1",
1300 },
1301 {
1302 "runner": "r2",
1303 "from": "#c2",
1304 },
1305 {
1306 "runner": "r3",
1307 "from": "#c3",
1308 },
1309 ],
1310 "resolvers": [
1311 {
1312 "resolver": "res1",
1313 "from": "#c1",
1314 "scheme": "foo",
1315 },
1316 {
1317 "resolver": "res2",
1318 "from": "#c1",
1319 "scheme": "bar",
1320 },
1321 ],
1322 "debug": [
1323 {
1324 "protocol": "baz",
1325 "from": "#c2"
1326 },
1327 {
1328 "protocol": "faz",
1329 "from": "#c2"
1330 }
1331 ]
1332 },
1333 ]})
1334 );
1335 }
1336
1337 #[test]
1338 fn test_merge_environments_errors() {
1339 {
1340 let mut some =
1341 document_context(r##"{"environments": [{"name": "one", "extends": "realm"}]}"##);
1342 let other =
1343 document_context(r##"{"environments": [{"name": "one", "extends": "none"}]}"##);
1344 assert!(some.merge_from(other, &Path::new("some/path")).is_err());
1345 }
1346 {
1347 let mut some = document_context(
1348 r##"{"environments": [{"name": "one", "__stop_timeout_ms": 10}]}"##,
1349 );
1350 let other = document_context(
1351 r##"{"environments": [{"name": "one", "__stop_timeout_ms": 20}]}"##,
1352 );
1353 assert!(some.merge_from(other, &Path::new("some/path")).is_err());
1354 }
1355
1356 {
1358 let mut some =
1359 document_context(r##"{"environments": [{"name": "one", "extends": "realm"}]}"##);
1360 let other =
1361 document_context(r##"{"environments": [{"name": "one", "extends": "realm"}]}"##);
1362 some.merge_from(other, &Path::new("some/path")).unwrap();
1363 assert_eq!(
1364 to_value(some).unwrap(),
1365 json!({"environments": [{"name": "one", "extends": "realm"}]})
1366 );
1367 }
1368 {
1369 let mut some = document_context(
1370 r##"{"environments": [{"name": "one", "__stop_timeout_ms": 10}]}"##,
1371 );
1372 let other = document_context(
1373 r##"{"environments": [{"name": "one", "__stop_timeout_ms": 10}]}"##,
1374 );
1375 some.merge_from(other, &Path::new("some/path")).unwrap();
1376 assert_eq!(
1377 to_value(some).unwrap(),
1378 json!({"environments": [{"name": "one", "__stop_timeout_ms": 10}]})
1379 );
1380 }
1381 }
1382
1383 #[test]
1384 fn test_merge_from_other_config() {
1385 let mut some = document_context(r##"{}"##);
1386 let other = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1387
1388 some.merge_from(other, &path::Path::new("some/path")).unwrap();
1389 let expected = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1390 assert_eq!(some.config, expected.config);
1391 }
1392
1393 #[test]
1394 fn test_merge_from_some_config() {
1395 let mut some = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1396 let other = document_context(r##"{}"##);
1397
1398 some.merge_from(other, &path::Path::new("some/path")).unwrap();
1399 let expected = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1400 assert_eq!(some.config, expected.config);
1401 }
1402
1403 #[test]
1404 fn test_merge_from_config() {
1405 let mut some = document_context(r##"{ "config": { "foo": { "type": "bool" } } }"##);
1406 let other = document_context(r##"{ "config": { "bar": { "type": "bool" } } }"##);
1407 some.merge_from(other, &path::Path::new("some/path")).unwrap();
1408
1409 assert_eq!(
1410 to_value(some).unwrap(),
1411 json!({
1412 "config": {
1413 "foo": { "type": "bool" },
1414 "bar": { "type": "bool" }
1415 }
1416 }),
1417 );
1418 }
1419
1420 #[test]
1421 fn test_merge_from_config_dedupe_identical_fields() {
1422 let mut some = document_context(r##"{ "config": { "foo": { "type": "bool" } } }"##);
1423 let other = document_context(r##"{ "config": { "foo": { "type": "bool" } } }"##);
1424 some.merge_from(other, &path::Path::new("some/path")).unwrap();
1425
1426 assert_eq!(to_value(some).unwrap(), json!({ "config": { "foo": { "type": "bool" } } }));
1427 }
1428
1429 #[test]
1430 fn test_merge_from_config_conflicting_keys() {
1431 let mut some = document_context(r##"{ "config": { "foo": { "type": "bool" } } }"##);
1432 let other = document_context(r##"{ "config": { "foo": { "type": "uint8" } } }"##);
1433
1434 assert_matches::assert_matches!(
1435 some.merge_from(other, &path::Path::new("some/path")),
1436 Err(Error::Merge { err, .. })
1437 if err == "Conflicting configuration key found: 'foo'"
1438 );
1439 }
1440
1441 #[test]
1442 fn test_canonicalize_context() {
1443 let mut some = document_context(
1444 &json!({
1445 "children": [
1446 { "name": "b_child", "url": "http://foo/b" },
1448 { "name": "a_child", "url": "http://foo/a" },
1449 ],
1450 "environments": [
1451 { "name": "b_env" },
1453 { "name": "a_env" },
1454 ],
1455 "collections": [
1456 { "name": "b_coll", "durability": "transient" },
1458 { "name": "a_coll", "durability": "transient" },
1459 ],
1460 "capabilities": [
1463 { "protocol": ["foo"] },
1465 { "protocol": "bar" },
1466 { "protocol": "arg", "path": "/arg" },
1468 { "service": ["b", "a"] },
1470 { "event_stream": ["b", "a"] },
1472 { "runner": "myrunner" },
1473 { "runner": "mypathrunner1", "path": "/foo" },
1475 { "runner": "mypathrunner2", "path": "/foo" },
1476 ],
1477 "offer": [
1479 { "protocol": "baz", "from": "#a_child", "to": "#c_child" },
1481 { "protocol": ["foo"], "from": "#a_child", "to": "#b_child" },
1483 { "protocol": "bar", "from": "#a_child", "to": "#b_child" },
1484 { "service": ["b", "a"], "from": "#a_child", "to": "#b_child" },
1486 {
1488 "event_stream": ["b", "a"],
1489 "from": "#a_child",
1490 "to": "#b_child",
1491 "scope": ["#b", "#c", "#a"] },
1493 { "runner": [ "myrunner", "a" ], "from": "#a_child", "to": "#b_child" },
1494 { "runner": [ "b" ], "from": "#a_child", "to": "#b_child" },
1495 { "directory": [ "b" ], "from": "#a_child", "to": "#b_child" },
1496 ],
1497 "expose": [
1498 { "protocol": ["foo"], "from": "#a_child" },
1499 { "protocol": "bar", "from": "#a_child" }, { "service": ["b", "a"], "from": "#a_child" },
1502 {
1504 "event_stream": ["b", "a"],
1505 "from": "#a_child",
1506 "scope": ["#b", "#c", "#a"] },
1508 { "runner": [ "myrunner", "a" ], "from": "#a_child" },
1509 { "runner": [ "b" ], "from": "#a_child" },
1510 { "directory": [ "b" ], "from": "#a_child" },
1511 ],
1512 "use": [
1513 { "protocol": ["zazzle"], "path": "/zazbaz" },
1515 { "protocol": ["foo"] },
1517 { "protocol": "bar" },
1518 { "service": ["b", "a"] },
1520 { "event_stream": ["b", "a"], "scope": ["#b", "#a"] },
1522 ],
1523 })
1524 .to_string(),
1525 );
1526 some.canonicalize();
1527
1528 assert_json_eq!(
1529 some,
1530 document_context(&json!({
1531 "children": [
1532 { "name": "a_child", "url": "http://foo/a" },
1533 { "name": "b_child", "url": "http://foo/b" },
1534 ],
1535 "collections": [
1536 { "name": "a_coll", "durability": "transient" },
1537 { "name": "b_coll", "durability": "transient" },
1538 ],
1539 "environments": [
1540 { "name": "a_env" },
1541 { "name": "b_env" },
1542 ],
1543 "capabilities": [
1544 { "event_stream": ["a", "b"] },
1545 { "protocol": "arg", "path": "/arg" },
1546 { "protocol": ["bar", "foo"] },
1547 { "runner": "mypathrunner1", "path": "/foo" },
1548 { "runner": "mypathrunner2", "path": "/foo" },
1549 { "runner": "myrunner" },
1550 { "service": ["a", "b"] },
1551 ],
1552 "use": [
1553 { "event_stream": ["a", "b"], "scope": ["#a", "#b"] },
1554 { "protocol": ["bar", "foo"] },
1555 { "protocol": "zazzle", "path": "/zazbaz" },
1556 { "service": ["a", "b"] },
1557 ],
1558 "offer": [
1559 { "directory": "b", "from": "#a_child", "to": "#b_child" },
1560 {
1561 "event_stream": ["a", "b"],
1562 "from": "#a_child",
1563 "to": "#b_child",
1564 "scope": ["#a", "#b", "#c"],
1565 },
1566 { "protocol": ["bar", "foo"], "from": "#a_child", "to": "#b_child" },
1567 { "protocol": "baz", "from": "#a_child", "to": "#c_child" },
1568 { "runner": [ "a", "b", "myrunner" ], "from": "#a_child", "to": "#b_child" },
1569 { "service": ["a", "b"], "from": "#a_child", "to": "#b_child" },
1570 ],
1571 "expose": [
1572 { "directory": "b", "from": "#a_child" },
1573 {
1574 "event_stream": ["a", "b"],
1575 "from": "#a_child",
1576 "scope": ["#a", "#b", "#c"],
1577 },
1578 { "protocol": ["bar", "foo"], "from": "#a_child" },
1579 { "runner": [ "a", "b", "myrunner" ], "from": "#a_child" },
1580 { "service": ["a", "b"], "from": "#a_child" },
1581 ],
1582 }).to_string())
1583 )
1584 }
1585
1586 #[test]
1587 fn deny_unknown_config_type_fields() {
1588 let contents =
1589 json!({ "config": { "foo": { "type": "bool", "unknown": "should error" } } });
1590 let file_arc = Arc::from(std::path::Path::new("test.cml"));
1591 parse_and_hydrate(file_arc, &contents.to_string())
1592 .expect_err("must reject unknown config field attributes");
1593 }
1594
1595 #[test]
1596 fn deny_unknown_config_nested_type_fields() {
1597 let input = json!({
1598 "config": {
1599 "foo": {
1600 "type": "vector",
1601 "max_count": 10,
1602 "element": {
1603 "type": "bool",
1604 "unknown": "should error"
1605 },
1606
1607 }
1608 }
1609 });
1610
1611 let file_arc = Arc::from(std::path::Path::new("test.cml"));
1612 parse_and_hydrate(file_arc, &input.to_string())
1613 .expect_err("must reject unknown config field attributes");
1614 }
1615
1616 #[test]
1617 fn test_merge_from_program() {
1618 let mut some =
1619 document_context(&json!({ "program": { "binary": "bin/hello_world" } }).to_string());
1620 let other = document_context(&json!({ "program": { "runner": "elf" } }).to_string());
1621 some.merge_from(other, &Path::new("some/path")).unwrap();
1622 let expected = document_context(
1623 &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1624 );
1625 assert_eq!(some.program, expected.program);
1626 }
1627
1628 #[test]
1629 fn test_merge_from_program_without_runner() {
1630 let mut some = document_context(
1631 &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1632 );
1633 let other = document_context(&json!({ "program": {} }).to_string());
1636 some.merge_from(other, &Path::new("some/path")).unwrap();
1637 let expected = document_context(
1638 &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1639 );
1640 assert_eq!(some.program, expected.program);
1641 }
1642
1643 #[test]
1644 fn test_merge_from_program_overlapping_environ() {
1645 let mut some = document_context(&json!({ "program": { "environ": ["1"] } }).to_string());
1647 let other = document_context(&json!({ "program": { "environ": ["2"] } }).to_string());
1648 some.merge_from(other, &Path::new("some/path")).unwrap();
1649 let expected =
1650 document_context(&json!({ "program": { "environ": ["1", "2"] } }).to_string());
1651 assert_eq!(some.program, expected.program);
1652 }
1653
1654 #[test]
1655 fn test_merge_from_program_overlapping_runner() {
1656 let mut some = document_context(
1658 &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1659 );
1660 let other = document_context(&json!({ "program": { "runner": "elf" } }).to_string());
1661 some.merge_from(other, &Path::new("some/path")).unwrap();
1662 let expected = document_context(
1663 &json!({ "program": { "binary": "bin/hello_world", "runner": "elf" } }).to_string(),
1664 );
1665 assert_eq!(some.program, expected.program);
1666 }
1667
1668 #[test]
1669 fn test_merge_from_program_error_runner() {
1670 let mut some = document_context(&json!({ "program": { "runner": "elf" } }).to_string());
1671 let other = document_context(&json!({ "program": { "runner": "fle" } }).to_string());
1672 assert_matches::assert_matches!(
1673 some.merge_from(other, &Path::new("some/path")),
1674 Err(Error::Merge { err, .. })
1675 if err == format!("Manifest include had a conflicting `program.runner`: parent='elf', include='fle'"));
1676 }
1677
1678 #[test]
1679 fn test_merge_from_program_error_binary() {
1680 let mut some =
1681 document_context(&json!({ "program": { "binary": "bin/hello_world" } }).to_string());
1682 let other =
1683 document_context(&json!({ "program": { "binary": "bin/hola_mundo" } }).to_string());
1684 assert_matches::assert_matches!(
1685 some.merge_from(other, &Path::new("some/path")),
1686 Err(Error::Merge { err, .. })
1687 if err == format!("Manifest include 'some/path' had a conflicting value for field \"program.binary\""));
1688 }
1689
1690 #[test]
1691 fn test_merge_from_program_error_args() {
1692 let mut some =
1693 document_context(&json!({ "program": { "args": ["a".to_owned()] } }).to_string());
1694 let other =
1695 document_context(&json!({ "program": { "args": ["b".to_owned()] } }).to_string());
1696 assert_matches::assert_matches!(
1697 some.merge_from(other, &Path::new("some/path")),
1698 Err(Error::Merge { err, .. })
1699 if err == format!("Conflicting array values for field \"program.args\""));
1700 }
1701
1702 #[test_case(
1703 document_context(&json!({ "facets": { "my.key": "my.value" } }).to_string()),
1704 document_context(&json!({ "facets": { "other.key": "other.value" } }).to_string()),
1705 document_context(&json!({ "facets": { "my.key": "my.value", "other.key": "other.value" } }).to_string())
1706 ; "two separate keys"
1707 )]
1708 #[test_case(
1709 document_context(&json!({ "facets": { "my.key": "my.value" } }).to_string()),
1710 document_context(&json!({ "facets": {} }).to_string()),
1711 document_context(&json!({ "facets": { "my.key": "my.value" } }).to_string())
1712 ; "empty other facet"
1713 )]
1714 #[test_case(
1715 document_context(&json!({ "facets": {} }).to_string()),
1716 document_context(&json!({ "facets": { "other.key": "other.value" } }).to_string()),
1717 document_context(&json!({ "facets": { "other.key": "other.value" } }).to_string())
1718 ; "empty my facet"
1719 )]
1720 #[test_case(
1721 document_context(&json!({ "facets": { "key": { "type": "some_type" } } }).to_string()),
1722 document_context(&json!({ "facets": { "key": { "runner": "some_runner"} } }).to_string()),
1723 document_context(&json!({ "facets": { "key": { "type": "some_type", "runner": "some_runner" } } }).to_string())
1724 ; "nested facet key"
1725 )]
1726 #[test_case(
1727 document_context(&json!({ "facets": { "key": { "type": "some_type", "nested_key": { "type": "new type" }}}}).to_string()),
1728 document_context(&json!({ "facets": { "key": { "nested_key": { "runner": "some_runner" }} } }).to_string()),
1729 document_context(&json!({ "facets": { "key": { "type": "some_type", "nested_key": { "runner": "some_runner", "type": "new type" }}}}).to_string())
1730 ; "double nested facet key"
1731 )]
1732 #[test_case(
1733 document_context(&json!({ "facets": { "key": { "array_key": ["value_1", "value_2"] } } }).to_string()),
1734 document_context(&json!({ "facets": { "key": { "array_key": ["value_3", "value_4"] } } }).to_string()),
1735 document_context(&json!({ "facets": { "key": { "array_key": ["value_1", "value_2", "value_3", "value_4"] } } }).to_string())
1736 ; "merge array values" )]
1738 fn test_merge_from_facets(
1739 mut my: DocumentContext,
1740 other: DocumentContext,
1741 expected: DocumentContext,
1742 ) {
1743 my.merge_from(other, &Path::new("some/path")).unwrap();
1744 assert_eq!(my.facets, expected.facets);
1745 }
1746
1747 #[test_case(
1748 document_context(&json!({ "facets": { "key": "my.value" }}).to_string()),
1749 document_context(&json!({ "facets": { "key": "other.value" }}).to_string()),
1750 "facets.key"
1751 ; "conflict first level keys" )]
1753 #[test_case(
1754 document_context(&json!({ "facets": { "key": {"type": "cts" }}}).to_string()),
1755 document_context(&json!({ "facets": { "key": {"type": "system" }}}).to_string()),
1756 "facets.key.type"
1757 ; "conflict second level keys"
1758 )]
1759 #[test_case(
1760 document_context(&json!({ "facets": { "key": {"type": {"key": "value" }}}}).to_string()),
1761 document_context(&json!({ "facets": { "key": {"type": "system" }}}).to_string()),
1762 "facets.key.type"
1763 ; "incompatible self nested type"
1764 )]
1765 #[test_case(
1766 document_context(&json!({ "facets": { "key": {"type": "system" }}}).to_string()),
1767 document_context(&json!({ "facets": { "key": {"type": {"key": "value" }}}}).to_string()),
1768 "facets.key.type"
1769 ; "incompatible other nested type"
1770 )]
1771 #[test_case(
1772 document_context(&json!({ "facets": { "key": {"type": {"key": "my.value" }}}}).to_string()),
1773 document_context(&json!({ "facets": { "key": {"type": {"key": "some.value" }}}}).to_string()),
1774 "facets.key.type.key"
1775 ; "conflict third level keys"
1776 )]
1777 #[test_case(
1778 document_context(&json!({ "facets": { "key": {"type": [ "value_1" ]}}}).to_string()),
1779 document_context(&json!({ "facets": { "key": {"type": "value_2" }}}).to_string()),
1780 "facets.key.type"
1781 ; "incompatible keys"
1782 )]
1783 fn test_merge_from_facet_error(mut my: DocumentContext, other: DocumentContext, field: &str) {
1784 assert_matches::assert_matches!(
1785 my.merge_from(other, &path::Path::new("some/path")),
1786 Err(Error::Merge { err, .. })
1787 if err == format!("Manifest include 'some/path' had a conflicting value for field \"{}\"", field)
1788 );
1789 }
1790
1791 #[test_case("protocol")]
1792 #[test_case("service")]
1793 #[test_case("event_stream")]
1794 fn test_merge_from_duplicate_use_array(typename: &str) {
1795 let mut my = document_context(&json!({ "use": [{ typename: "a" }]}).to_string());
1796 let other = document_context(
1797 &json!({ "use": [
1798 { typename: ["a", "b"], "availability": "optional"}
1799 ]})
1800 .to_string(),
1801 );
1802 let result = document_context(
1803 &json!({ "use": [
1804 { typename: "a" },
1805 { typename: "b", "availability": "optional" },
1806 ]})
1807 .to_string(),
1808 );
1809
1810 my.merge_from(other, &path::Path::new("some/path")).unwrap();
1811 assert_eq!(my, result);
1812 }
1813
1814 #[test_case("directory")]
1815 #[test_case("storage")]
1816 fn test_merge_from_duplicate_use_noarray(typename: &str) {
1817 let mut my =
1818 document_context(&json!({ "use": [{ typename: "a", "path": "/a"}]}).to_string());
1819 let other = document_context(
1820 &json!({ "use": [
1821 { typename: "a", "path": "/a", "availability": "optional" },
1822 { typename: "b", "path": "/b", "availability": "optional" },
1823 ]})
1824 .to_string(),
1825 );
1826 let result = document_context(
1827 &json!({ "use": [
1828 { typename: "a", "path": "/a" },
1829 { typename: "b", "path": "/b", "availability": "optional" },
1830 ]})
1831 .to_string(),
1832 );
1833 my.merge_from(other, &path::Path::new("some/path")).unwrap();
1834 assert_eq!(my, result);
1835 }
1836
1837 #[test_case("protocol")]
1838 #[test_case("service")]
1839 #[test_case("event_stream")]
1840 fn test_merge_from_duplicate_capabilities_array(typename: &str) {
1841 let mut my = document_context(&json!({ "capabilities": [{ typename: "a" }]}).to_string());
1842 let other =
1843 document_context(&json!({ "capabilities": [ { typename: ["a", "b"] } ]}).to_string());
1844 let result = document_context(
1845 &json!({ "capabilities": [ { typename: "a" }, { typename: "b" } ]}).to_string(),
1846 );
1847
1848 my.merge_from(other, &path::Path::new("some/path")).unwrap();
1849 assert_eq!(my, result);
1850 }
1851
1852 #[test_case("directory")]
1853 #[test_case("storage")]
1854 #[test_case("runner")]
1855 #[test_case("resolver")]
1856 fn test_merge_from_duplicate_capabilities_noarray(typename: &str) {
1857 let mut my = document_context(
1858 &json!({ "capabilities": [{ typename: "a", "path": "/a"}]}).to_string(),
1859 );
1860 let other = document_context(
1861 &json!({ "capabilities": [
1862 { typename: "a", "path": "/a" },
1863 { typename: "b", "path": "/b" },
1864 ]})
1865 .to_string(),
1866 );
1867 let result = document_context(
1868 &json!({ "capabilities": [
1869 { typename: "a", "path": "/a" },
1870 { typename: "b", "path": "/b" },
1871 ]})
1872 .to_string(),
1873 );
1874 my.merge_from(other, &path::Path::new("some/path")).unwrap();
1875 assert_eq!(my, result);
1876 }
1877
1878 #[test]
1879 fn test_merge_with_empty_names() {
1880 let mut my = document_context(&json!({ "capabilities": [{ "path": "/a"}]}).to_string());
1882
1883 let other = document_context(
1884 &json!({ "capabilities": [
1885 { "directory": "a", "path": "/a" },
1886 { "directory": "b", "path": "/b" },
1887 ]})
1888 .to_string(),
1889 );
1890 my.merge_from(other, &path::Path::new("some/path")).unwrap_err();
1891 }
1892
1893 #[test_case("protocol")]
1894 #[test_case("service")]
1895 #[test_case("event_stream")]
1896 #[test_case("directory")]
1897 #[test_case("storage")]
1898 #[test_case("runner")]
1899 #[test_case("resolver")]
1900 fn test_merge_from_duplicate_offers(typename: &str) {
1901 let mut my = document_context(
1902 &json!({ "offer": [{ typename: "a", "from": "self", "to": "#c" }]}).to_string(),
1903 );
1904 let other = document_context(
1905 &json!({ "offer": [
1906 { typename: ["a", "b"], "from": "self", "to": "#c", "availability": "optional" }
1907 ]})
1908 .to_string(),
1909 );
1910 let result = document_context(
1911 &json!({ "offer": [
1912 { typename: "a", "from": "self", "to": "#c" },
1913 { typename: "b", "from": "self", "to": "#c", "availability": "optional" },
1914 ]})
1915 .to_string(),
1916 );
1917
1918 my.merge_from(other, &path::Path::new("some/path")).unwrap();
1919 assert_eq!(my, result);
1920 }
1921
1922 #[test_case("protocol")]
1923 #[test_case("service")]
1924 #[test_case("event_stream")]
1925 #[test_case("directory")]
1926 #[test_case("runner")]
1927 #[test_case("resolver")]
1928 fn test_merge_from_duplicate_exposes(typename: &str) {
1929 let mut my =
1930 document_context(&json!({ "expose": [{ typename: "a", "from": "self" }]}).to_string());
1931 let other = document_context(
1932 &json!({ "expose": [
1933 { typename: ["a", "b"], "from": "self" }
1934 ]})
1935 .to_string(),
1936 );
1937 let result = document_context(
1938 &json!({ "expose": [
1939 { typename: "a", "from": "self" },
1940 { typename: "b", "from": "self" },
1941 ]})
1942 .to_string(),
1943 );
1944
1945 my.merge_from(other, &path::Path::new("some/path")).unwrap();
1946 assert_eq!(my, result);
1947 }
1948
1949 #[test_case(
1950 document_context(&json!({ "use": [
1951 { "protocol": "a", "availability": "required" },
1952 { "protocol": "b", "availability": "optional" },
1953 { "protocol": "c", "availability": "transitional" },
1954 { "protocol": "d", "availability": "same_as_target" },
1955 ]}).to_string()),
1956 document_context(&json!({ "use": [
1957 { "protocol": ["a"], "availability": "required" },
1958 { "protocol": ["b"], "availability": "optional" },
1959 { "protocol": ["c"], "availability": "transitional" },
1960 { "protocol": ["d"], "availability": "same_as_target" },
1961 ]}).to_string()),
1962 document_context(&json!({ "use": [
1963 { "protocol": "a", "availability": "required" },
1964 { "protocol": "b", "availability": "optional" },
1965 { "protocol": "c", "availability": "transitional" },
1966 { "protocol": "d", "availability": "same_as_target" },
1967 ]}).to_string())
1968 ; "merge both same"
1969 )]
1970 #[test_case(
1971 document_context(&json!({ "use": [
1972 { "protocol": "a", "availability": "optional" },
1973 { "protocol": "b", "availability": "transitional" },
1974 { "protocol": "c", "availability": "transitional" },
1975 ]}).to_string()),
1976 document_context(&json!({ "use": [
1977 { "protocol": ["a", "x"], "availability": "required" },
1978 { "protocol": ["b", "y"], "availability": "optional" },
1979 { "protocol": ["c", "z"], "availability": "required" },
1980 ]}).to_string()),
1981 document_context(&json!({ "use": [
1982 { "protocol": ["a", "x"], "availability": "required" },
1983 { "protocol": ["b", "y"], "availability": "optional" },
1984 { "protocol": ["c", "z"], "availability": "required" },
1985 ]}).to_string())
1986 ; "merge with upgrade"
1987 )]
1988 #[test_case(
1989 document_context(&json!({ "use": [
1990 { "protocol": "a", "availability": "required" },
1991 { "protocol": "b", "availability": "optional" },
1992 { "protocol": "c", "availability": "required" },
1993 ]}).to_string()),
1994 document_context(&json!({ "use": [
1995 { "protocol": ["a", "x"], "availability": "optional" },
1996 { "protocol": ["b", "y"], "availability": "transitional" },
1997 { "protocol": ["c", "z"], "availability": "transitional" },
1998 ]}).to_string()),
1999 document_context(&json!({ "use": [
2000 { "protocol": "a", "availability": "required" },
2001 { "protocol": "b", "availability": "optional" },
2002 { "protocol": "c", "availability": "required" },
2003 { "protocol": "x", "availability": "optional" },
2004 { "protocol": "y", "availability": "transitional" },
2005 { "protocol": "z", "availability": "transitional" },
2006 ]}).to_string())
2007 ; "merge with downgrade"
2008 )]
2009 #[test_case(
2010 document_context(&json!({ "use": [
2011 { "protocol": "a", "availability": "optional" },
2012 { "protocol": "b", "availability": "transitional" },
2013 { "protocol": "c", "availability": "transitional" },
2014 ]}).to_string()),
2015 document_context(&json!({ "use": [
2016 { "protocol": ["a", "x"], "availability": "same_as_target" },
2017 { "protocol": ["b", "y"], "availability": "same_as_target" },
2018 { "protocol": ["c", "z"], "availability": "same_as_target" },
2019 ]}).to_string()),
2020 document_context(&json!({ "use": [
2021 { "protocol": "a", "availability": "optional" },
2022 { "protocol": "b", "availability": "transitional" },
2023 { "protocol": "c", "availability": "transitional" },
2024 { "protocol": ["a", "x"], "availability": "same_as_target" },
2025 { "protocol": ["b", "y"], "availability": "same_as_target" },
2026 { "protocol": ["c", "z"], "availability": "same_as_target" },
2027 ]}).to_string())
2028 ; "merge with no replacement"
2029 )]
2030 #[test_case(
2031 document_context(&json!({ "use": [
2032 { "protocol": ["a", "b", "c"], "availability": "optional" },
2033 { "protocol": "d", "availability": "same_as_target" },
2034 { "protocol": ["e", "f"] },
2035 ]}).to_string()),
2036 document_context(&json!({ "use": [
2037 { "protocol": ["c", "e", "g"] },
2038 { "protocol": ["d", "h"] },
2039 { "protocol": ["f", "i"], "availability": "transitional" },
2040 ]}).to_string()),
2041 document_context(&json!({ "use": [
2042 { "protocol": ["a", "b"], "availability": "optional" },
2043 { "protocol": "d", "availability": "same_as_target" },
2044 { "protocol": ["e", "f"] },
2045 { "protocol": ["c", "g"] },
2046 { "protocol": ["d", "h"] },
2047 { "protocol": "i", "availability": "transitional" },
2048 ]}).to_string())
2049 ; "merge multiple"
2050 )]
2051
2052 fn test_merge_from_duplicate_capability_availability(
2053 mut my: DocumentContext,
2054 other: DocumentContext,
2055 result: DocumentContext,
2056 ) {
2057 my.merge_from(other, &path::Path::new("some/path")).unwrap();
2058 assert_eq!(my, result);
2059 }
2060
2061 #[test_case(
2062 document_context(&json!({ "use": [{ "protocol": ["a", "b"] }]}).to_string()),
2063 document_context(&json!({ "use": [{ "protocol": ["c", "d"] }]}).to_string()),
2064 document_context(&json!({ "use": [
2065 { "protocol": ["a", "b"] }, { "protocol": ["c", "d"] }
2066 ]}).to_string())
2067 ; "merge capabilities with disjoint sets"
2068 )]
2069 #[test_case(
2070 document_context(&json!({ "use": [
2071 { "protocol": ["a"] },
2072 { "protocol": "b" },
2073 ]}).to_string()),
2074 document_context(&json!({ "use": [{ "protocol": ["a", "b"] }]}).to_string()),
2075 document_context(&json!({ "use": [
2076 { "protocol": ["a"] }, { "protocol": "b" },
2077 ]}).to_string())
2078 ; "merge capabilities with equal set"
2079 )]
2080 #[test_case(
2081 document_context(&json!({ "use": [
2082 { "protocol": ["a", "b"] },
2083 { "protocol": "c" },
2084 ]}).to_string()),
2085 document_context(&json!({ "use": [{ "protocol": ["a", "b"] }]}).to_string()),
2086 document_context(&json!({ "use": [
2087 { "protocol": ["a", "b"] }, { "protocol": "c" },
2088 ]}).to_string())
2089 ; "merge capabilities with subset"
2090 )]
2091 #[test_case(
2092 document_context(&json!({ "use": [
2093 { "protocol": ["a", "b"] },
2094 ]}).to_string()),
2095 document_context(&json!({ "use": [{ "protocol": ["a", "b", "c"] }]}).to_string()),
2096 document_context(&json!({ "use": [
2097 { "protocol": ["a", "b"] },
2098 { "protocol": "c" },
2099 ]}).to_string())
2100 ; "merge capabilities with superset"
2101 )]
2102 #[test_case(
2103 document_context(&json!({ "use": [
2104 { "protocol": ["a", "b"] },
2105 ]}).to_string()),
2106 document_context(&json!({ "use": [{ "protocol": ["b", "c", "d"] }]}).to_string()),
2107 document_context(&json!({ "use": [
2108 { "protocol": ["a", "b"] }, { "protocol": ["c", "d"] }
2109 ]}).to_string())
2110 ; "merge capabilities with intersection"
2111 )]
2112 #[test_case(
2113 document_context(&json!({ "use": [{ "protocol": ["a", "b"] }]}).to_string()),
2114 document_context(&json!({ "use": [
2115 { "protocol": ["c", "b", "d"] },
2116 { "protocol": ["e", "d"] },
2117 ]}).to_string()),
2118 document_context(&json!({ "use": [
2119 {"protocol": ["a", "b"] },
2120 {"protocol": ["c", "d"] },
2121 {"protocol": "e" }]}).to_string())
2122 ; "merge capabilities from multiple arrays"
2123 )]
2124 #[test_case(
2125 document_context(&json!({ "use": [{ "protocol": "foo.bar.Baz", "from": "self"}]}).to_string()),
2126 document_context(&json!({ "use": [{ "service": "foo.bar.Baz", "from": "self"}]}).to_string()),
2127 document_context(&json!({ "use": [
2128 {"protocol": "foo.bar.Baz", "from": "self"},
2129 {"service": "foo.bar.Baz", "from": "self"}]}).to_string())
2130 ; "merge capabilities, types don't match"
2131 )]
2132 #[test_case(
2133 document_context(&json!({ "use": [{ "protocol": "foo.bar.Baz", "from": "self"}]}).to_string()),
2134 document_context(&json!({ "use": [{ "protocol": "foo.bar.Baz" }]}).to_string()),
2135 document_context(&json!({ "use": [
2136 {"protocol": "foo.bar.Baz", "from": "self"},
2137 {"protocol": "foo.bar.Baz"}]}).to_string())
2138 ; "merge capabilities, fields don't match"
2139 )]
2140
2141 fn test_merge_from_duplicate_capability(
2142 mut my: DocumentContext,
2143 other: DocumentContext,
2144 result: DocumentContext,
2145 ) {
2146 my.merge_from(other, &path::Path::new("some/path")).unwrap();
2147 assert_eq!(my, result);
2148 }
2149}