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