1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
6
7use serde_json::Value;
8
9use crate::{
10 defaults::DefaultsMerger,
11 error::{FMLError, Result},
12 frontend::{
13 ExampleBlock, FeatureAdditionChoices, FeatureAdditions, ImportBlock, InlineExampleBlock,
14 ManifestFrontEnd, PartialExampleBlock, PathOnly, Types,
15 },
16 intermediate_representation::{FeatureManifest, ModuleId, TypeRef},
17 util::loaders::{FileLoader, FilePath},
18};
19
20fn parse_typeref_string(input: String) -> Result<(String, Option<String>)> {
21 let mut object_type_iter = input.split(&['<', '>'][..]);
23
24 let type_ref_name = object_type_iter.next().unwrap().trim();
26
27 if ["String", "Int", "Boolean"].contains(&type_ref_name) {
28 return Ok((type_ref_name.to_string(), None));
29 }
30
31 match object_type_iter.next() {
33 Some(object_type_name) => Ok((
34 type_ref_name.to_string(),
35 Some(object_type_name.to_string()),
36 )),
37 None => Ok((type_ref_name.to_string(), None)),
38 }
39}
40
41pub(crate) fn get_typeref_from_string(
42 input: String,
43 types: &HashMap<String, TypeRef>,
44) -> Result<TypeRef, FMLError> {
45 let (type_ref, type_name) = parse_typeref_string(input)?;
46
47 Ok(match type_ref.as_str() {
48 "String" => TypeRef::String,
49 "Int" => TypeRef::Int,
50 "Boolean" => TypeRef::Boolean,
51 "BundleText" | "Text" => TypeRef::BundleText,
52 "BundleImage" | "Drawable" | "Image" => TypeRef::BundleImage,
53 "Enum" => TypeRef::Enum(type_name.unwrap()),
54 "Object" => TypeRef::Object(type_name.unwrap()),
55 "List" => TypeRef::List(Box::new(get_typeref_from_string(
56 type_name.unwrap(),
57 types,
58 )?)),
59 "Option" => TypeRef::Option(Box::new(get_typeref_from_string(
60 type_name.unwrap(),
61 types,
62 )?)),
63 "Map" => {
64 let type_name = type_name.unwrap();
66 let mut map_type_info_iter = type_name.split(',');
67
68 let key_type = map_type_info_iter.next().unwrap().to_string();
69 let value_type = map_type_info_iter.next().unwrap().trim().to_string();
70
71 if key_type.eq("String") {
72 TypeRef::StringMap(Box::new(get_typeref_from_string(value_type, types)?))
73 } else {
74 TypeRef::EnumMap(
75 Box::new(get_typeref_from_string(key_type, types)?),
76 Box::new(get_typeref_from_string(value_type, types)?),
77 )
78 }
79 }
80 type_name => types.get(type_name).cloned().ok_or_else(|| {
81 FMLError::TypeParsingError(format!("{type_name} is not a recognized FML type"))
82 })?,
83 })
84}
85
86#[derive(Debug)]
87pub struct Parser {
88 files: FileLoader,
89 source: FilePath,
90}
91
92impl Parser {
93 pub fn new(files: FileLoader, source: FilePath) -> Result<Parser> {
94 Ok(Parser { source, files })
95 }
96
97 pub fn load_frontend(files: FileLoader, source: &str) -> Result<ManifestFrontEnd> {
98 let source = files.file_path(source)?;
99 let parser: Parser = Parser::new(files, source)?;
100 let mut loading = HashSet::new();
101 parser.load_manifest(&parser.source, &mut loading)
102 }
103
104 pub fn load_manifest(
108 &self,
109 path: &FilePath,
110 loading: &mut HashSet<ModuleId>,
111 ) -> Result<ManifestFrontEnd> {
112 let id: ModuleId = path.try_into()?;
113 let files = &self.files;
114
115 let mut parent = files
116 .read::<ManifestFrontEnd>(path)
117 .map_err(|e| FMLError::FMLModuleError(id.clone(), e.to_string()))?;
118
119 self.canonicalize_import_paths(path, &mut parent.imports)
123 .map_err(|e| FMLError::FMLModuleError(id.clone(), e.to_string()))?;
124
125 self.inline_manifest_resources(path, &mut parent)?;
126
127 loading.insert(id.clone());
128 parent
129 .includes()
130 .iter()
131 .try_fold(parent, |parent: ManifestFrontEnd, f| {
132 let src_path = files.join(path, f)?;
133 let child_id = ModuleId::try_from(&src_path)?;
134 Ok(if !loading.contains(&child_id) {
135 let manifest = self.load_manifest(&src_path, loading)?;
136 self.merge_manifest(&src_path, parent, &src_path, manifest)
137 .map_err(|e| FMLError::FMLModuleError(id.clone(), e.to_string()))?
138 } else {
139 parent
140 })
141 })
142 }
143
144 fn merge_manifest(
147 &self,
148 parent_path: &FilePath,
149 parent: ManifestFrontEnd,
150 child_path: &FilePath,
151 child: ManifestFrontEnd,
152 ) -> Result<ManifestFrontEnd> {
153 self.check_can_merge_manifest(parent_path, &parent, child_path, &child)?;
154
155 let features = merge_map(
157 &parent.features,
158 &child.features,
159 "Features",
160 "features",
161 child_path,
162 )?;
163
164 let p_types = &parent.legacy_types.unwrap_or(parent.types);
165 let c_types = &child.legacy_types.unwrap_or(child.types);
166
167 let objects = merge_map(
168 &c_types.objects,
169 &p_types.objects,
170 "Objects",
171 "objects",
172 child_path,
173 )?;
174 let enums = merge_map(&c_types.enums, &p_types.enums, "Enums", "enums", child_path)?;
175
176 let imports = self.merge_import_block_list(&parent.imports, &child.imports)?;
177
178 let merged = ManifestFrontEnd {
179 features,
180 types: Types { enums, objects },
181 legacy_types: None,
182 imports,
183 ..parent
184 };
185
186 Ok(merged)
187 }
188
189 fn inline_manifest_resources(
190 &self,
191 path: &FilePath,
192 manifest: &mut ManifestFrontEnd,
193 ) -> Result<()> {
194 for feature in manifest.features.values_mut() {
195 let as_typed = &feature.examples;
196 let mut inlined = Vec::with_capacity(as_typed.len());
197 for example in as_typed {
198 inlined.push(example.inline(&self.files, path)?);
199 }
200 feature.examples = inlined;
201 }
202
203 for import in &mut manifest.imports {
204 let mut features: BTreeMap<String, FeatureAdditionChoices> = Default::default();
205 for (feature_id, additions) in &import.features {
206 let additions: FeatureAdditions = additions.clone().into();
207 features.insert(
208 feature_id.clone(),
209 additions.inline(&self.files, path)?.into(),
210 );
211 }
212 import.features = features;
213 }
214
215 Ok(())
216 }
217
218 fn load_imports(
223 &self,
224 current: &FilePath,
225 channel: Option<&str>,
226 imports: &mut BTreeMap<ModuleId, FeatureManifest>,
227 ) -> Result<ModuleId> {
229 let id = current.try_into()?;
230 if imports.contains_key(&id) {
231 return Ok(id);
232 }
233 imports.insert(id.clone(), Default::default());
235
236 let frontend = self.load_manifest(current, &mut HashSet::new())?;
239
240 let channel = if frontend.channels.len() == 1 {
244 frontend.channels.first().map(String::as_str)
245 } else {
246 channel
247 };
248
249 let mut manifest = frontend.get_intermediate_representation(&id, channel)?;
250
251 let mut imported_feature_id_map = BTreeMap::new();
257
258 for block in &frontend.imports {
259 let path = self.files.join(current, &block.path)?;
261 let child_id = self.load_imports(&path, Some(&block.channel), imports)?;
263 let child_manifest = imports.get_mut(&child_id).expect("just loaded this file");
264
265 check_can_import_manifest(&manifest, child_manifest)?;
269
270 let mut feature_ids = BTreeSet::new();
276
277 let merger = DefaultsMerger::new(
281 &child_manifest.obj_defs,
282 frontend.channels.clone(),
283 channel.map(str::to_string),
284 );
285
286 let feature_map = &mut child_manifest.feature_defs;
290
291 for (f, feature_additions) in &block.features {
295 let feature_def = feature_map.get_mut(f).ok_or_else(|| {
296 FMLError::FMLModuleError(
297 id.clone(),
298 format!("Cannot override defaults for `{f}` feature from {child_id}"),
299 )
300 })?;
301 let additions: FeatureAdditions = feature_additions.clone().into();
303
304 feature_def
306 .examples
307 .extend(additions.examples.iter().map(Into::into));
308
309 merger
311 .merge_feature_defaults(feature_def, &Some(additions.defaults))
312 .map_err(|e| FMLError::FMLModuleError(child_id.clone(), e.to_string()))?;
313
314 feature_ids.insert(f.clone());
315 }
316
317 imported_feature_id_map.insert(child_id.clone(), feature_ids);
319 }
320
321 manifest.imported_features = imported_feature_id_map;
322 imports.insert(id.clone(), manifest);
323
324 Ok(id)
325 }
326
327 pub fn get_intermediate_representation(
328 &self,
329 channel: Option<&str>,
330 ) -> Result<FeatureManifest, FMLError> {
331 let mut manifests = BTreeMap::new();
332 let id = self.load_imports(&self.source, channel, &mut manifests)?;
333 let mut fm = manifests
334 .remove(&id)
335 .expect("Top level manifest should always be present");
336
337 for child in manifests.values() {
338 check_can_import_manifest(&fm, child)?;
339 }
340
341 fm.all_imports = manifests;
342
343 Ok(fm)
344 }
345}
346
347impl Parser {
348 fn check_can_merge_manifest(
349 &self,
350 parent_path: &FilePath,
351 parent: &ManifestFrontEnd,
352 child_path: &FilePath,
353 child: &ManifestFrontEnd,
354 ) -> Result<()> {
355 if !child.channels.is_empty() {
356 let child = &child.channels;
357 let child = child.iter().collect::<HashSet<&String>>();
358 let parent = &parent.channels;
359 let parent = parent.iter().collect::<HashSet<&String>>();
360 if !child.is_subset(&parent) {
361 return Err(FMLError::ValidationError(
362 "channels".to_string(),
363 format!(
364 "Included manifest should not define its own channels: {}",
365 child_path
366 ),
367 ));
368 }
369 }
370
371 if let Some(about) = &child.about {
372 if !about.is_includable() {
373 return Err(FMLError::ValidationError(
374 "about".to_string(),
375 format!("Only files that don't already correspond to generated files may be included: file has a `class` and `package`/`module` name: {}", child_path),
376 ));
377 }
378 }
379
380 let mut map = Default::default();
381 self.check_can_merge_imports(parent_path, &parent.imports, &mut map)?;
382 self.check_can_merge_imports(child_path, &child.imports, &mut map)?;
383
384 Ok(())
385 }
386
387 fn canonicalize_import_paths(
388 &self,
389 path: &FilePath,
390 blocks: &mut Vec<ImportBlock>,
391 ) -> Result<()> {
392 for ib in blocks {
393 let p = &self.files.join(path, &ib.path)?;
394 ib.path = p.canonicalize()?.to_string();
395 }
396 Ok(())
397 }
398
399 fn check_can_merge_imports(
400 &self,
401 path: &FilePath,
402 blocks: &Vec<ImportBlock>,
403 map: &mut HashMap<String, String>,
404 ) -> Result<()> {
405 for b in blocks {
406 let id = &b.path;
407 let channel = &b.channel;
408 let existing = map.insert(id.clone(), channel.clone());
409 if let Some(v) = existing {
410 if &v != channel {
411 return Err(FMLError::FMLModuleError(
412 path.try_into()?,
413 format!(
414 "File {} is imported with two different channels: {} and {}",
415 id, v, &channel
416 ),
417 ));
418 }
419 }
420 }
421 Ok(())
422 }
423
424 fn merge_import_block_list(
425 &self,
426 parent: &[ImportBlock],
427 child: &[ImportBlock],
428 ) -> Result<Vec<ImportBlock>> {
429 let mut map = parent
430 .iter()
431 .map(|im| (im.path.clone(), im.clone()))
432 .collect::<HashMap<_, _>>();
433
434 for cib in child {
435 let path = &cib.path;
436 if let Some(pib) = map.get(path) {
437 let merged = merge_import_block(cib, pib)?;
441 map.insert(path.clone(), merged);
442 } else {
443 map.insert(path.clone(), cib.clone());
444 }
445 }
446
447 Ok(map.values().map(|b| b.to_owned()).collect::<Vec<_>>())
448 }
449}
450
451fn merge_map<T: Clone>(
452 a: &BTreeMap<String, T>,
453 b: &BTreeMap<String, T>,
454 display_key: &str,
455 key: &str,
456 child_path: &FilePath,
457) -> Result<BTreeMap<String, T>> {
458 let mut set = HashSet::new();
459
460 let (a, b) = if a.len() < b.len() { (a, b) } else { (b, a) };
461
462 let mut map = b.clone();
463
464 for (k, v) in a {
465 if map.contains_key(k) {
466 set.insert(k.clone());
467 } else {
468 map.insert(k.clone(), v.clone());
469 }
470 }
471
472 if set.is_empty() {
473 Ok(map)
474 } else {
475 Err(FMLError::ValidationError(
476 format!("{}/{:?}", key, set),
477 format!(
478 "{} cannot be defined twice, overloaded definition detected at {}",
479 display_key, child_path,
480 ),
481 ))
482 }
483}
484
485fn merge_import_block(a: &ImportBlock, b: &ImportBlock) -> Result<ImportBlock> {
486 let mut block = a.clone();
487
488 for (id, additions) in &b.features {
489 block
490 .features
491 .entry(id.clone())
492 .and_modify(|existing| existing.merge(additions))
493 .or_insert(additions.clone());
494 }
495 Ok(block)
496}
497
498fn check_can_import_manifest(parent: &FeatureManifest, child: &FeatureManifest) -> Result<()> {
500 check_can_import_list(parent, child, "enum", |fm: &FeatureManifest| {
501 fm.enum_defs.keys().collect()
502 })?;
503 check_can_import_list(parent, child, "objects", |fm: &FeatureManifest| {
504 fm.obj_defs.keys().collect()
505 })?;
506 check_can_import_list(parent, child, "features", |fm: &FeatureManifest| {
507 fm.feature_defs.keys().collect()
508 })?;
509
510 Ok(())
511}
512
513fn check_can_import_list(
514 parent: &FeatureManifest,
515 child: &FeatureManifest,
516 key: &str,
517 f: fn(&FeatureManifest) -> HashSet<&String>,
518) -> Result<()> {
519 let p = f(parent);
520 let c = f(child);
521 let intersection = p.intersection(&c).collect::<HashSet<_>>();
522 if !intersection.is_empty() {
523 Err(FMLError::ValidationError(
524 key.to_string(),
525 format!(
526 "`{}` types {:?} conflict when {} imports {}",
527 key, &intersection, &parent.id, &child.id
528 ),
529 ))
530 } else {
531 Ok(())
532 }
533}
534
535impl ExampleBlock {
536 fn inline(&self, files: &FileLoader, root: &FilePath) -> Result<Self> {
537 Ok(match self {
538 Self::Inline(_) => self.clone(),
539 Self::Partial(PartialExampleBlock { metadata, path }) => {
540 let file = files.join(root, path)?;
541 let value: Value = files.read(&file)?;
542 Self::Inline(InlineExampleBlock {
543 metadata: metadata.to_owned(),
544 value,
545 })
546 }
547 Self::BarePath(path) | Self::Path(PathOnly { path }) => {
548 let file = files.join(root, path)?;
549 let value: InlineExampleBlock = files.read(&file)?;
550 Self::Inline(value)
551 }
552 })
553 }
554}
555
556impl FeatureAdditionChoices {
557 fn merge(&mut self, other: &Self) {
558 match (self, other) {
559 (Self::FeatureAdditions(a), Self::FeatureAdditions(b)) => a.merge(b),
560 _ => unreachable!("FeatureAdditionChoices should have been rationalized already. This is a bug in nimbus-fml"),
561 };
562 }
563}
564
565impl FeatureAdditions {
566 fn inline(&self, files: &FileLoader, root: &FilePath) -> Result<Self> {
567 let examples = self
568 .examples
569 .iter()
570 .map(|ex| ex.inline(files, root))
571 .collect::<Result<_>>()?;
572 Ok(Self {
573 examples,
574 defaults: self.defaults.clone(),
575 })
576 }
577
578 fn merge(&mut self, other: &Self) {
579 self.examples.extend(other.examples.clone());
580 self.defaults.extend(other.defaults.clone());
581 }
582}
583
584#[cfg(test)]
585mod unit_tests {
586
587 use std::{
588 path::{Path, PathBuf},
589 vec,
590 };
591
592 use serde_json::json;
593
594 use super::*;
595 use crate::{
596 error::Result,
597 frontend::ImportBlock,
598 intermediate_representation::{PropDef, VariantDef},
599 util::{join, pkg_dir},
600 };
601
602 #[test]
603 fn test_parse_from_front_end_representation() -> Result<()> {
604 let path = join(pkg_dir(), "fixtures/fe/nimbus_features.yaml");
605 let path = Path::new(&path);
606 let files = FileLoader::default()?;
607 let parser = Parser::new(files, path.into())?;
608 let ir = parser.get_intermediate_representation(Some("release"))?;
609
610 assert!(ir.enum_defs.len() == 1);
612 let enum_def = &ir.enum_defs["PlayerProfile"];
613 assert!(enum_def.name == *"PlayerProfile");
614 assert!(enum_def.doc == *"This is an enum type");
615 assert!(enum_def.variants.contains(&VariantDef {
616 name: "adult".to_string(),
617 doc: "This represents an adult player profile".to_string()
618 }));
619 assert!(enum_def.variants.contains(&VariantDef {
620 name: "child".to_string(),
621 doc: "This represents a child player profile".to_string()
622 }));
623
624 assert!(ir.obj_defs.len() == 1);
626 let obj_def = &ir.obj_defs["Button"];
627 assert!(obj_def.name == *"Button");
628 assert!(obj_def.doc == *"This is a button object");
629 assert!(obj_def.props.contains(&PropDef::with_doc(
630 "label",
631 "This is the label for the button",
632 &TypeRef::String,
633 &serde_json::json!("REQUIRED FIELD")
634 )));
635 assert!(obj_def.props.contains(&PropDef::with_doc(
636 "color",
637 "This is the color of the button",
638 &TypeRef::Option(Box::new(TypeRef::String)),
639 &serde_json::Value::Null
640 )));
641
642 assert!(ir.feature_defs.len() == 1);
644 let feature_def = ir.get_feature("dialog-appearance").unwrap();
645 assert!(feature_def.name == *"dialog-appearance");
646 assert!(feature_def.doc() == *"This is the appearance of the dialog");
647 let positive_button = feature_def
648 .props
649 .iter()
650 .find(|x| x.name == "positive")
651 .unwrap();
652 assert!(positive_button.name == *"positive");
653 assert!(positive_button.doc == *"This is a positive button");
654 assert!(positive_button.typ == TypeRef::Object("Button".to_string()));
655 assert!(positive_button.default.get("label").unwrap().as_str() == Some("Ok then"));
658 assert!(positive_button.default.get("color").unwrap().as_str() == Some("green"));
659 let negative_button = feature_def
660 .props
661 .iter()
662 .find(|x| x.name == "negative")
663 .unwrap();
664 assert!(negative_button.name == *"negative");
665 assert!(negative_button.doc == *"This is a negative button");
666 assert!(negative_button.typ == TypeRef::Object("Button".to_string()));
667 assert!(negative_button.default.get("label").unwrap().as_str() == Some("Not this time"));
668 assert!(negative_button.default.get("color").unwrap().as_str() == Some("red"));
669 let background_color = feature_def
670 .props
671 .iter()
672 .find(|x| x.name == "background-color")
673 .unwrap();
674 assert!(background_color.name == *"background-color");
675 assert!(background_color.doc == *"This is the background color");
676 assert!(background_color.typ == TypeRef::String);
677 assert!(background_color.default.as_str() == Some("white"));
678 let player_mapping = feature_def
679 .props
680 .iter()
681 .find(|x| x.name == "player-mapping")
682 .unwrap();
683 assert!(player_mapping.name == *"player-mapping");
684 assert!(player_mapping.doc == *"This is the map of the player type to a button");
685 assert!(
686 player_mapping.typ
687 == TypeRef::EnumMap(
688 Box::new(TypeRef::Enum("PlayerProfile".to_string())),
689 Box::new(TypeRef::Object("Button".to_string()))
690 )
691 );
692 assert!(
693 player_mapping.default
694 == json!({
695 "child": {
696 "label": "Play game!",
697 "color": "green"
698 },
699 "adult": {
700 "label": "Play game!",
701 "color": "blue",
702 }
703 })
704 );
705
706 Ok(())
707 }
708
709 #[test]
710 fn test_merging_defaults() -> Result<()> {
711 let path = join(pkg_dir(), "fixtures/fe/default_merging.yaml");
712 let path = Path::new(&path);
713 let files = FileLoader::default()?;
714 let parser = Parser::new(files, path.into())?;
715 let ir = parser.get_intermediate_representation(Some("release"))?;
716 let feature_def = ir.get_feature("dialog-appearance").unwrap();
717 let positive_button = feature_def
718 .props
719 .iter()
720 .find(|x| x.name == "positive")
721 .unwrap();
722 assert_eq!(
724 positive_button
725 .default
726 .get("alt-text")
727 .unwrap()
728 .as_str()
729 .unwrap(),
730 "Go Ahead!"
731 );
732 assert_eq!(
735 positive_button
736 .default
737 .get("label")
738 .unwrap()
739 .as_str()
740 .unwrap(),
741 "Ok then"
742 );
743 assert_eq!(
746 positive_button
747 .default
748 .get("color")
749 .unwrap()
750 .as_str()
751 .unwrap(),
752 "green"
753 );
754 let files = FileLoader::default()?;
756 let parser = Parser::new(files, path.into())?;
757 let ir = parser.get_intermediate_representation(Some("nightly"))?;
758 let feature_def = ir.get_feature("dialog-appearance").unwrap();
759 let positive_button = feature_def
760 .props
761 .iter()
762 .find(|x| x.name == "positive")
763 .unwrap();
764 assert_eq!(
770 positive_button
771 .default
772 .get("color")
773 .unwrap()
774 .as_str()
775 .unwrap(),
776 "bright-red"
777 );
778 assert_eq!(
781 positive_button
782 .default
783 .get("alt-text")
784 .unwrap()
785 .as_str()
786 .unwrap(),
787 "Go Ahead!"
788 );
789 Ok(())
790 }
791
792 #[test]
793 fn test_convert_to_typeref_string() -> Result<()> {
794 let types = Default::default();
796 assert_eq!(
797 get_typeref_from_string("String".to_string(), &types).unwrap(),
798 TypeRef::String
799 );
800 get_typeref_from_string("string".to_string(), &types).unwrap_err();
801 get_typeref_from_string("str".to_string(), &types).unwrap_err();
802
803 Ok(())
804 }
805
806 #[test]
807 fn test_convert_to_typeref_int() -> Result<()> {
808 let types = Default::default();
810 assert_eq!(
811 get_typeref_from_string("Int".to_string(), &types).unwrap(),
812 TypeRef::Int
813 );
814 get_typeref_from_string("integer".to_string(), &types).unwrap_err();
815 get_typeref_from_string("int".to_string(), &types).unwrap_err();
816
817 Ok(())
818 }
819
820 #[test]
821 fn test_convert_to_typeref_boolean() -> Result<()> {
822 let types = Default::default();
824 assert_eq!(
825 get_typeref_from_string("Boolean".to_string(), &types).unwrap(),
826 TypeRef::Boolean
827 );
828 get_typeref_from_string("boolean".to_string(), &types).unwrap_err();
829 get_typeref_from_string("bool".to_string(), &types).unwrap_err();
830
831 Ok(())
832 }
833
834 #[test]
835 fn test_convert_to_typeref_bundletext() -> Result<()> {
836 let types = Default::default();
838 get_typeref_from_string("bundletext(something)".to_string(), &types).unwrap_err();
839 get_typeref_from_string("BundleText()".to_string(), &types).unwrap_err();
840
841 Ok(())
849 }
850
851 #[test]
852 fn test_convert_to_typeref_bundleimage() -> Result<()> {
853 let types = Default::default();
855 assert_eq!(
856 get_typeref_from_string("BundleImage<test_name>".to_string(), &types).unwrap(),
857 TypeRef::BundleImage
858 );
859 get_typeref_from_string("bundleimage(something)".to_string(), &types).unwrap_err();
860 get_typeref_from_string("BundleImage()".to_string(), &types).unwrap_err();
861
862 Ok(())
870 }
871
872 #[test]
873 fn test_convert_to_typeref_enum() -> Result<()> {
874 let types = Default::default();
876 assert_eq!(
877 get_typeref_from_string("Enum<test_name>".to_string(), &types).unwrap(),
878 TypeRef::Enum("test_name".to_string())
879 );
880 get_typeref_from_string("enum(something)".to_string(), &types).unwrap_err();
881 get_typeref_from_string("Enum()".to_string(), &types).unwrap_err();
882
883 Ok(())
891 }
892
893 #[test]
894 fn test_convert_to_typeref_object() -> Result<()> {
895 let types = Default::default();
897 assert_eq!(
898 get_typeref_from_string("Object<test_name>".to_string(), &types).unwrap(),
899 TypeRef::Object("test_name".to_string())
900 );
901 get_typeref_from_string("object(something)".to_string(), &types).unwrap_err();
902 get_typeref_from_string("Object()".to_string(), &types).unwrap_err();
903
904 Ok(())
912 }
913
914 #[test]
915 fn test_convert_to_typeref_list() -> Result<()> {
916 let types = Default::default();
918 assert_eq!(
919 get_typeref_from_string("List<String>".to_string(), &types).unwrap(),
920 TypeRef::List(Box::new(TypeRef::String))
921 );
922 assert_eq!(
923 get_typeref_from_string("List<Int>".to_string(), &types).unwrap(),
924 TypeRef::List(Box::new(TypeRef::Int))
925 );
926 assert_eq!(
927 get_typeref_from_string("List<Boolean>".to_string(), &types).unwrap(),
928 TypeRef::List(Box::new(TypeRef::Boolean))
929 );
930
931 let mut types: HashMap<_, _> = Default::default();
933 types.insert(
934 "TestEnum".to_string(),
935 TypeRef::Enum("TestEnum".to_string()),
936 );
937 types.insert(
938 "TestObject".to_string(),
939 TypeRef::Object("TestObject".to_string()),
940 );
941
942 assert_eq!(
943 get_typeref_from_string("List<TestEnum>".to_string(), &types).unwrap(),
944 TypeRef::List(Box::new(TypeRef::Enum("TestEnum".to_string())))
945 );
946 assert_eq!(
947 get_typeref_from_string("List<TestObject>".to_string(), &types).unwrap(),
948 TypeRef::List(Box::new(TypeRef::Object("TestObject".to_string())))
949 );
950
951 get_typeref_from_string("list(something)".to_string(), &types).unwrap_err();
952 get_typeref_from_string("List()".to_string(), &types).unwrap_err();
953
954 Ok(())
962 }
963
964 #[test]
965 fn test_convert_to_typeref_option() -> Result<()> {
966 let types = Default::default();
968 assert_eq!(
969 get_typeref_from_string("Option<String>".to_string(), &types).unwrap(),
970 TypeRef::Option(Box::new(TypeRef::String))
971 );
972 assert_eq!(
973 get_typeref_from_string("Option<Int>".to_string(), &types).unwrap(),
974 TypeRef::Option(Box::new(TypeRef::Int))
975 );
976 assert_eq!(
977 get_typeref_from_string("Option<Boolean>".to_string(), &types).unwrap(),
978 TypeRef::Option(Box::new(TypeRef::Boolean))
979 );
980
981 let mut types = HashMap::new();
983 types.insert(
984 "TestEnum".to_string(),
985 TypeRef::Enum("TestEnum".to_string()),
986 );
987 types.insert(
988 "TestObject".to_string(),
989 TypeRef::Object("TestObject".to_string()),
990 );
991 assert_eq!(
992 get_typeref_from_string("Option<TestEnum>".to_string(), &types).unwrap(),
993 TypeRef::Option(Box::new(TypeRef::Enum("TestEnum".to_string())))
994 );
995 assert_eq!(
996 get_typeref_from_string("Option<TestObject>".to_string(), &types).unwrap(),
997 TypeRef::Option(Box::new(TypeRef::Object("TestObject".to_string())))
998 );
999
1000 get_typeref_from_string("option(something)".to_string(), &types).unwrap_err();
1001 get_typeref_from_string("Option(Something)".to_string(), &types).unwrap_err();
1002
1003 Ok(())
1011 }
1012
1013 #[test]
1014 fn test_convert_to_typeref_map() -> Result<()> {
1015 let types = Default::default();
1017 assert_eq!(
1018 get_typeref_from_string("Map<String, String>".to_string(), &types).unwrap(),
1019 TypeRef::StringMap(Box::new(TypeRef::String))
1020 );
1021 assert_eq!(
1022 get_typeref_from_string("Map<String, Int>".to_string(), &types).unwrap(),
1023 TypeRef::StringMap(Box::new(TypeRef::Int))
1024 );
1025 assert_eq!(
1026 get_typeref_from_string("Map<String, Boolean>".to_string(), &types).unwrap(),
1027 TypeRef::StringMap(Box::new(TypeRef::Boolean))
1028 );
1029
1030 let mut types = HashMap::new();
1032 types.insert(
1033 "TestEnum".to_string(),
1034 TypeRef::Enum("TestEnum".to_string()),
1035 );
1036 types.insert(
1037 "TestObject".to_string(),
1038 TypeRef::Object("TestObject".to_string()),
1039 );
1040 assert_eq!(
1041 get_typeref_from_string("Map<String, TestEnum>".to_string(), &types).unwrap(),
1042 TypeRef::StringMap(Box::new(TypeRef::Enum("TestEnum".to_string())))
1043 );
1044 assert_eq!(
1045 get_typeref_from_string("Map<String, TestObject>".to_string(), &types).unwrap(),
1046 TypeRef::StringMap(Box::new(TypeRef::Object("TestObject".to_string())))
1047 );
1048 assert_eq!(
1049 get_typeref_from_string("Map<TestEnum, String>".to_string(), &types).unwrap(),
1050 TypeRef::EnumMap(
1051 Box::new(TypeRef::Enum("TestEnum".to_string())),
1052 Box::new(TypeRef::String)
1053 )
1054 );
1055 assert_eq!(
1056 get_typeref_from_string("Map<TestEnum, TestObject>".to_string(), &types).unwrap(),
1057 TypeRef::EnumMap(
1058 Box::new(TypeRef::Enum("TestEnum".to_string())),
1059 Box::new(TypeRef::Object("TestObject".to_string()))
1060 )
1061 );
1062
1063 get_typeref_from_string("map(something)".to_string(), &Default::default()).unwrap_err();
1064 get_typeref_from_string("Map(Something)".to_string(), &Default::default()).unwrap_err();
1065
1066 Ok(())
1074 }
1075
1076 #[test]
1077 fn test_include_check_can_merge_manifest() -> Result<()> {
1078 let files = FileLoader::default()?;
1079 let parser = Parser::new(files, std::env::temp_dir().as_path().into())?;
1080 let parent_path: FilePath = std::env::temp_dir().as_path().into();
1081 let child_path = parent_path.join("http://not-needed.com")?;
1082 let parent = ManifestFrontEnd {
1083 channels: vec!["alice".to_string(), "bob".to_string()],
1084 ..Default::default()
1085 };
1086 let child = ManifestFrontEnd {
1087 channels: vec!["alice".to_string(), "bob".to_string()],
1088 ..Default::default()
1089 };
1090
1091 assert!(parser
1092 .check_can_merge_manifest(&parent_path, &parent, &child_path, &child)
1093 .is_ok());
1094
1095 let child = ManifestFrontEnd {
1096 channels: vec!["eve".to_string()],
1097 ..Default::default()
1098 };
1099
1100 assert!(parser
1101 .check_can_merge_manifest(&parent_path, &parent, &child_path, &child)
1102 .is_err());
1103
1104 Ok(())
1105 }
1106
1107 #[test]
1108 fn test_include_check_can_merge_manifest_with_imports() -> Result<()> {
1109 let files = FileLoader::default()?;
1110 let parser = Parser::new(files, std::env::temp_dir().as_path().into())?;
1111 let parent_path: FilePath = std::env::temp_dir().as_path().into();
1112 let child_path = parent_path.join("http://child")?;
1113 let parent = ManifestFrontEnd {
1114 channels: vec!["alice".to_string(), "bob".to_string()],
1115 imports: vec![ImportBlock {
1116 path: "absolute_path".to_string(),
1117 channel: "one_channel".to_string(),
1118 features: Default::default(),
1119 }],
1120 ..Default::default()
1121 };
1122 let child = ManifestFrontEnd {
1123 channels: vec!["alice".to_string(), "bob".to_string()],
1124 imports: vec![ImportBlock {
1125 path: "absolute_path".to_string(),
1126 channel: "another_channel".to_string(),
1127 features: Default::default(),
1128 }],
1129 ..Default::default()
1130 };
1131
1132 let mut map = Default::default();
1133 let res = parser.check_can_merge_imports(&parent_path, &parent.imports, &mut map);
1134 assert!(res.is_ok());
1135 assert_eq!(map.get("absolute_path").unwrap(), "one_channel");
1136
1137 let err_msg = "Problem with http://child/: File absolute_path is imported with two different channels: one_channel and another_channel";
1138 let res = parser.check_can_merge_imports(&child_path, &child.imports, &mut map);
1139 assert!(res.is_err());
1140 assert_eq!(res.unwrap_err().to_string(), err_msg.to_string());
1141
1142 let res = parser.check_can_merge_manifest(&parent_path, &parent, &child_path, &child);
1143 assert!(res.is_err());
1144 assert_eq!(res.unwrap_err().to_string(), err_msg.to_string());
1145
1146 Ok(())
1147 }
1148
1149 #[test]
1150 fn test_include_circular_includes() -> Result<()> {
1151 use crate::util::pkg_dir;
1152 let path = PathBuf::from(pkg_dir()).join("fixtures/fe/including/circular/snake.yaml");
1154
1155 let files = FileLoader::default()?;
1156 let parser = Parser::new(files, path.as_path().into())?;
1157 let ir = parser.get_intermediate_representation(Some("release"));
1158 assert!(ir.is_ok());
1159
1160 Ok(())
1161 }
1162
1163 #[test]
1164 fn test_include_deeply_nested_includes() -> Result<()> {
1165 use crate::util::pkg_dir;
1166 let path_buf = PathBuf::from(pkg_dir()).join("fixtures/fe/including/deep/00-head.yaml");
1169
1170 let files = FileLoader::default()?;
1171 let parser = Parser::new(files, path_buf.as_path().into())?;
1172
1173 let ir = parser.get_intermediate_representation(Some("release"))?;
1174 assert_eq!(ir.feature_defs.len(), 1);
1175
1176 Ok(())
1177 }
1178}